Mounir Bouaiche
Committed by GitHub

Merge pull request #387 from OpenFlutter/testing

Fix resizing and add rebuildFactor property.
@@ -16,12 +16,12 @@ class MyApp extends StatelessWidget { @@ -16,12 +16,12 @@ class MyApp extends StatelessWidget {
16 // You can use the library anywhere in the app even in theme 16 // You can use the library anywhere in the app even in theme
17 theme: ThemeData( 17 theme: ThemeData(
18 primarySwatch: Colors.blue, 18 primarySwatch: Colors.blue,
19 - textTheme: TextTheme(bodyText2: TextStyle(fontSize: 16.sp)), 19 + textTheme: Typography.englishLike2018.apply(fontSizeFactor: 1.sp),
20 ), 20 ),
21 home: child, 21 home: child,
22 ); 22 );
23 }, 23 },
24 - child: HomePage(title: 'First Method'), 24 + child: const HomePage(title: 'First Method'),
25 ); 25 );
26 } 26 }
27 } 27 }
@@ -23,6 +23,10 @@ class HomePageScaffold extends StatelessWidget { @@ -23,6 +23,10 @@ class HomePageScaffold extends StatelessWidget {
23 Widget build(BuildContext context) { 23 Widget build(BuildContext context) {
24 printScreenInformation(); 24 printScreenInformation();
25 25
  26 + /// Uncomment if you wanna force current widget to be rebuilt with updated values
  27 + /// Must use it if you use the second method, or if you use ScreenUtilInit's child.
  28 + /// Note: don't use it along with ScreenUtil.init()
  29 + // ScreenUtil.registerToBuild(context);
26 return Scaffold( 30 return Scaffold(
27 appBar: AppBar( 31 appBar: AppBar(
28 title: Text(title), 32 title: Text(title),
@@ -78,38 +82,54 @@ class HomePageScaffold extends StatelessWidget { @@ -78,38 +82,54 @@ class HomePageScaffold extends StatelessWidget {
78 ), 82 ),
79 ), 83 ),
80 ), 84 ),
81 - Text('Device width:${ScreenUtil().screenWidth}dp'),  
82 - Text('Device height:${ScreenUtil().screenHeight}dp'),  
83 - Text('Device pixel density:${ScreenUtil().pixelRatio}'),  
84 - Text('Bottom safe zone distance:${ScreenUtil().bottomBarHeight}dp'),  
85 - Text('Status bar height:${ScreenUtil().statusBarHeight}dp'),  
86 - Text(  
87 - 'The ratio of actual width to UI design:${ScreenUtil().scaleWidth}'),  
88 - Text(  
89 - 'The ratio of actual height to UI design:${ScreenUtil().scaleHeight}'),  
90 - 10.verticalSpace,  
91 - Text('System font scaling factor:${ScreenUtil().textScaleFactor}'),  
92 - 5.verticalSpace,  
93 - Column(  
94 - crossAxisAlignment: CrossAxisAlignment.start,  
95 - children: <Widget>[  
96 - Text(  
97 - '16sp, will not change with the system.',  
98 - style: TextStyle(  
99 - color: Colors.black,  
100 - fontSize: 16.sp, 85 + Padding(
  86 + padding: const EdgeInsets.all(18).r,
  87 + child: Column(
  88 + crossAxisAlignment: CrossAxisAlignment.stretch,
  89 + children: [
  90 + TextField(
  91 + decoration: InputDecoration(
  92 + border: OutlineInputBorder(),
  93 + ),
101 ), 94 ),
102 - textScaleFactor: 1.0,  
103 - ),  
104 - Text(  
105 - '16sp,if data is not set in MediaQuery,my font size will change with the system.',  
106 - style: TextStyle(  
107 - color: Colors.black,  
108 - fontSize: 16.sp, 95 + 18.verticalSpace,
  96 + Text('Device width:${ScreenUtil().screenWidth}dp'),
  97 + Text('Device height:${ScreenUtil().screenHeight}dp'),
  98 + Text('Device pixel density:${ScreenUtil().pixelRatio}'),
  99 + Text(
  100 + 'Bottom safe zone distance:${ScreenUtil().bottomBarHeight}dp'),
  101 + Text('Status bar height:${ScreenUtil().statusBarHeight}dp'),
  102 + Text(
  103 + 'The ratio of actual width to UI design:${ScreenUtil().scaleWidth}'),
  104 + Text(
  105 + 'The ratio of actual height to UI design:${ScreenUtil().scaleHeight}'),
  106 + 10.verticalSpace,
  107 + Text(
  108 + 'System font scaling factor:${ScreenUtil().textScaleFactor}'),
  109 + 5.verticalSpace,
  110 + Column(
  111 + crossAxisAlignment: CrossAxisAlignment.stretch,
  112 + children: <Widget>[
  113 + Text(
  114 + '16sp, will not change with the system.',
  115 + style: TextStyle(
  116 + color: Colors.black,
  117 + fontSize: 16.sp,
  118 + ),
  119 + textScaleFactor: 1.0,
  120 + ),
  121 + Text(
  122 + '16sp,if data is not set in MediaQuery,my font size will change with the system.',
  123 + style: TextStyle(
  124 + color: Colors.black,
  125 + fontSize: 16.sp,
  126 + ),
  127 + ),
  128 + ],
109 ), 129 ),
110 - ),  
111 - ],  
112 - ) 130 + ],
  131 + ),
  132 + ),
113 ], 133 ],
114 ), 134 ),
115 ), 135 ),
@@ -10,11 +10,11 @@ import 'package:flutter/widgets.dart'; @@ -10,11 +10,11 @@ import 'package:flutter/widgets.dart';
10 10
11 class ScreenUtil { 11 class ScreenUtil {
12 static const Size defaultSize = Size(360, 690); 12 static const Size defaultSize = Size(360, 690);
13 - static late ScreenUtil _instance; 13 + static ScreenUtil _instance = ScreenUtil._();
14 14
15 /// UI设计中手机尺寸 , dp 15 /// UI设计中手机尺寸 , dp
16 /// Size of the phone in UI Design , dp 16 /// Size of the phone in UI Design , dp
17 - late Size uiSize; 17 + late Size _uiSize;
18 18
19 ///屏幕方向 19 ///屏幕方向
20 late Orientation _orientation; 20 late Orientation _orientation;
@@ -22,7 +22,7 @@ class ScreenUtil { @@ -22,7 +22,7 @@ class ScreenUtil {
22 late double _screenWidth; 22 late double _screenWidth;
23 late double _screenHeight; 23 late double _screenHeight;
24 late bool _minTextAdapt; 24 late bool _minTextAdapt;
25 - BuildContext? context; 25 + BuildContext? _context;
26 late bool _splitScreenMode; 26 late bool _splitScreenMode;
27 27
28 ScreenUtil._(); 28 ScreenUtil._();
@@ -69,16 +69,21 @@ class ScreenUtil { @@ -69,16 +69,21 @@ class ScreenUtil {
69 } 69 }
70 } 70 }
71 71
  72 + Set<Element>? _elementsToRebuild;
  73 +
72 /// ### Experimental 74 /// ### Experimental
73 - /// Register current page and all its descendants to rebuild 75 + /// Register current page and all its descendants to rebuild.
74 /// Helpful when building for web and desktop 76 /// Helpful when building for web and desktop
75 static void registerToBuild( 77 static void registerToBuild(
76 BuildContext context, [ 78 BuildContext context, [
77 bool withDescendants = false, 79 bool withDescendants = false,
78 ]) { 80 ]) {
79 - MediaQuery.maybeOf(context); 81 + final instance = ScreenUtil();
  82 + (instance._elementsToRebuild ??= {}).add(context as Element);
  83 +
  84 + // MediaQuery.maybeOf(context);
80 if (withDescendants) { 85 if (withDescendants) {
81 - (context as Element).visitChildren((element) { 86 + context.visitChildren((element) {
82 registerToBuild(element, true); 87 registerToBuild(element, true);
83 }); 88 });
84 } 89 }
@@ -108,14 +113,16 @@ class ScreenUtil { @@ -108,14 +113,16 @@ class ScreenUtil {
108 ? Orientation.landscape 113 ? Orientation.landscape
109 : Orientation.portrait); 114 : Orientation.portrait);
110 115
111 - _instance = ScreenUtil._()  
112 - ..uiSize = designSize 116 + _instance
  117 + .._uiSize = designSize
113 .._splitScreenMode = splitScreenMode 118 .._splitScreenMode = splitScreenMode
114 .._minTextAdapt = minTextAdapt 119 .._minTextAdapt = minTextAdapt
115 .._orientation = orientation 120 .._orientation = orientation
116 .._screenWidth = deviceSize.width 121 .._screenWidth = deviceSize.width
117 .._screenHeight = deviceSize.height 122 .._screenHeight = deviceSize.height
118 - ..context = mediaQueryContext != null ? context : null; 123 + .._context = context;
  124 +
  125 + _instance._elementsToRebuild?.forEach((el) => el.markNeedsBuild());
119 } 126 }
120 127
121 ///获取屏幕方向 128 ///获取屏幕方向
@@ -125,41 +132,41 @@ class ScreenUtil { @@ -125,41 +132,41 @@ class ScreenUtil {
125 /// 每个逻辑像素的字体像素数,字体的缩放比例 132 /// 每个逻辑像素的字体像素数,字体的缩放比例
126 /// The number of font pixels for each logical pixel. 133 /// The number of font pixels for each logical pixel.
127 double get textScaleFactor => 134 double get textScaleFactor =>
128 - context != null ? MediaQuery.of(context!).textScaleFactor : 1; 135 + _context != null ? MediaQuery.of(_context!).textScaleFactor : 1;
129 136
130 /// 设备的像素密度 137 /// 设备的像素密度
131 /// The size of the media in logical pixels (e.g, the size of the screen). 138 /// The size of the media in logical pixels (e.g, the size of the screen).
132 double? get pixelRatio => 139 double? get pixelRatio =>
133 - context != null ? MediaQuery.of(context!).devicePixelRatio : 1; 140 + _context != null ? MediaQuery.of(_context!).devicePixelRatio : 1;
134 141
135 /// 当前设备宽度 dp 142 /// 当前设备宽度 dp
136 /// The horizontal extent of this size. 143 /// The horizontal extent of this size.
137 double get screenWidth => 144 double get screenWidth =>
138 - context != null ? MediaQuery.of(context!).size.width : _screenWidth; 145 + _context != null ? MediaQuery.of(_context!).size.width : _screenWidth;
139 146
140 ///当前设备高度 dp 147 ///当前设备高度 dp
141 ///The vertical extent of this size. dp 148 ///The vertical extent of this size. dp
142 double get screenHeight => 149 double get screenHeight =>
143 - context != null ? MediaQuery.of(context!).size.height : _screenHeight; 150 + _context != null ? MediaQuery.of(_context!).size.height : _screenHeight;
144 151
145 /// 状态栏高度 dp 刘海屏会更高 152 /// 状态栏高度 dp 刘海屏会更高
146 /// The offset from the top, in dp 153 /// The offset from the top, in dp
147 double get statusBarHeight => 154 double get statusBarHeight =>
148 - context == null ? 0 : MediaQuery.of(context!).padding.top; 155 + _context == null ? 0 : MediaQuery.of(_context!).padding.top;
149 156
150 /// 底部安全区距离 dp 157 /// 底部安全区距离 dp
151 /// The offset from the bottom, in dp 158 /// The offset from the bottom, in dp
152 double get bottomBarHeight => 159 double get bottomBarHeight =>
153 - context == null ? 0 : MediaQuery.of(context!).padding.bottom; 160 + _context == null ? 0 : MediaQuery.of(_context!).padding.bottom;
154 161
155 /// 实际尺寸与UI设计的比例 162 /// 实际尺寸与UI设计的比例
156 /// The ratio of actual width to UI design 163 /// The ratio of actual width to UI design
157 - double get scaleWidth => screenWidth / uiSize.width; 164 + double get scaleWidth => screenWidth / _uiSize.width;
158 165
159 /// /// The ratio of actual height to UI design 166 /// /// The ratio of actual height to UI design
160 double get scaleHeight => 167 double get scaleHeight =>
161 (_splitScreenMode ? max(screenHeight, 700) : screenHeight) / 168 (_splitScreenMode ? max(screenHeight, 700) : screenHeight) /
162 - uiSize.height; 169 + _uiSize.height;
163 170
164 double get scaleText => 171 double get scaleText =>
165 _minTextAdapt ? min(scaleWidth, scaleHeight) : scaleWidth; 172 _minTextAdapt ? min(scaleWidth, scaleHeight) : scaleWidth;
@@ -2,15 +2,21 @@ import 'package:flutter/widgets.dart'; @@ -2,15 +2,21 @@ import 'package:flutter/widgets.dart';
2 2
3 import 'screen_util.dart'; 3 import 'screen_util.dart';
4 4
5 -class ScreenUtilInit extends StatelessWidget { 5 +typedef RebuildFactor = bool Function(MediaQueryData old, MediaQueryData data);
  6 +
  7 +bool defaultRebuildFactor(old, data) => old.size != data.size;
  8 +
  9 +class ScreenUtilInit extends StatefulWidget {
6 /// A helper widget that initializes [ScreenUtil] 10 /// A helper widget that initializes [ScreenUtil]
7 const ScreenUtilInit({ 11 const ScreenUtilInit({
  12 + Key? key,
8 this.builder, 13 this.builder,
9 this.child, 14 this.child,
  15 + this.rebuildFactor = defaultRebuildFactor,
10 this.designSize = ScreenUtil.defaultSize, 16 this.designSize = ScreenUtil.defaultSize,
11 this.splitScreenMode = false, 17 this.splitScreenMode = false,
12 this.minTextAdapt = false, 18 this.minTextAdapt = false,
13 - Key? key, 19 + this.useInheritedMediaQuery = false,
14 }) : assert( 20 }) : assert(
15 builder != null || child != null, 21 builder != null || child != null,
16 'You must either pass builder or child or both', 22 'You must either pass builder or child or both',
@@ -21,43 +27,106 @@ class ScreenUtilInit extends StatelessWidget { @@ -21,43 +27,106 @@ class ScreenUtilInit extends StatelessWidget {
21 final Widget? child; 27 final Widget? child;
22 final bool splitScreenMode; 28 final bool splitScreenMode;
23 final bool minTextAdapt; 29 final bool minTextAdapt;
  30 + final bool useInheritedMediaQuery;
  31 + final RebuildFactor rebuildFactor;
24 32
25 /// The [Size] of the device in the design draft, in dp 33 /// The [Size] of the device in the design draft, in dp
26 final Size designSize; 34 final Size designSize;
27 35
28 @override 36 @override
29 - Widget build(BuildContext context) {  
30 - bool firstFrameAllowed = false;  
31 - final binding = WidgetsFlutterBinding.ensureInitialized();  
32 - binding.deferFirstFrame();  
33 -  
34 - final rootMediaQueryData = (context  
35 - .getElementForInheritedWidgetOfExactType<MediaQuery>()  
36 - ?.widget as MediaQuery?)  
37 - ?.data;  
38 -  
39 - return LayoutBuilder(  
40 - builder: (_, constraints) {  
41 - if (constraints.biggest == Size.zero) return const SizedBox.shrink();  
42 -  
43 - if (!firstFrameAllowed) {  
44 - binding.allowFirstFrame();  
45 - firstFrameAllowed = true;  
46 - }  
47 -  
48 - return MediaQuery(  
49 - data: rootMediaQueryData ?? MediaQueryData.fromWindow(binding.window),  
50 - child: Builder(builder: (_context) { 37 + State<ScreenUtilInit> createState() => _ScreenUtilInitState();
  38 +}
  39 +
  40 +class _ScreenUtilInitState extends State<ScreenUtilInit>
  41 + with WidgetsBindingObserver {
  42 + late MediaQueryData mediaQueryData;
  43 + bool wrappedInMediaQuery = false;
  44 +
  45 + WidgetsBinding get binding => WidgetsFlutterBinding.ensureInitialized();
  46 +
  47 + MediaQueryData get newData {
  48 + if (widget.useInheritedMediaQuery) {
  49 + final el = context.getElementForInheritedWidgetOfExactType<MediaQuery>();
  50 + final mq = el?.widget as MediaQuery?;
  51 + final data = mq?.data;
  52 +
  53 + if (data != null) {
  54 + wrappedInMediaQuery = true;
  55 + return data;
  56 + }
  57 + }
  58 +
  59 + return MediaQueryData.fromWindow(binding.window);
  60 + }
  61 +
  62 + Widget get child {
  63 + return SizedBox(
  64 + key: GlobalObjectKey(
  65 + hashValues(
  66 + mediaQueryData.size.width,
  67 + mediaQueryData.size.height,
  68 + ),
  69 + ),
  70 + child: widget.builder?.call(widget.child) ?? widget.child!,
  71 + );
  72 + }
  73 +
  74 + @override
  75 + void initState() {
  76 + super.initState();
  77 + mediaQueryData = newData;
  78 + binding.addObserver(this);
  79 + }
  80 +
  81 + @override
  82 + void didChangeMetrics() {
  83 + final old = mediaQueryData;
  84 + final data = newData;
  85 +
  86 + if (widget.rebuildFactor(old, data)) setState(() => mediaQueryData = data);
  87 + }
  88 +
  89 + @override
  90 + void didChangeDependencies() {
  91 + super.didChangeDependencies();
  92 + didChangeMetrics();
  93 + }
  94 +
  95 + @override
  96 + void dispose() {
  97 + binding.removeObserver(this);
  98 + super.dispose();
  99 + }
  100 +
  101 + @override
  102 + Widget build(BuildContext _context) {
  103 + if (mediaQueryData.size == Size.zero) return const SizedBox.shrink();
  104 +
  105 + if (!wrappedInMediaQuery) {
  106 + return MediaQuery(
  107 + // key: GlobalObjectKey('mediaQuery'),
  108 + data: mediaQueryData,
  109 + child: Builder(
  110 + builder: (__context) {
51 ScreenUtil.init( 111 ScreenUtil.init(
52 - _context,  
53 - designSize: designSize,  
54 - splitScreenMode: splitScreenMode,  
55 - minTextAdapt: minTextAdapt, 112 + __context,
  113 + designSize: widget.designSize,
  114 + splitScreenMode: widget.splitScreenMode,
  115 + minTextAdapt: widget.minTextAdapt,
56 ); 116 );
57 - return builder?.call(child) ?? child!;  
58 - }),  
59 - );  
60 - }, 117 + return child;
  118 + },
  119 + ),
  120 + );
  121 + }
  122 +
  123 + ScreenUtil.init(
  124 + _context,
  125 + designSize: widget.designSize,
  126 + splitScreenMode: widget.splitScreenMode,
  127 + minTextAdapt: widget.minTextAdapt,
61 ); 128 );
  129 +
  130 + return child;
62 } 131 }
63 } 132 }