flutter-plugin源码爱恨情仇第二篇,屏幕适配----flutter_screenutil(^3.2.0)。
前言
上篇和马老师一起看了sp在flutter端的实现,主要功能是原生平台代码实现的,今天看一个不使用任何原生代码的插件flutter_screenutil,插件实现了在不同设备上的屏幕适配。接下来就让我们耗子尾汁。
适配原理
先说一下适配原理,根据UI设计时的手机尺寸,计算出实际设备与UI设计的比例,然后每次给控件赋值时都通过这个比例,计算实际尺寸。举个栗子,UI设计尺寸是360x640(单位像素,下同),测试机实际尺寸是720x1280,那么比例就是2,某一个控件在标注稿上,是100x100,那么在测试机上的实际尺寸就是在此基础上在乘比例2,为200x200,这样的效果就是,100的宽,在设计稿上占了屏幕的1/3(大概),实际设备上,使用200的宽,也是占用屏幕1/3,达到相同的显示效果。
注意点
手机的宽高比不是都一样,有的手机会矮一点,有的手机会高一点,实际项目中,一般处理方案是根据宽的比例适配。至于高度部分,如果在部分手机上,会有超过整个屏幕的情况,高度会做成可滚动的,从上往下布局,这样的效果是在高屏幕手机上,下面会有空白,在矮屏幕手机上,显示不完的部分滚动显示。如果不会有超过屏幕的情况,那垂直方向可以相对布局,把多余的空白部分均分,这样的效果是,在高屏手机上,控件间距离大一点,显得空旷一点,在矮屏手机上,控件间距离小一点,显得紧凑一点,但是不同手机的整体效果都是比较一致的。
源码
这时候可能有朋友就要说了,你这扯了这么多有的没的废话,源码呢?我说我这个有用,他说没用,我说有用,他说没用,我说年轻人要讲武德,现在就开始淦源码。
初始化代码:
ScreenUtil.init(context, designSize: Size(375, 667), allowFontScaling: true);
传入的参数分别为,上下文对象,UI设计的尺寸,是否根据系统的字体大小进行缩放,其中后两个参数的默认值分别是1080 x 1920 和false,源码如下:
static void init(
BuildContext context, {
Size designSize = defaultSize,
bool allowFontScaling = false,
})
static const Size defaultSize = Size(1080, 1920);
完整的初始化源码如下:
ScreenUtil._();
factory ScreenUtil() {
assert(
_instance != null,
'\nEnsure to initialize ScreenUtil before accessing it.\nPlease execute the init method : ScreenUtil.init()',
);
return _instance;
}
static void init(
BuildContext context, {
Size designSize = defaultSize,
bool allowFontScaling = false,
}) {
_instance ??= ScreenUtil._();
_instance
..uiSize = designSize
..allowFontScaling = allowFontScaling;
MediaQueryData mediaQuery = MediaQuery.of(context);
_pixelRatio = mediaQuery.devicePixelRatio;
_screenWidth = mediaQuery.size.width;
_screenHeight = mediaQuery.size.height;
_statusBarHeight = mediaQuery.padding.top;
_bottomBarHeight = mediaQuery.padding.bottom;
_textScaleFactor = mediaQuery.textScaleFactor;
}
和sp插件一样,也是一来就就是单例模式,区别是sp每次调用获取单例的方法getInstance时,都会判断有没有初始化,如果没有就先初始化,而screenutil则是必须先手动调用一次初始化方法init后,才会生成单例对象,然后才可以通过ScreenUtil()方法获取到单例对象,导致这一差异的原因,是screenutil的初始化需要传值。
上诉代码中,用到的dart语法糖有
1.判空赋值 a??= b,只有当a为null时,b赋值给a;
2.级联运算符 a..i=1 ..s='s',使用级联运算符,可以让你在同一个对象上连续调用多个对象的变量或方法。
(更多dart语法,可以参考这里或者这里)
初始化方法里面,做的就是一些初始化的操作(这踏马不是废话吗。。。),包括设置UI设计尺寸,字体是否使用系统缩放,设备屏幕信息,其中屏幕信息包括以下:
/// 每个逻辑像素的字体像素数,字体的缩放比例
/// The number of font pixels for each logical pixel.
double get textScaleFactor => _textScaleFactor;
/// 设备的像素密度
/// The size of the media in logical pixels (e.g, the size of the screen).
double get pixelRatio => _pixelRatio;
/// 当前设备宽度 dp
/// The horizontal extent of this size.
double get screenWidth => _screenWidth;
///当前设备高度 dp
///The vertical extent of this size. dp
double get screenHeight => _screenHeight;
/// 当前设备宽度 px
/// The vertical extent of this size. px
double get screenWidthPx => _screenWidth * _pixelRatio;
/// 当前设备高度 px
/// The vertical extent of this size. px
double get screenHeightPx => _screenHeight * _pixelRatio;
/// 状态栏高度 dp 刘海屏会更高
/// The offset from the top
double get statusBarHeight => _statusBarHeight;
/// 底部安全区距离 dp
/// The offset from the bottom.
double get bottomBarHeight => _bottomBarHeight;
有这些信息后,就可以计算比例了,代码如下:
/// 实际的dp与UI设计px的比例
/// The ratio of the actual dp to the design draft px
double get scaleWidth => _screenWidth / uiSize.width;
double get scaleHeight => _screenHeight / uiSize.height;
double get scaleText => scaleWidth;
最后是三个对外暴露的方法,代码如下:
/// 根据UI设计的设备宽度适配
/// 高度也可以根据这个来做适配可以保证不变形,比如你先要一个正方形的时候.
/// Adapted to the device width of the UI Design.
/// Height can also be adapted according to this to ensure no deformation ,
/// if you want a square
double setWidth(num width) => width * scaleWidth;
/// 根据UI设计的设备高度适配
/// 当发现UI设计中的一屏显示的与当前样式效果不符合时,
/// 或者形状有差异时,建议使用此方法实现高度适配.
/// 高度适配主要针对想根据UI设计的一屏展示一样的效果
/// Highly adaptable to the device according to UI Design
/// It is recommended to use this method to achieve a high degree of adaptation
/// when it is found that one screen in the UI design
/// does not match the current style effect, or if there is a difference in shape.
double setHeight(num height) => height * scaleHeight;
///字体大小适配方法
///- [fontSize] UI设计上字体的大小,单位px.
///Font size adaptation method
///- [fontSize] The size of the font on the UI design, in px.
///- [allowFontScaling]
double setSp(num fontSize, {bool allowFontScalingSelf}) =>
allowFontScalingSelf == null
? (allowFontScaling
? (fontSize * scaleText)
: ((fontSize * scaleText) / _textScaleFactor))
: (allowFontScalingSelf
? (fontSize * scaleText)
: ((fontSize * scaleText) / _textScaleFactor));
逻辑很简单,用传入的值,分别乘上宽或者高或者字体的比例,其中字体还要判断是否使用系统缩放。
至此,整个源码流程全部走完。
使用优化
除了上面的流程,插件还提供了一些使用上的优化,一个是size_extension.dart下提供了扩展函数,另一个是flutter_screenutil.dart统一包管理。
扩展函数源码:
extension SizeExtension on num {
///[ScreenUtil.setWidth]
double get w => ScreenUtil().setWidth(this);
///[ScreenUtil.setHeight]
double get h => ScreenUtil().setHeight(this);
///[ScreenUtil.setSp]
double get sp => ScreenUtil().setSp(this);
///[ScreenUtil.setSp]
double get ssp => ScreenUtil().setSp(this, allowFontScalingSelf: true);
///[ScreenUtil.setSp]
double get nsp => ScreenUtil().setSp(this, allowFontScalingSelf: false);
///屏幕宽度的倍数
///Multiple of screen width
double get sw => ScreenUtil().screenWidth * this;
///屏幕高度的倍数
///Multiple of screen height
double get sh => ScreenUtil().screenHeight * this;
}
dart的扩展函数和kotlin差不多,允许你在已有的类上,扩展新的方法,我想说这个语法糖真的是太甜了。
统一包管理源码:
library flutter_screenutil;
export 'size_extension.dart';
export 'screenutil.dart';
这样做的目的是,在项目中只需要导入flutter_screenutil这个包,就可以使用下面定义的另外的包,而不是导入每一个包,在很多插件中都有提供这样的功能。
项目中的具体使用
说下在实际项目中的使用,首先是在第一个界面初始化screenutil,传入UI设计尺寸
ScreenUtil.init(context, designSize: Size(375, 667), allowFontScaling: true);
然后使用宽度适配
Container(
width: 48.w,
height: 48.w,...)
style: TextStyle(color: Color(0xFF404040), fontSize: 14.sp),
使用的是扩展函数写法,需要注意的是,扩展函数数dart2.6之后才有的功能。
总结
插件没有太多的代码,实现的功能却比较强大,回头再看一遍所有的源码,相信很多朋友都可以实现,截止到现在,插件在github共获2.2k star,所以朋友们,想做一个上千star的开源项目,也不是太难的(没有任何贬低的意思),大胆放手去做。(小声BB,我啥时候能做一个上千star的开源就好了)