因移动设备的多样性,特别是 Android 的碎片化严重,存在各种各样的分辨率,而 Flutter 跨平台开发又需同时支持 Android 和 iOS ,为尽可能的还原设计图效果提升用户体验,屏幕适配就势在必行了。
Flutter 暂时没有官方的屏幕适配方案,在 Flutter 项目开发中目前大部分的适配方案都是通过比例来进行适配,是一个通用的适配方法,该适配方法也在前端、Android、iOS、小程序等开发中广泛使用。
原理
UI 设计的时候一般会按照一个固定的尺寸进行设计,如 360 x 690
,实际设备分辨率可能是 Google Pixel: 1080 x 1920
、Google Pixel XL: 1440 x 2560
、iPhone 12 Pro Max: 1284 x 2778
等等。开发时如果直接按照设计图写死数值则会出现最后实现的效果跟设计效果不一致的情况。这个时候就可以用比例的方式来进行适配。
将设计图分为固定单位并给这个单位定义一个标识,例如就叫 w
,然后通过获取设备分辨率,使用设备真实宽度除以设计图宽度 ,就得到了 1w
代表的真实宽度:
1w = 设备真实宽度 / 设计图宽度
如设计图尺寸是 360 x 690
,则宽度为 360w
,真实设备宽度为 1080 则 1w = 1080 / 360 = 3
。
根据上面的算法,得到对应设备的 1w
的真实宽度:
Google Pixel: 1w = 1080 / 360 = 3 Google Pixel XL:1w = 1440 / 360 = 4 iPhone 12 Pro Max:1w = 1284 / 360 = 3.57
按照同样的算法,可以给高度定义一个单位为 h
, 得出对应设备的高度单位的真实值,如下:
Google Pixel: 1h = 1920 / 690 = 2.78 Google Pixel XL:1h = 2560 / 690 = 3.71 iPhone 12 Pro Max:1h = 2778 / 690 = 4.03
得到换算以后 w
、h
的真实值以后,开发过程中就可以使用其来设置 UI 控件的高、宽、间距等,使其最终呈现的效果无限接近设计图的效果。
开发过程中一般采用宽度来进行适配,控件高度要么自适应,要么也设置宽度的单位,然后整体高度根据内容自适应。但是如果有特殊需求也可以使用高度来进行适配,比如需求要求是 banner 占屏幕的 1/4 ,或者要求内容刚好一屏显示,这个时候设置控件的高度时就可以采用高度单位来进行适配。
基于上面的算法,在项目中就可以实现对应的适配方案了,但本着不重复造轮子的思想,项目开发中可以直接使用 flutter_screenutil[1] 这个适配库。
flutter_screenutil
flutter_screenutil
就是基于上述比例适配原理而实现的屏幕适配库。目前最新版本是 5.0.1
,在 GitHub 上拥有 2.8k
的 star 。在 pub.dev[2] 上拥有1536
个 like ,130
的 pub 指数, 99%
的人气,说明这是一个靠谱的轮子。
flutter_screenutil:让你的UI在不同尺寸的屏幕上都能显示合理的布局!
添加依赖
在项目根目录的 pubspec.yaml
中添加 flutter_screenutil
的依赖:
dependencies:
flutter:
sdk: flutter
# 添加依赖
flutter_screenutil: ^5.0.1
初始化
flutter_screenutil
提供了两种方式进行初始化:ScreenUtilInit
方式和 ScreenUtil.init
方式。首先在使用的地方导入包:
import 'package:flutter_screenutil/flutter_screenutil.dart';
ScreenUtilInit
使用 ScreenUtilInit 方式进行初始化,需要将项目的 MaterialApp 进行一层包裹,然后在 builder
中返回项目本身的 MaterialApp ,在 ScreenUtilInit 的 designSize
参数中传入设计图的尺寸,实现如下:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ScreenUtilInit(
designSize: Size(360, 690), //传入设计图尺寸
builder: () => MaterialApp(
...
),
);
}
}
ScreenUtil.init
直接使用 ScreenUtil.init
方法,传入屏幕尺寸、设计图尺寸和屏幕方向即可对 flutter_screenutil
进行初始化,代码如下:
ScreenUtil.init(
BoxConstraints(
maxWidth: MediaQuery.of(context).size.width, //屏幕宽度
maxHeight: MediaQuery.of(context).size.height, //屏幕高度
),
designSize: const Size(360, 690), // 设计图尺寸
orientation: Orientation.portrait); // 屏幕方向
使用这种方式只需在使用 flutter_screenutil
前进行初始化即可,一般放在根路由即第一个页面加载的时候进行初始化。
注意:
ScreenUtil.init
不能在MyApp
中进行初始化,会报如下错误No MediaQuery ancestor could be found starting from the context that was passed to MediaQuery.of(). This can happen because you have not added a WidgetsApp, CupertinoApp, or MaterialApp widget (those widgets introduce a MediaQuery), or it can happen if the context you use comes from a widget above those widgets.
因为这个时候还没加载MaterialApp
无法使用 MediaQuery.of(context ) 获取到屏幕宽高关于上面两种初始化方法,
flutter_screenutil
作者推荐使用第二种方式。
使用
初始化以后就可以使用 flutter_screenutil
提供的方法获取到适配后的数值进行使用了。
可通过如下 api 获取宽高以及字体的适配数值:
代码语言:javascript复制ScreenUtil().setWidth(540) //根据屏幕宽度适配尺寸
ScreenUtil().setHeight(200) //根据屏幕高度适配尺寸(一般根据宽度适配即可)
ScreenUtil().radius(200) //根据宽度或高度中的较小者进行调整
ScreenUtil().setSp(24) //字体大小适配
传入的参数即为设计图上的大小。在实际使用中的示例如下:
代码语言:javascript复制Container(
width: ScreenUtil().setWidth(200),
height: ScreenUtil().setHeight(540),
child: Text("Hello", style: TextStyle(fontSize: ScreenUtil().setSp(24)),),
);
这样即可使用适配的数值进行开发。
但发现这样写太麻烦了,为了获取一个适配的数值,要写一串的很长的代码。flutter_screenutil
提供了更简洁的调用方法,使用 Dart 扩展为 num 类型扩展了一系列属性可以方便开发者调用,上面的 api 可以通过扩展属性进行如下转换:
ScreenUtil().setWidth(540) => 540.h
ScreenUtil().setHeight(200) => 200.w
ScreenUtil().radius(200) => 200.r
ScreenUtil().setSp(24) => 24.sp
修改后的使用示例如下:
代码语言:javascript复制Container(
width: 200.w,
height: 540.h,
child: Text("Hello", style: TextStyle(fontSize: 24.sp),),
);
这样就简洁多了。
注意:根据前面讲解的适配原理知道,一般情况下
1.w != 1.h
,除非刚好屏幕分辨率比例与设计图比例一致,所以如果要设置正方形,切记使用相同的单位,如都设置相同的 w 或者 h ,否则可能显示为长方形。
除了上面 4 种扩展属性以外,还提供了 sm
以及 sw
、 sh
•sm
:取数值本身与 sp
的值最小的值,如 12.sm
则取 12
与 12.sp
的值进行比较,取最小的值。•sw
:screen width 的缩写,即屏幕宽度,作用是按屏幕宽度比例返回值。如 0.2.sw
则返回屏幕宽度的 20%,1.sw
则是整个屏幕宽度•sh
:screen height 的缩写,及屏幕高度,作用与 sw 类似,返回指定比例的屏幕高度值。如 1.sh
为整个屏幕高度
使用 sp
作为字体单位,默认是会随着系统字体缩放进行变化,如果不想字体随着系统缩放而变化,可设置 textScaleFactor
为 1.0
来实现。项目中可对 MaterialApp
进行全局设置或者对 Text
进行单独设置:
全局设置:
代码语言:javascript复制MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter_ScreenUtil',
theme: ThemeData(
primarySwatch: Colors.blue,
),
builder: (context, widget) {
return MediaQuery(
///设置文字大小不随系统设置改变
data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0),
child: widget,
);
},
home: HomePage(title: 'FlutterScreenUtil Demo'),
),
Text 单独设置:
代码语言:javascript复制Text("text", textScaleFactor: 1.0)
效果
附上官方效果图:
其他 Api
除了适配的 api 以外,flutter_screenutil
还提供了很多实用的 api ,如下 :
•ScreenUtil().pixelRatio
:设备的像素密度•ScreenUtil().screenWidth
:屏幕宽度,等同于 1.sw
•ScreenUtil().screenHeight
:屏幕高度,等同于 1.sh
•ScreenUtil().bottomBarHeight
:底部导航高度,如全屏底部按键的高度•ScreenUtil().statusBarHeight
:状态栏高度• ScreenUtil().textScaleFactor
:系统字体缩放比例•ScreenUtil().scaleWidth
:实际宽度与设计图宽度的比例•ScreenUtil().scaleHeight
:实际高度与设计图高度的比例•ScreenUtil().orientation
:屏幕方向
References
[1]
flutter_screenutil: https://github.com/OpenFlutter/flutter_screenutil
[2]
pub.dev: https://pub.dev/packages/flutter_screenutil