Mounir Bouaiche

Clean codes & add features & fix bugs

Features/Bugs:
- Feature: You can select what classes needs to be rebuilt instead of rebuilding everything, if you have widget A, either addi SU mixin or add 'A' to list ScreenUtilInit.responsiveWidgets
- Feature: Using ScreenUtilInit.builder is optional (use it only when using library in theme)
- Bug: Second call to ScreenUtil.init ignores any existing values and uses the default values when not provided, use ScreenUtil.configure instead
- Bug: ScreenUtil.ensureScreenSize raises an overflow error
... ... @@ -139,3 +139,6 @@ doc
.lock
coverage*
*.lock
# Don't commit .fvm directory containing machine-specific symlink to sdk & flutter version
**/.fvm
... ...
/////////////////////////////////////////////////////////////////////////
/// Generated via plugin: flutter_screenutil_generator - Do Not Touch ///
/////////////////////////////////////////////////////////////////////////
part of 'responsive_widgets.su.dart';
const _responsiveWidgets = {
'MyThemedApp',
'MyApp',
'HomePageScaffold',
};
... ...
part 'responsive_widgets.g.dart';
get responsiveWidgets => _responsiveWidgets;
... ...
import 'package:example/responsive_widgets.su.dart';
import 'package:example/src/home.dart';
import 'package:flutter/material.dart';
// import 'package:example/allowed_classes.su.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
class MyApp extends StatelessWidget {
... ... @@ -9,22 +11,19 @@ class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
// In first method you only need to wrap [MaterialApp] with [ScreenUtilInit] and that's it
return ScreenUtilInit(
useInheritedMediaQuery: false,
builder: (_, child) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'First Method',
// You can use the library anywhere in the app even in theme
theme: ThemeData(
primarySwatch: Colors.blue,
textTheme: Typography(platform: TargetPlatform.iOS)
.black
.apply(fontSizeFactor: 1),
),
home: child,
);
},
child: const HomePage(title: 'First Method'),
responsiveWidgets: responsiveWidgets,
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'First Method',
// You can use the library anywhere in the app even in theme
theme: ThemeData(
primarySwatch: Colors.blue,
textTheme: Typography(platform: TargetPlatform.iOS)
.black
.apply(fontSizeFactor: 1),
),
home: const HomePage(title: 'First Method'),
),
);
}
}
... ...
... ... @@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
class HomePageScaffold extends StatelessWidget {
class HomePageScaffold extends StatelessWidget with SU {
const HomePageScaffold({Key? key, this.title = ''}) : super(key: key);
void printScreenInformation(BuildContext context) {
... ...
... ... @@ -25,6 +25,9 @@ dev_dependencies:
flutter_test:
sdk: flutter
test: ^1.15.7
build_runner:
flutter_screenutil_generator:
path: ../../flutter_screenutil_generator
flutter:
uses-material-design: true
\ No newline at end of file
uses-material-design: true
... ...
... ... @@ -10,3 +10,4 @@ export 'src/r_sizedbox.dart';
export 'src/screen_util.dart';
export 'src/screenutil_init.dart';
export 'src/size_extension.dart';
export 'src/screenutil_mixin.dart';
... ...
... ... @@ -4,8 +4,7 @@
*/
import 'dart:math' show min, max;
import 'dart:ui' show FlutterView;
import 'dart:async' show Completer;
import 'dart:ui' as ui show FlutterView;
import 'package:flutter/widgets.dart';
... ... @@ -20,10 +19,8 @@ class ScreenUtil {
///屏幕方向
late Orientation _orientation;
late double _screenWidth;
late double _screenHeight;
late bool _minTextAdapt;
BuildContext? _context;
late MediaQueryData _data;
late bool _splitScreenMode;
ScreenUtil._();
... ... @@ -32,6 +29,22 @@ class ScreenUtil {
return _instance;
}
factory ScreenUtil.init(
BuildContext context, {
Size designSize = defaultSize,
bool splitScreenMode = false,
bool minTextAdapt = false,
}) {
configure(
data: MediaQuery.maybeOf(context),
designSize: designSize,
minTextAdapt: minTextAdapt,
splitScreenMode: splitScreenMode,
);
return _instance;
}
/// Manually wait for window size to be initialized
///
/// `Recommended` to use before you need access window size
... ... @@ -55,19 +68,25 @@ class ScreenUtil {
/// )
/// ```
static Future<void> ensureScreenSize([
FlutterView? window,
ui.FlutterView? window,
Duration duration = const Duration(milliseconds: 10),
]) async {
final binding = WidgetsFlutterBinding.ensureInitialized();
window ??= WidgetsBinding.instance.platformDispatcher.implicitView;
binding.deferFirstFrame();
if (window?.physicalGeometry.isEmpty == true) {
return Future.delayed(duration, () async {
binding.deferFirstFrame();
await ensureScreenSize(window, duration);
return binding.allowFirstFrame();
});
}
await Future.doWhile(() {
if (window == null) {
window = binding.platformDispatcher.implicitView;
}
if (window == null || window!.physicalGeometry.isEmpty == true) {
return Future.delayed(duration, () => true);
}
return false;
});
binding.allowFirstFrame();
}
Set<Element>? _elementsToRebuild;
... ... @@ -89,43 +108,30 @@ class ScreenUtil {
}
/// Initializing the library.
static Future<void> init(BuildContext context,
{Size designSize = defaultSize,
bool splitScreenMode = false,
bool minTextAdapt = false,
bool scaleByHeight = false}) async {
final mediaQueryContext =
context.getElementForInheritedWidgetOfExactType<MediaQuery>();
final initCompleter = Completer<void>();
WidgetsFlutterBinding.ensureInitialized().addPostFrameCallback((_) {
mediaQueryContext?.visitChildElements((el) => _instance._context = el);
if (_instance._context != null) initCompleter.complete();
});
static void configure({
MediaQueryData? data,
Size? designSize,
bool? splitScreenMode,
bool? minTextAdapt,
}) {
if (data != null) _instance._data = data;
final deviceData = _instance._data.nonEmptySizeOrNull();
final deviceSize = deviceData?.size ?? designSize ?? _instance._uiSize;
final deviceData = MediaQuery.maybeOf(context).nonEmptySizeOrNull();
if (designSize != null) _instance._uiSize = designSize;
final deviceSize = deviceData?.size ?? designSize;
final orientation = deviceData?.orientation ??
(deviceSize.width > deviceSize.height
? Orientation.landscape
: Orientation.portrait);
_instance
.._context = scaleByHeight ? null : context
.._uiSize = designSize
.._splitScreenMode = splitScreenMode
.._minTextAdapt = minTextAdapt
.._orientation = orientation
.._screenWidth = scaleByHeight
? (deviceSize.height * designSize.width) / designSize.height
: deviceSize.width
.._screenHeight = deviceSize.height;
.._minTextAdapt = minTextAdapt ?? _instance._minTextAdapt
.._splitScreenMode = splitScreenMode ?? _instance._splitScreenMode
.._orientation = orientation;
_instance._elementsToRebuild?.forEach((el) => el.markNeedsBuild());
return initCompleter.future;
}
///获取屏幕方向
... ... @@ -134,33 +140,27 @@ class ScreenUtil {
/// 每个逻辑像素的字体像素数,字体的缩放比例
/// The number of font pixels for each logical pixel.
double get textScaleFactor =>
_context != null ? MediaQuery.of(_context!).textScaleFactor : 1;
double get textScaleFactor => _data.textScaleFactor;
/// 设备的像素密度
/// 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;
double? get pixelRatio => _data.devicePixelRatio;
/// 当前设备宽度 dp
/// The horizontal extent of this size.
double get screenWidth =>
_context != null ? MediaQuery.of(_context!).size.width : _screenWidth;
double get screenWidth => _data.size.width;
///当前设备高度 dp
///The vertical extent of this size. dp
double get screenHeight =>
_context != null ? MediaQuery.of(_context!).size.height : _screenHeight;
double get screenHeight => _data.size.height;
/// 状态栏高度 dp 刘海屏会更高
/// The offset from the top, in dp
double get statusBarHeight =>
_context == null ? 0 : MediaQuery.of(_context!).padding.top;
double get statusBarHeight => _data.padding.top;
/// 底部安全区距离 dp
/// The offset from the bottom, in dp
double get bottomBarHeight =>
_context == null ? 0 : MediaQuery.of(_context!).padding.bottom;
double get bottomBarHeight => _data.padding.bottom;
/// 实际尺寸与UI设计的比例
/// The ratio of actual width to UI design
... ...
import 'package:flutter/widgets.dart';
import 'screenutil_mixin.dart';
import 'screen_util.dart';
typedef RebuildFactor = bool Function(MediaQueryData old, MediaQueryData data);
... ... @@ -9,9 +10,7 @@ typedef ScreenUtilInitBuilder = Widget Function(
Widget? child,
);
class RebuildFactors {
const RebuildFactors._();
abstract class RebuildFactors {
static bool size(MediaQueryData old, MediaQueryData data) {
return old.size != data.size;
}
... ... @@ -24,35 +23,43 @@ class RebuildFactors {
return old.viewInsets != data.viewInsets;
}
static bool all(MediaQueryData old, MediaQueryData data) {
static bool change(MediaQueryData old, MediaQueryData data) {
return old != data;
}
static bool always(MediaQueryData _, MediaQueryData __) {
return true;
}
static bool none(MediaQueryData _, MediaQueryData __) {
return false;
}
}
class ScreenUtilInit extends StatefulWidget {
/// A helper widget that initializes [ScreenUtil]
const ScreenUtilInit(
{Key? key,
required this.builder,
this.child,
this.rebuildFactor = RebuildFactors.size,
this.designSize = ScreenUtil.defaultSize,
this.splitScreenMode = false,
this.minTextAdapt = false,
this.useInheritedMediaQuery = false,
this.scaleByHeight = false})
: super(key: key);
final ScreenUtilInitBuilder builder;
const ScreenUtilInit({
Key? key,
this.builder,
this.child,
this.rebuildFactor = RebuildFactors.size,
this.designSize = ScreenUtil.defaultSize,
this.splitScreenMode = false,
this.minTextAdapt = false,
this.useInheritedMediaQuery = false,
this.responsiveWidgets,
}) : super(key: key);
final ScreenUtilInitBuilder? builder;
final Widget? child;
final bool splitScreenMode;
final bool minTextAdapt;
final bool useInheritedMediaQuery;
final bool scaleByHeight;
final RebuildFactor rebuildFactor;
/// The [Size] of the device in the design draft, in dp
final Size designSize;
final Iterable<String>? responsiveWidgets;
@override
State<ScreenUtilInit> createState() => _ScreenUtilInitState();
... ... @@ -60,127 +67,85 @@ class ScreenUtilInit extends StatefulWidget {
class _ScreenUtilInitState extends State<ScreenUtilInit>
with WidgetsBindingObserver {
late final Iterable<String>? _canMarkedToBuild;
MediaQueryData? _mediaQueryData;
final WidgetsBinding _binding = WidgetsBinding.instance;
@override
void initState() {
_canMarkedToBuild = widget.responsiveWidgets;
super.initState();
_binding.addObserver(this);
}
@override
void didChangeMetrics() {
super.didChangeMetrics();
_revalidate();
}
bool wrappedInMediaQuery = false;
@override
void didChangeDependencies() {
super.didChangeDependencies();
_revalidate();
}
WidgetsBinding get binding => WidgetsFlutterBinding.ensureInitialized();
MediaQueryData? _newData() {
MediaQueryData? mq = MediaQuery.maybeOf(context);
if (mq == null) mq = MediaQueryData.fromView(View.of(context));
MediaQueryData get mediaQueryData => _mediaQueryData!;
return mq;
}
MediaQueryData get newData {
final data = MediaQuery.maybeOf(context);
void _markNeedsBuildIfAllowed(Element el) {
if (_canMarkedToBuild != null) {
final widget = el.widget, widgetName = el.widget.runtimeType.toString();
if (data != null) {
if (widget.useInheritedMediaQuery) {
wrappedInMediaQuery = true;
}
return data;
if (widget is! SU && !_canMarkedToBuild!.contains(widgetName)) return;
}
return MediaQueryData.fromView(View.of(context));
el.markNeedsBuild();
}
_updateTree(Element el) {
el.markNeedsBuild();
void _updateTree(Element el) {
_markNeedsBuildIfAllowed(el);
el.visitChildren(_updateTree);
}
@override
void initState() {
super.initState();
binding.addObserver(this);
}
void _revalidate([void Function()? callback]) {
final oldData = _mediaQueryData;
final newData = _newData();
@override
void didChangeMetrics() {
final old = _mediaQueryData!;
final data = newData;
if (newData == null) return;
if (widget.scaleByHeight || widget.rebuildFactor(old, data)) {
_mediaQueryData = data;
_updateTree(context as Element);
if (oldData == null || widget.rebuildFactor(oldData, newData)) {
setState(() {
_mediaQueryData = newData;
_updateTree(context as Element);
callback?.call();
});
}
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
if (_mediaQueryData == null) _mediaQueryData = newData;
didChangeMetrics();
}
@override
void dispose() {
binding.removeObserver(this);
super.dispose();
}
Widget build(BuildContext context) {
final mq = _mediaQueryData;
@override
Widget build(BuildContext _context) {
if (mediaQueryData.size == Size.zero) return const SizedBox.shrink();
if (!wrappedInMediaQuery) {
return MediaQuery(
data: mediaQueryData,
child: Builder(
builder: (__context) {
ScreenUtil.init(
__context,
designSize: widget.designSize,
splitScreenMode: widget.splitScreenMode,
minTextAdapt: widget.minTextAdapt,
scaleByHeight: widget.scaleByHeight,
);
final deviceData = MediaQuery.maybeOf(__context);
final deviceSize = deviceData?.size ?? widget.designSize;
return MediaQuery(
data: MediaQueryData.fromView(View.of(__context)),
child: Container(
width: deviceSize.width,
height: deviceSize.height,
child: FittedBox(
fit: BoxFit.none,
alignment: Alignment.center,
child: Container(
width: widget.scaleByHeight
? (deviceSize.height * widget.designSize.width) /
widget.designSize.height
: deviceSize.width,
height: deviceSize.height,
child: widget.builder(__context, widget.child),
),
),
),
);
},
),
);
}
if (mq == null) return SizedBox.shrink();
ScreenUtil.init(
_context,
ScreenUtil.configure(
data: mq,
designSize: widget.designSize,
splitScreenMode: widget.splitScreenMode,
minTextAdapt: widget.minTextAdapt,
scaleByHeight: widget.scaleByHeight,
);
final deviceData = MediaQuery.maybeOf(_context);
final deviceSize = deviceData?.size ?? widget.designSize;
return Container(
width: deviceSize.width,
height: deviceSize.height,
child: FittedBox(
fit: BoxFit.none,
alignment: Alignment.center,
child: Container(
width: widget.scaleByHeight
? (deviceSize.height * widget.designSize.width) /
widget.designSize.height
: deviceSize.width,
height: deviceSize.height,
child: widget.builder(_context, widget.child),
),
),
);
return widget.builder?.call(context, widget.child) ?? widget.child!;
}
@override
void dispose() {
_binding.removeObserver(this);
super.dispose();
}
}
... ...
import 'package:flutter/widgets.dart';
mixin SU on Widget {}
... ...
... ... @@ -14,3 +14,4 @@ dependencies:
dev_dependencies:
flutter_test:
sdk: flutter
build_runner:
... ...