device.dart 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. import 'dart:io';
  2. import 'dart:math';
  3. import 'dart:ui';
  4. import 'package:device_info_plus/device_info_plus.dart';
  5. import 'package:flutter/material.dart';
  6. class Device {
  7. static Locale? locale;
  8. AndroidDeviceInfo? androidDeviceInfo;
  9. final Directory baseDir;
  10. Device(this.context, this.baseDir);
  11. // ✅ 判断是否32位设备(安全访问)
  12. bool get is32BitDevice {
  13. if (Platform.isIOS) return false;
  14. final info = androidDeviceInfo;
  15. if (info == null) return false; // 默认假设64位
  16. return !info.supportedAbis.any((abi) => abi.contains('64'));
  17. }
  18. /// 获取平台性能(安全默认值)
  19. int get androidSdkInt {
  20. final info = androidDeviceInfo;
  21. return info?.version.sdkInt ?? 100; // 默认假设高版本
  22. }
  23. bool get isOldAndroid => androidSdkInt < 26;
  24. bool get isLowRamDevice {
  25. final info = androidDeviceInfo;
  26. return info?.isLowRamDevice ?? false; // 默认假设不是
  27. }
  28. bool get lowCpu => Platform.numberOfProcessors <= 2;
  29. /// ✅ 综合判断是否低端设备(即使 androidDeviceInfo 为 null 也能工作)
  30. bool get isLowEndDevice {
  31. if (Platform.isIOS) return false;
  32. // 基于 CPU 的快速判断(不依赖 androidDeviceInfo)
  33. if (lowCpu) return true;
  34. // 如果设备信息已加载,进行详细判断
  35. final info = androidDeviceInfo;
  36. if (info != null) {
  37. return isOldAndroid || isLowRamDevice || is32BitDevice;
  38. }
  39. // 默认假设不是低端设备
  40. return false;
  41. }
  42. static double get devPixelRatio => PlatformDispatcher.instance.views.first.devicePixelRatio;
  43. static Size get physicalSize => PlatformDispatcher.instance.views.first.physicalSize;
  44. static Size get logicalSize => physicalSize / devPixelRatio;
  45. ///final Size screenSize;
  46. final BuildContext context;
  47. final aspectRatio = 3.0 / 2.0; // 图片宽高比按照 2 / 3 设定,相应的 board play 区域也是这个比例
  48. final _tabletProfile = DeviceProfile(horizontalPadding: 60, verticalPadding: 10);
  49. final _profile = DeviceProfile(horizontalPadding: 10, verticalPadding: 10);
  50. double _bannerHeight = 0;
  51. set bannerHeight(double height) {
  52. _bannerHeight = height;
  53. }
  54. /// 获取当前配置
  55. DeviceProfile get profile => isTablet ? _tabletProfile : _profile;
  56. /// 屏幕尺寸
  57. Size get screenSize => MediaQuery.of(context).size;
  58. // 真实像素密度
  59. double get realPixelRatio => devPixelRatio;
  60. /// 像素密度
  61. /// 像素密度:低端机通过降低采样倍率来保护内存
  62. /// 这里的逻辑是:高端机用真实DPR,低端机降级,但不直接降到1(除非设备极差)
  63. double get effectivePixelRatio {
  64. double realDPR = PlatformDispatcher.instance.views.first.devicePixelRatio;
  65. if (isLowEndDevice) {
  66. return (realDPR > 2.0) ? 1.5 : 1.0; // 适当降级,保留一点清晰度
  67. }
  68. return realDPR;
  69. }
  70. /// ✅ 建议的图片质量(优先基于屏幕尺寸,避免依赖 androidDeviceInfo)
  71. String get suggestedQuality {
  72. // 平板设备:根据是否为低端平板做降级处理,避免对低端平板返回过高分辨率
  73. if (isTablet) {
  74. if (isLowEndDevice) {
  75. // 低端平板使用中等质量
  76. return "1800";
  77. }
  78. // 中高端平板使用更高质量
  79. return "2400";
  80. }
  81. // 手机设备:低端使用较低质量,其他使用默认质量
  82. if (isLowEndDevice) return "1200";
  83. // 根据实际像素比适度调整(高 DPR 的手机可以适当提高)
  84. final double dpr = devPixelRatio;
  85. if (dpr >= 3.0) return "2000";
  86. if (dpr >= 2.0) return "1800";
  87. return "1200";
  88. }
  89. /// safeArea高度 Z
  90. double get safeAreaHeight => MediaQuery.of(context).viewPadding.top + MediaQuery.of(context).viewPadding.bottom;
  91. /// 获取appbar的高度
  92. double get appBarHeight => AppBar().preferredSize.height;
  93. /// Play 尺寸
  94. Size get boardSize =>
  95. Size(screenSize.width - profile.horizontalPadding * 2, screenSize.height - (safeAreaHeight + appBarHeight + profile.verticalPadding * 2));
  96. /// 根据谷歌的描述,banner尺寸最小50px, 最大不超过15%
  97. double get estimateBannerHeight => max(50, screenSize.height * 0.15).truncateToDouble();
  98. // double get bannerHeight => _bannerHeight == 0 ? estimateBannerHeight : _bannerHeight;
  99. double get bannerHeight => _bannerHeight != 0
  100. ? _bannerHeight
  101. : isTablet
  102. ? 90
  103. : 50;
  104. /// 是否平板
  105. bool get isTablet => screenSize.shortestSide >= 600;
  106. String filePath(String relativePath) => '${baseDir.path}/$relativePath';
  107. // board核心绘制区域
  108. Rect get targetRect {
  109. final double availableHeight = screenSize.height - appBarHeight - bannerHeight - 10; // -10 是预留给banner广告的间隔距离
  110. final double paddedWidth = screenSize.width - 2 * profile.horizontalPadding;
  111. final double paddedHeight = availableHeight - 2 * profile.verticalPadding;
  112. final double targetWidth = paddedWidth;
  113. final double targetHeight = targetWidth * aspectRatio;
  114. final double finalPuzzleWidth;
  115. final double finalPuzzleHeight;
  116. if (targetHeight > paddedHeight) {
  117. finalPuzzleHeight = paddedHeight;
  118. finalPuzzleWidth = paddedHeight / aspectRatio;
  119. } else {
  120. finalPuzzleWidth = targetWidth;
  121. finalPuzzleHeight = targetHeight;
  122. }
  123. final double targetYStart = appBarHeight + profile.verticalPadding + (paddedHeight - finalPuzzleHeight) / 2;
  124. final double targetXStart = profile.horizontalPadding + (paddedWidth - finalPuzzleWidth) / 2;
  125. final newTargetRect = Rect.fromLTWH(targetXStart, targetYStart, finalPuzzleWidth, finalPuzzleHeight);
  126. return newTargetRect;
  127. }
  128. // 最佳图片分辨率
  129. Size get bestImageSize => Size(targetRect.width * effectivePixelRatio, targetRect.height * effectivePixelRatio);
  130. }
  131. class DeviceProfile {
  132. final double horizontalPadding; // 水平padding
  133. final double verticalPadding; // 垂直padding
  134. DeviceProfile({
  135. this.horizontalPadding = 10,
  136. this.verticalPadding = 10, //Play board padding
  137. });
  138. }