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 {
// You can use the library anywhere in the app even in theme
theme: ThemeData(
primarySwatch: Colors.blue,
textTheme: TextTheme(bodyText2: TextStyle(fontSize: 16.sp)),
textTheme: Typography.englishLike2018.apply(fontSizeFactor: 1.sp),
),
home: child,
);
},
child: HomePage(title: 'First Method'),
child: const HomePage(title: 'First Method'),
);
}
}
... ...
... ... @@ -23,6 +23,10 @@ class HomePageScaffold extends StatelessWidget {
Widget build(BuildContext context) {
printScreenInformation();
/// Uncomment if you wanna force current widget to be rebuilt with updated values
/// Must use it if you use the second method, or if you use ScreenUtilInit's child.
/// Note: don't use it along with ScreenUtil.init()
// ScreenUtil.registerToBuild(context);
return Scaffold(
appBar: AppBar(
title: Text(title),
... ... @@ -78,20 +82,33 @@ class HomePageScaffold extends StatelessWidget {
),
),
),
Padding(
padding: const EdgeInsets.all(18).r,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
TextField(
decoration: InputDecoration(
border: OutlineInputBorder(),
),
),
18.verticalSpace,
Text('Device width:${ScreenUtil().screenWidth}dp'),
Text('Device height:${ScreenUtil().screenHeight}dp'),
Text('Device pixel density:${ScreenUtil().pixelRatio}'),
Text('Bottom safe zone distance:${ScreenUtil().bottomBarHeight}dp'),
Text(
'Bottom safe zone distance:${ScreenUtil().bottomBarHeight}dp'),
Text('Status bar height:${ScreenUtil().statusBarHeight}dp'),
Text(
'The ratio of actual width to UI design:${ScreenUtil().scaleWidth}'),
Text(
'The ratio of actual height to UI design:${ScreenUtil().scaleHeight}'),
10.verticalSpace,
Text('System font scaling factor:${ScreenUtil().textScaleFactor}'),
Text(
'System font scaling factor:${ScreenUtil().textScaleFactor}'),
5.verticalSpace,
Column(
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text(
'16sp, will not change with the system.',
... ... @@ -109,7 +126,10 @@ class HomePageScaffold extends StatelessWidget {
),
),
],
)
),
],
),
),
],
),
),
... ...
... ... @@ -10,11 +10,11 @@ import 'package:flutter/widgets.dart';
class ScreenUtil {
static const Size defaultSize = Size(360, 690);
static late ScreenUtil _instance;
static ScreenUtil _instance = ScreenUtil._();
/// UI设计中手机尺寸 , dp
/// Size of the phone in UI Design , dp
late Size uiSize;
late Size _uiSize;
///屏幕方向
late Orientation _orientation;
... ... @@ -22,7 +22,7 @@ class ScreenUtil {
late double _screenWidth;
late double _screenHeight;
late bool _minTextAdapt;
BuildContext? context;
BuildContext? _context;
late bool _splitScreenMode;
ScreenUtil._();
... ... @@ -69,16 +69,21 @@ class ScreenUtil {
}
}
Set<Element>? _elementsToRebuild;
/// ### Experimental
/// Register current page and all its descendants to rebuild
/// Register current page and all its descendants to rebuild.
/// Helpful when building for web and desktop
static void registerToBuild(
BuildContext context, [
bool withDescendants = false,
]) {
MediaQuery.maybeOf(context);
final instance = ScreenUtil();
(instance._elementsToRebuild ??= {}).add(context as Element);
// MediaQuery.maybeOf(context);
if (withDescendants) {
(context as Element).visitChildren((element) {
context.visitChildren((element) {
registerToBuild(element, true);
});
}
... ... @@ -108,14 +113,16 @@ class ScreenUtil {
? Orientation.landscape
: Orientation.portrait);
_instance = ScreenUtil._()
..uiSize = designSize
_instance
.._uiSize = designSize
.._splitScreenMode = splitScreenMode
.._minTextAdapt = minTextAdapt
.._orientation = orientation
.._screenWidth = deviceSize.width
.._screenHeight = deviceSize.height
..context = mediaQueryContext != null ? context : null;
.._context = context;
_instance._elementsToRebuild?.forEach((el) => el.markNeedsBuild());
}
///获取屏幕方向
... ... @@ -125,41 +132,41 @@ class ScreenUtil {
/// 每个逻辑像素的字体像素数,字体的缩放比例
/// The number of font pixels for each logical pixel.
double get textScaleFactor =>
context != null ? MediaQuery.of(context!).textScaleFactor : 1;
_context != null ? MediaQuery.of(_context!).textScaleFactor : 1;
/// 设备的像素密度
/// The size of the media in logical pixels (e.g, the size of the screen).
double? get pixelRatio =>
context != null ? MediaQuery.of(context!).devicePixelRatio : 1;
_context != null ? MediaQuery.of(_context!).devicePixelRatio : 1;
/// 当前设备宽度 dp
/// The horizontal extent of this size.
double get screenWidth =>
context != null ? MediaQuery.of(context!).size.width : _screenWidth;
_context != null ? MediaQuery.of(_context!).size.width : _screenWidth;
///当前设备高度 dp
///The vertical extent of this size. dp
double get screenHeight =>
context != null ? MediaQuery.of(context!).size.height : _screenHeight;
_context != null ? MediaQuery.of(_context!).size.height : _screenHeight;
/// 状态栏高度 dp 刘海屏会更高
/// The offset from the top, in dp
double get statusBarHeight =>
context == null ? 0 : MediaQuery.of(context!).padding.top;
_context == null ? 0 : MediaQuery.of(_context!).padding.top;
/// 底部安全区距离 dp
/// The offset from the bottom, in dp
double get bottomBarHeight =>
context == null ? 0 : MediaQuery.of(context!).padding.bottom;
_context == null ? 0 : MediaQuery.of(_context!).padding.bottom;
/// 实际尺寸与UI设计的比例
/// The ratio of actual width to UI design
double get scaleWidth => screenWidth / uiSize.width;
double get scaleWidth => screenWidth / _uiSize.width;
/// /// The ratio of actual height to UI design
double get scaleHeight =>
(_splitScreenMode ? max(screenHeight, 700) : screenHeight) /
uiSize.height;
_uiSize.height;
double get scaleText =>
_minTextAdapt ? min(scaleWidth, scaleHeight) : scaleWidth;
... ...
... ... @@ -2,15 +2,21 @@ import 'package:flutter/widgets.dart';
import 'screen_util.dart';
class ScreenUtilInit extends StatelessWidget {
typedef RebuildFactor = bool Function(MediaQueryData old, MediaQueryData data);
bool defaultRebuildFactor(old, data) => old.size != data.size;
class ScreenUtilInit extends StatefulWidget {
/// A helper widget that initializes [ScreenUtil]
const ScreenUtilInit({
Key? key,
this.builder,
this.child,
this.rebuildFactor = defaultRebuildFactor,
this.designSize = ScreenUtil.defaultSize,
this.splitScreenMode = false,
this.minTextAdapt = false,
Key? key,
this.useInheritedMediaQuery = false,
}) : assert(
builder != null || child != null,
'You must either pass builder or child or both',
... ... @@ -21,43 +27,106 @@ class ScreenUtilInit extends StatelessWidget {
final Widget? child;
final bool splitScreenMode;
final bool minTextAdapt;
final bool useInheritedMediaQuery;
final RebuildFactor rebuildFactor;
/// The [Size] of the device in the design draft, in dp
final Size designSize;
@override
Widget build(BuildContext context) {
bool firstFrameAllowed = false;
final binding = WidgetsFlutterBinding.ensureInitialized();
binding.deferFirstFrame();
State<ScreenUtilInit> createState() => _ScreenUtilInitState();
}
class _ScreenUtilInitState extends State<ScreenUtilInit>
with WidgetsBindingObserver {
late MediaQueryData mediaQueryData;
bool wrappedInMediaQuery = false;
WidgetsBinding get binding => WidgetsFlutterBinding.ensureInitialized();
MediaQueryData get newData {
if (widget.useInheritedMediaQuery) {
final el = context.getElementForInheritedWidgetOfExactType<MediaQuery>();
final mq = el?.widget as MediaQuery?;
final data = mq?.data;
if (data != null) {
wrappedInMediaQuery = true;
return data;
}
}
return MediaQueryData.fromWindow(binding.window);
}
Widget get child {
return SizedBox(
key: GlobalObjectKey(
hashValues(
mediaQueryData.size.width,
mediaQueryData.size.height,
),
),
child: widget.builder?.call(widget.child) ?? widget.child!,
);
}
@override
void initState() {
super.initState();
mediaQueryData = newData;
binding.addObserver(this);
}
final rootMediaQueryData = (context
.getElementForInheritedWidgetOfExactType<MediaQuery>()
?.widget as MediaQuery?)
?.data;
@override
void didChangeMetrics() {
final old = mediaQueryData;
final data = newData;
if (widget.rebuildFactor(old, data)) setState(() => mediaQueryData = data);
}
return LayoutBuilder(
builder: (_, constraints) {
if (constraints.biggest == Size.zero) return const SizedBox.shrink();
@override
void didChangeDependencies() {
super.didChangeDependencies();
didChangeMetrics();
}
if (!firstFrameAllowed) {
binding.allowFirstFrame();
firstFrameAllowed = true;
@override
void dispose() {
binding.removeObserver(this);
super.dispose();
}
@override
Widget build(BuildContext _context) {
if (mediaQueryData.size == Size.zero) return const SizedBox.shrink();
if (!wrappedInMediaQuery) {
return MediaQuery(
data: rootMediaQueryData ?? MediaQueryData.fromWindow(binding.window),
child: Builder(builder: (_context) {
// key: GlobalObjectKey('mediaQuery'),
data: mediaQueryData,
child: Builder(
builder: (__context) {
ScreenUtil.init(
_context,
designSize: designSize,
splitScreenMode: splitScreenMode,
minTextAdapt: minTextAdapt,
);
return builder?.call(child) ?? child!;
}),
__context,
designSize: widget.designSize,
splitScreenMode: widget.splitScreenMode,
minTextAdapt: widget.minTextAdapt,
);
return child;
},
),
);
}
ScreenUtil.init(
_context,
designSize: widget.designSize,
splitScreenMode: widget.splitScreenMode,
minTextAdapt: widget.minTextAdapt,
);
return child;
}
}
... ...