Mounir-Bouaiche

Some fixes & adding rebuildFactor

Performance tip: rebuildFactor is for those who want to set some breakpoints or to rebuild based on steps
@@ -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 }
@@ -24,7 +24,7 @@ class HomePageScaffold extends StatelessWidget { @@ -24,7 +24,7 @@ class HomePageScaffold extends StatelessWidget {
24 printScreenInformation(); 24 printScreenInformation();
25 25
26 /// Uncomment if you wanna force current widget to be rebuilt with updated values 26 /// Uncomment if you wanna force current widget to be rebuilt with updated values
27 - /// Use it only if you use the second method, or if you use ScreenUtilInit's child. 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() 28 /// Note: don't use it along with ScreenUtil.init()
29 // ScreenUtil.registerToBuild(context); 29 // ScreenUtil.registerToBuild(context);
30 return Scaffold( 30 return Scaffold(
@@ -82,38 +82,54 @@ class HomePageScaffold extends StatelessWidget { @@ -82,38 +82,54 @@ class HomePageScaffold extends StatelessWidget {
82 ), 82 ),
83 ), 83 ),
84 ), 84 ),
85 - Text('Device width:${ScreenUtil().screenWidth}dp'),  
86 - Text('Device height:${ScreenUtil().screenHeight}dp'),  
87 - Text('Device pixel density:${ScreenUtil().pixelRatio}'),  
88 - Text('Bottom safe zone distance:${ScreenUtil().bottomBarHeight}dp'),  
89 - Text('Status bar height:${ScreenUtil().statusBarHeight}dp'),  
90 - Text(  
91 - 'The ratio of actual width to UI design:${ScreenUtil().scaleWidth}'),  
92 - Text(  
93 - 'The ratio of actual height to UI design:${ScreenUtil().scaleHeight}'),  
94 - 10.verticalSpace,  
95 - Text('System font scaling factor:${ScreenUtil().textScaleFactor}'),  
96 - 5.verticalSpace,  
97 - Column(  
98 - crossAxisAlignment: CrossAxisAlignment.start,  
99 - children: <Widget>[  
100 - Text(  
101 - '16sp, will not change with the system.',  
102 - style: TextStyle(  
103 - color: Colors.black,  
104 - 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 + ),
105 ), 94 ),
106 - textScaleFactor: 1.0,  
107 - ),  
108 - Text(  
109 - '16sp,if data is not set in MediaQuery,my font size will change with the system.',  
110 - style: TextStyle(  
111 - color: Colors.black,  
112 - 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 + ],
113 ), 129 ),
114 - ),  
115 - ],  
116 - ) 130 + ],
  131 + ),
  132 + ),
117 ], 133 ],
118 ), 134 ),
119 ), 135 ),
@@ -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 }