device.dart 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  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. if (isTablet) return "1600"; // 2400 → 1600 (减少56%内存)
  73. if (isLowEndDevice) return "1000"; // 1200 → 1000 (减少44%内存)
  74. return "1200"; // 1800 → 1200 (减少56%内存)
  75. }
  76. /// safeArea高度 Z
  77. double get safeAreaHeight => MediaQuery.of(context).viewPadding.top + MediaQuery.of(context).viewPadding.bottom;
  78. /// 获取appbar的高度
  79. double get appBarHeight => AppBar().preferredSize.height;
  80. /// Play 尺寸
  81. Size get boardSize =>
  82. Size(screenSize.width - profile.horizontalPadding * 2, screenSize.height - (safeAreaHeight + appBarHeight + profile.verticalPadding * 2));
  83. /// 根据谷歌的描述,banner尺寸最小50px, 最大不超过15%
  84. double get estimateBannerHeight => max(50, screenSize.height * 0.15).truncateToDouble();
  85. // double get bannerHeight => _bannerHeight == 0 ? estimateBannerHeight : _bannerHeight;
  86. double get bannerHeight => _bannerHeight != 0
  87. ? _bannerHeight
  88. : isTablet
  89. ? 90
  90. : 50;
  91. /// 是否平板
  92. bool get isTablet => screenSize.shortestSide >= 600;
  93. String filePath(String relativePath) => '${baseDir.path}/$relativePath';
  94. // board核心绘制区域
  95. Rect get targetRect {
  96. final double availableHeight = screenSize.height - appBarHeight - bannerHeight - 10; // -10 是预留给banner广告的间隔距离
  97. final double paddedWidth = screenSize.width - 2 * profile.horizontalPadding;
  98. final double paddedHeight = availableHeight - 2 * profile.verticalPadding;
  99. final double targetWidth = paddedWidth;
  100. final double targetHeight = targetWidth * aspectRatio;
  101. final double finalPuzzleWidth;
  102. final double finalPuzzleHeight;
  103. if (targetHeight > paddedHeight) {
  104. finalPuzzleHeight = paddedHeight;
  105. finalPuzzleWidth = paddedHeight / aspectRatio;
  106. } else {
  107. finalPuzzleWidth = targetWidth;
  108. finalPuzzleHeight = targetHeight;
  109. }
  110. final double targetYStart = appBarHeight + profile.verticalPadding + (paddedHeight - finalPuzzleHeight) / 2;
  111. final double targetXStart = profile.horizontalPadding + (paddedWidth - finalPuzzleWidth) / 2;
  112. final newTargetRect = Rect.fromLTWH(targetXStart, targetYStart, finalPuzzleWidth, finalPuzzleHeight);
  113. return newTargetRect;
  114. }
  115. // 最佳图片分辨率
  116. Size get bestImageSize => Size(targetRect.width * effectivePixelRatio, targetRect.height * effectivePixelRatio);
  117. }
  118. class DeviceProfile {
  119. final double horizontalPadding; // 水平padding
  120. final double verticalPadding; // 垂直padding
  121. DeviceProfile({
  122. this.horizontalPadding = 10,
  123. this.verticalPadding = 10, //Play board padding
  124. });
  125. }