device.dart 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  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. final Directory baseDir;
  9. Device(this.context, this.baseDir);
  10. AndroidDeviceInfo? androidDeviceInfo;
  11. // 新增:判断是否32位设备
  12. bool is32BitDevice() {
  13. if (Platform.isIOS) return false; // 近年iOS全是64位
  14. if (androidDeviceInfo == null) return false;
  15. final abis = androidDeviceInfo!.supportedAbis;
  16. return !abis.any((abi) => abi.contains('64'));
  17. }
  18. /// 获取平台性能
  19. int get androidSdkInt => androidDeviceInfo != null ? androidDeviceInfo!.version.sdkInt : 100;
  20. bool get isOldAndroid => androidSdkInt < 26; // 安卓8以下
  21. bool get isLowRamDevice => androidDeviceInfo != null ? androidDeviceInfo!.isLowRamDevice : false;
  22. bool get lowCpu => Platform.numberOfProcessors <= 2;
  23. bool get isLowEndDevice {
  24. if (androidDeviceInfo == null) return false;
  25. return lowCpu || isOldAndroid || isLowRamDevice || is32BitDevice();
  26. }
  27. static double get devPixelRatio => PlatformDispatcher.instance.views.first.devicePixelRatio;
  28. static Size get physicalSize => PlatformDispatcher.instance.views.first.physicalSize;
  29. static Size get logicalSize => physicalSize / devPixelRatio;
  30. ///final Size screenSize;
  31. final BuildContext context;
  32. final aspectRatio = 3.0 / 2.0; // 图片宽高比按照 2 / 3 设定,相应的 board play 区域也是这个比例
  33. final _tabletProfile = DeviceProfile(horizontalPadding: 60, verticalPadding: 10);
  34. final _profile = DeviceProfile(horizontalPadding: 10, verticalPadding: 10);
  35. double _bannerHeight = 0;
  36. set bannerHeight(double height) {
  37. _bannerHeight = height;
  38. }
  39. /// 获取当前配置
  40. DeviceProfile get profile => isTablet ? _tabletProfile : _profile;
  41. /// 屏幕尺寸
  42. Size get screenSize => MediaQuery.of(context).size;
  43. // 真实像素密度
  44. double get realPixelRatio => devPixelRatio;
  45. /// 像素密度
  46. /// 像素密度:低端机通过降低采样倍率来保护内存
  47. /// 这里的逻辑是:高端机用真实DPR,低端机降级,但不直接降到1(除非设备极差)
  48. double get effectivePixelRatio {
  49. double realDPR = PlatformDispatcher.instance.views.first.devicePixelRatio;
  50. if (isLowEndDevice) {
  51. return (realDPR > 2.0) ? 1.5 : 1.0; // 适当降级,保留一点清晰度
  52. }
  53. return realDPR;
  54. }
  55. String get suggestedQuality {
  56. if (isLowEndDevice) return "1200";
  57. if (isTablet) return "2400"; // 平板需要更高像素
  58. return "1800";
  59. }
  60. /// safeArea高度 Z
  61. double get safeAreaHeight => MediaQuery.of(context).viewPadding.top + MediaQuery.of(context).viewPadding.bottom;
  62. /// 获取appbar的高度
  63. double get appBarHeight => AppBar().preferredSize.height;
  64. /// Play 尺寸
  65. Size get boardSize =>
  66. Size(screenSize.width - profile.horizontalPadding * 2, screenSize.height - (safeAreaHeight + appBarHeight + profile.verticalPadding * 2));
  67. /// 根据谷歌的描述,banner尺寸最小50px, 最大不超过15%
  68. double get estimateBannerHeight => max(50, screenSize.height * 0.15).truncateToDouble();
  69. // double get bannerHeight => _bannerHeight == 0 ? estimateBannerHeight : _bannerHeight;
  70. double get bannerHeight => _bannerHeight != 0
  71. ? _bannerHeight
  72. : isTablet
  73. ? 90
  74. : 50;
  75. /// 是否平板
  76. bool get isTablet => screenSize.shortestSide >= 600;
  77. String filePath(String relativePath) => '${baseDir.path}/$relativePath';
  78. // board核心绘制区域
  79. Rect get targetRect {
  80. final double availableHeight = screenSize.height - appBarHeight - bannerHeight - 10; // -10 是预留给banner广告的间隔距离
  81. final double paddedWidth = screenSize.width - 2 * profile.horizontalPadding;
  82. final double paddedHeight = availableHeight - 2 * profile.verticalPadding;
  83. final double targetWidth = paddedWidth;
  84. final double targetHeight = targetWidth * aspectRatio;
  85. final double finalPuzzleWidth;
  86. final double finalPuzzleHeight;
  87. if (targetHeight > paddedHeight) {
  88. finalPuzzleHeight = paddedHeight;
  89. finalPuzzleWidth = paddedHeight / aspectRatio;
  90. } else {
  91. finalPuzzleWidth = targetWidth;
  92. finalPuzzleHeight = targetHeight;
  93. }
  94. final double targetYStart = appBarHeight + profile.verticalPadding + (paddedHeight - finalPuzzleHeight) / 2;
  95. final double targetXStart = profile.horizontalPadding + (paddedWidth - finalPuzzleWidth) / 2;
  96. final newTargetRect = Rect.fromLTWH(targetXStart, targetYStart, finalPuzzleWidth, finalPuzzleHeight);
  97. return newTargetRect;
  98. }
  99. // 最佳图片分辨率
  100. Size get bestImageSize => Size(targetRect.width * effectivePixelRatio, targetRect.height * effectivePixelRatio);
  101. }
  102. class DeviceProfile {
  103. final double horizontalPadding; // 水平padding
  104. final double verticalPadding; // 垂直padding
  105. DeviceProfile({
  106. this.horizontalPadding = 10,
  107. this.verticalPadding = 10, //Play board padding
  108. });
  109. }