Flutter UI适配详解 —— Flutter开发必看!
Flutter中的宽高单位
不同于Android中的dp和IOS中的pt,Flutter奉行另外一种单位,即逻辑像素。
Flutter 遵循简单的基于密度的格式,如 iOS。资产可能是1.0x、 2.0x、3.0x或任何其他乘数。Flutter 没有dp 但有逻辑像素,与设备无关像素基本相同。所谓devicePixelRatio 表示物理像素在单个逻辑像素中的比例。
devicePixelRatio表示1逻辑像素在设备上对应的物理像素数(px),不同设备的devicePixelRatio不尽相同,比如我手上的小米8 SE,其devicePixelRatio值为2.75,即在小米8 SE上,1逻辑像素等于2.75物理像素,而在我手中的另一部华为 Mate9上,其devicePixelRatio值为3,即在华为 Mate9上,1逻辑像素等于3物理像素。
(注:devicePixelRatio值可以通过Flutter中的MediaQuery类查看)
这就有点奇怪了,明明小米8 SE比华为 Mate9的分辨率更高,可是小米8 SE的devicePixelRatio值竟然比华为 Mate9的devicePixelRatio值还低,我们以Android中的dp为例,计算这两款手机dp与px的关系。首先,我们需要先计算这两款手机的DPI,根据DPI的计算公式得:
手机型号 | DPI |
---|---|
小米8 SE | √(2244²+1080²)/5.88 ≈ 423.5 |
华为 Mate9 | √(1920²+1080²)/5.9 ≈ 373.4 |
再根据公式得
手机型号 | DP | PX |
---|---|---|
小米8 SE | 1 | 423.5/160 ≈ 2.65 |
华为 Mate9 | 1 | 373.4/160 ≈ 2.33 |
可以看出,同样是1dp,在小米8 SE上对应的像素数是大于华为 Mate9的。
那么,为什么分辨率高的小米8 SE的devicePixelRatio值要比分辨率低的华为Mate 9还小呢?我们来看看devicePixelRatio是如何计算出来的。
通过源码可以发现
在Android中
源码位置>shell/platform/android/io/flutter/view/FlutterView.java
public FlutterView(Context context, AttributeSet attrs, FlutterNativeView nativeView) {super(context, attrs);// ...... 省略 ......mMetrics = new ViewportMetrics();// 通过Java代码获取平台中的density值mMetrics.devicePixelRatio = context.getResources().getDisplayMetrics().density;// ...... 省略 ......
}
可以看到,devicePixelRatio在Android平台就是DisplayMetrics类中的density,拿density是啥,density是密度的意思,其实就是1dp对应的px数,这就怪了,这怎么跟我们自己算出来的值不一样呢,干脆把DisplayMetrics打印出来看看。
- 小米8 SE
DisplayMetrics(density=2.75, width= 1080, height=2029,scaledDensity=2. 75, xdpi=422.03,ydpi=422.204,densityDpi = 440)
- 华为 Mate9
DisplayMetrics(density=3.0, width=1080, height=1920,scaledDensity=3.0, xdpi=375.78, ydpi=375.138, densityDpi=480)
我tm…这dpi的值系统是咋算的,和标准计算公式算出来的不能说一模一样,可以说是毫不沾边啊。
佛了,彻底整蒙圈了,看来这devicePixelRatio是不能直接用了。
在IOS中
源码位置>engine/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm
- (void)viewDidLayoutSubviews {CGSize viewSize = self.view.bounds.size;CGFloat scale = [UIScreen mainScreen].scale;// Purposefully place this not visible._scrollView.get().frame = CGRectMake(0.0, 0.0, viewSize.width, 0.0);_scrollView.get().contentOffset = CGPointMake(kScrollViewContentSize, kScrollViewContentSize);// First time since creation that the dimensions of its view is known.bool firstViewBoundsUpdate = !_viewportMetrics.physical_width;// 在iOS 上,device_pixel_ratio 的值是一个缩放比_viewportMetrics.device_pixel_ratio = scale;_viewportMetrics.physical_width = viewSize.width * scale;_viewportMetrics.physical_height = viewSize.height * scale;
// ....... 省略 .......
}
关于这个scale,苹果的官方文档这么介绍
该值反映了从默认逻辑坐标空间转换到本界面设备坐标空间所需的比例系数。默认的逻辑坐标空间是用点来衡量的。对于Retina显示器,比例因子可能是3.0或2.0,一个点可以分别用9个或4个像素表示。对于标准分辨率显示器,比例系数为1.0,一个点等于一个像素。
简单讲就是
scale == 1 :代表320 x 480 的分辨率(iphone4之前的设备,非Retain屏幕)
scale == 2 :代表640 x 960 的分辨率(Retain屏幕)
scale == 3 :代表1242 x 2208 的分辨率
这个跟IOS中pt的计算方式也不一样吧(我百度的,看到这的IOS的小伙伴,评论里吱一声)。
得,费尽周折,最后发现devicePixelRatio不靠谱,导致Flutter中使用其作为转换的逻辑像素也不靠谱,适配还得另想办法。
flutter_screenUtil
flutter_screenUtil的使用
简单讲一下flutter_scrrenUtil的使用,官方的介绍点这里
只需在main或跟路由(第一个flutter页面)中调用一次Screen.init()
即可
init()
方法接收三个参数,一个必要参数和两个可选参数
static void init(BoxConstraints constraints, {Orientation orientation = Orientation.portrait,Size designSize = defaultSize,})
- BoxConstraints
运行设备的屏幕宽高 - Orientation
屏幕方向(默认竖屏) - Size
UI设计图的宽高尺寸(默认360*690,单位dp)
其中Size中的宽高尺寸可以使用任何单位,dp、pt、px都可以,只要和后面设置使用的单位保持一致就可以。
例如,我这设计图尺寸如下
代码则为
ScreenUtil.init(BoxConstraints(maxWidth: MediaQuery.of(context).size.width,maxHeight: MediaQuery.of(context).size.height),designSize: Size(375, 667),);
初始化完成后怎么使用呢?
比如,现在我们要实现这个按钮
代码如下
Container(width: 325.w,height: 40.h,alignment: Alignment.center,child:ElevatedButton(onPressed: () {}, child: Text("手机一键登录")),)
注意,因为我们在init()
的时候,Size使用的单位是dp,所以这里width和height的使用的也是dp单位对应的值,如果这里使用的是px单位对应的值的话,那就错了。
width对应的值后面要加上.w,height对应的值后面要加上.h。
ok,宽高除了.w和.h,其实flutter_screenUtil还提供了另外一种设置方式,有时候,我不关心宽高的具体值,我只需要设置某个控件的宽高为手机屏幕宽高的一半,那么这个时候就可以用.sw和.sh这两个属性。
- .sw代表屏幕的宽度
- .sh代表屏幕的高度
Container(width: 0.5.sw,height: 0.5.sh,)
到这里,宽高就适配完成了,那么字体呢?
字体也简单,在字体大小值的后面加.sp就可。
Text("手机一键登录",style: TextStyle(fontSize: 14.sp),)
处了上面这些,flutter_screenUtil还有一个.r属性。
这个属性干啥用呢,radius?角度?
不是的,它其实也是宽高的单位,只不过在特殊的情况下才会使用,比如我们要一个宽高都为100dp的按钮,这时候我们再设置width、height为100.w和100.h时,运行起来后会得到一个长方形,惊不惊喜意不意外,这种情况,设置width、height为100.r和100.r即可解决。
flutter_screenUtil的原理
flutter_screenUtil的适配方案非常简单粗暴,那就是 — ’比例‘
init()
方法中传入的屏幕宽高和设计图宽高以及保持单位统一就是为了搞定比例这个事儿的。
拿宽度为例,假如我设计图的宽高尺寸为100dp*200dp,其中有一个10dp宽度的按钮,那么它对应在设备中的宽度就是 > 设备宽度 * 10(控件宽度) / 100(设计图宽度),这个很容易理解吧。
原理理解后,我们去看看.w的代码实现
extension SizeExtension on num {///[ScreenUtil.setWidth]double get w => ScreenUtil().setWidth(this);/.../}
可以看出.w是通过给num增加扩展方法实现的,其调用了ScreenUtil().setWidth(this)
方法作为返回值。
double setWidth(num width) => width * scaleWidth;
setWidth()
接受一个num作为参数,并将num与scaleWidth的乘积做返回值返回。
/// 实际尺寸与UI设计的比例
/// The ratio of actual width to UI design
double get scaleWidth => _screenWidth / uiSize.width;
scaleWidth即为屏幕宽度与设计图宽度的比例。
所以最后
w = _screenWidth * width / uiSize.width;
即
w = 设备宽度 * 控件宽度 / 设计图宽度
其他属性,也是如此计算的。
flutter_screenUtil中的问题
当你的App在不考虑横屏切换时,以上方式的UI适配基本不会有问题,但是,一旦有横屏的状态,.sp和.r就萎了。
竖屏
横屏
大家一起来找茬。。
眼尖的可能已经发现,红蓝色块儿中的字体大小和绿色快儿的宽高出现了明显变小。
这是红色块儿中文字的代码,字体大小用了.sp做适配。
Text('我的实际宽度:${180.w}dp \n''我的实际高度:${200.h}dp',style: TextStyle(color: Colors.white, fontSize: 12.sp),
)
这是绿色块儿的代码,宽高用了.r做适配
Container(padding: EdgeInsets.all(ScreenUtil().setWidth(10)),width: 100.r,height: 100.r,color: Colors.green,child: Text('我是正方形,边长是100',style: TextStyle(color: Colors.white,fontSize: 12,),),
)
直接看.sp和.r的代码,发现这两个方法都使用了scaleText
这个字段做乘数。
///根据宽度或高度中的较小值进行适配double radius(num r) => r * scaleText;///字体大小适配方法///- [fontSize] UI设计上字体的大小,单位dp.double setSp(num fontSize) => fontSize * scaleText;
scaleText
怎么来的?它是scaleWidth和scaleHeight其中较小的内个值。
double get scaleWidth => _screenWidth / uiSize.width;double get scaleHeight => _screenHeight / uiSize.height;double get scaleText => min(scaleWidth, scaleHeight);
看到这,你明白了吗?为什么横屏比竖屏,发生了文字和宽高的缩小。
因为scaleWidth和scaleHeight是屏幕宽高度和设计图宽高度的比值,横屏状态下手机屏幕的宽高值发生变化,而设计图的宽高值却是写死的,就导致了横屏与竖屏下计算出来的的scaleWidth和scaleHeight值不一样。而scaleWidth和scaleHeight的不一样,又导致了scaleText的不一样,而scaleText的不一样又导致了.sp和.r的不一样,最后,这不一样就体现在了横竖屏下的UI上。
最后
如果你的App不考虑横屏,那么flutter_screenUtil中的属性放心用。
如果你的App要考虑横屏的情况,那么.sp和.r属性慎用,字体大小推荐就按flutter中的逻辑像素来,控件长宽相同的情况下推荐都使用.w或.h。
当然,你也可以对.sp和.r的源码进行修改,换成你想要的算法。
参考文章
[1] Flutter大小单位详解
[2] 支持不同的像素密度
[3] DPI、PPI、DP、PX 的详细计算方法及算法来源是什么?
[4] 手机屏幕的DPI是什么和PPI又有什么区别
Flutter UI适配详解 —— Flutter开发必看!相关推荐
- 思科ccna认证工程师NETCONF协议详解网工必看
思科ccna认证工程师NETCONF协议详解网工必看,这些年随着SDN的热潮,一个诞生了十年之久的协议再一次引起了人们的重视,它就是NETCONF协议. 网络配置协议NETCONF(Network C ...
- BAT面试题汇总及详解(进大厂必看)03
该策略可以 大化地节省CPU资源,却对内存非常不友好.极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存.定期过期:每隔一定的时间,会扫描一定数量的数据库 ...
- vue-cli脚手架配置基础文件详解/新手入门必看
vue-cli 脚手架中webpack 配置基础文件详解 需要Word版本 的小伙伴可以发我邮件2445478193@qq.com 一.前言 vue-cli是构建vue单页应用的脚手架,输入一串指定的 ...
- linux shell命令行及脚本编程实例详解_Linux高手必看的10本经典书籍
Linux高手必看的10本经典书籍 Linux 是一个开放.灵活.跨平台的操作系统,上至庞大的数据中心,下至可放于掌心中的嵌入式设备,Linux 的身影无处不在. 如果你想成为一名精通 Linux 程 ...
- Web services详解 :入门必看 | WSDL、SOAP
文章目录 概念 交互过程 Web services 三种基本元素: SEI和CXF WSDL 概念 WSDL文档结构 `` **Operation**(操作) `` ***binding*** 元素 ...
- 超详细 Hadoop 安装(内附ssh免密登录,图文详解,小白必看)
Hadoop 伪分布安装 (内附ssh免密登录,收藏起来看哦) 目录 Hadoop 伪分布安装 (内附ssh免密登录,收藏起来看哦) 一: 安装前准备 二: jdk 安装,java环境配置 ...
- oracle 倒库详细步骤,科目二倒车入库步骤详解,考前必看!
倒车入库被学员们称为"科目二的老大难",其实,掌握了技巧,倒车入库并没有那么难.倒车入库中,控制车速和看准点位至关重要,做好这两点,基本就能拿下了. 看准点位 第一步:上线 上线的 ...
- 集合详解(小白必看)
一.集合概述 集合是JAVA中提供的一种容器,用来存储多个数据. 1.集合与数组的区别 数组特点:类型固定,长度固定 集合特点:类型不固定,长度也不固定,随意存放任何数据 二.集合框架 三.集合分类与 ...
- Windows10自带Ubuntu配置详解(小白必看)
注:1.运行命令时一定要等上一条命令运行完后再运行下一条命令,一串绿色字体后面接个~$然后一个光标闪动,就可以输入下一个命令了. 2.sudo -i 此条命令是进入root模式,标志是~#后一个光标闪 ...
- html表单最全详解,初学必看
大家去面试,去开户都要填各式各样的表单,填好之后给工作人员,他们会按照表单项目与你填的内容来帮你完成业务. 同样的,在互联网冲浪也需要填各种各样的表单,比如用户问卷调查,新注册账号等.那么我们填好的表 ...
最新文章
- Oracle树查询总结
- 【C语言】【笔试题】模拟实现memcmp
- 【原创】多dpi适配的新姿势
- 开放世界下的混合域适应 ——面向真实自然场景下的全新迁移学习范式
- 人人视频从 App Store 下架整改,并下线“快看”相关内容,网友:我追的剧怎么办?...
- python 文件批量转换格式_python实现快速文件格式批量转换的方法
- 初开:什么是系统思考
- 送给你,PBA商业分析指南(全书下载)
- 图片优化——质量与性能的博弈
- 京东区块链白皮书摘要
- 星巴克季节限定星怡杯樱花味拿铁升级回归
- Char Popp加入PSB担任高级副总裁兼全球定性研究主管
- java 设计模式
- wps文本中表格表头重复_WPS表格如何在每页都设置相同表头,原来是这样的
- Vue项目小米购物车
- 氮化镓助力快充小型化,KEMET聚合物钽电容大显身手
- Python之路第一课Day2--随堂笔记
- android 编译 汇编,汇编语言写的hellworld,在安卓手机上运行
- 从零开始快速搭建SpringBoot+Mybatis+小程序应用--微信小程序的入门和前后端的联调
- C语言的内部函数与外部函数
热门文章
- 三星手机将在MWC 2017首展可折叠手机
- 科赛网新人赛-员工满意度预测 MSE 0.02882
- ssm+jsp计算机毕业设计学生综合素质评估管理系统l3rnh(程序+lw+源码+远程部署)
- Audition测试项目制作
- oracle sql 优化(待更新)
- 孤独是人生的必修课 卢思浩
- 乔致庸毕生的三大项目之三:汇通天下、货通天下
- root zenfone 5 android 4.4.2,ZenFone 2(ZE551ML)Root教程震撼来袭!
- JVM系列之:你知道为什么要有两个 Survivor吗?关于卡表技术又有多少了解
- 看机器视觉助力花椒除杂产线!