需求-为什么要适配
app首页是一个不可以滑动的页面,因此需要高和宽同时适配;内容比较多——日期,抽奖按钮,步数表盘,步数柱状图,底部的Tab,广告等;首页中心是一个圆形表盘,高度取剩余高度,宽度取屏幕宽度,并且考虑高宽相等,两方面适应之后决定圆形表盘的直径,导致在不同手机上效果千奇百怪。
适配方案的选择
常见的适配方案,可以参考这篇文章——《Android 目前稳定高效的UI适配方案》,文中提到的几种适配方案包括:
- dp 直接适配
- 宽高限定符适配
- SamllestWidth适配
- 今日头条适配方案
对于Pacer这个App和首页的适配需求来说:
- 使用dp直接适配方案,dp作为像素独立单位,配合使用wrap_content, match_parent, weight等参数,可以满足大部分业务需求。但是主页需要精确到比例的适配,不适合。
- 宽高限定符适配方案,需要穷举市面上所有的Android手机的宽高像素值。屏幕碎片化越来越严重,不适合;而且长宽同时适配,比例失调。
- SamllestWidth适配方案,也叫sw限定符适配。指的是Android会识别屏幕可用高度和宽度的最小尺寸的dp值(其实就是手机的宽度值),然后根据识别到的结果去资源文件中寻找对应限定符的文件夹下的资源文件。维护麻烦,而且首页不是比例的问题,不是只有宽度适配,需要高度宽度同时适配,只在一个维度上的适配不能满足。
- 今日头条适配方案本质也是按照比例缩放,可侵入性低,而且以此为基础的AutoSize框架又增加了一些好用的接口方法,满足一般页面的适配和首页的宽度适配,在高度上自行设计同时满足高度宽度适配。
具体适配方案
1.使用AndroidAutoSize屏幕适配框架
使用dp为单位,让屏 幕宽度不等于设计图的都能很好的适配,以宽为基准进行适配。
可以解决大部分页面效果不统一的情况。
2.首页需要宽度高度同时适配
设定宽度基准之后,假设宽度都是360的基础上,计算首页中心fragment的高度:
fragment高度=屏幕高度 - 系统导航栏高度 - 系统状态栏高度 - app广告高度 - app导航tab高度 - 头部按钮高度
根据不同的fragment高度,划分到5档,5个区间高度分别为:
- 大于520dp
- 520 到 456
- 455 到 379
- 378 到 342
- 小于342
由UI分别按照比例设计5套UI,尽可能保证在不同宽高比的屏幕上,视觉效果统一。
代码上,根据fragment高度,加载5套布局文件中的一套。
3.ConstraintLayout和Space保证首页布局效果
布局文件的区别体现在:
1.字体大小不同
2.控件大小不同
因为布局的高度在一定范围内而不是确定的值,所以需要控件之间的间隔在一定范围内可以自适应。
首页内容比较多,使用ConstaintLayout布局,减少嵌套层次。
使用Space控件来做组件之间的间隔,Space控件的高度使用权重(layout_constraintVertical_weight)设置,保证5个档位内的高度范围内,按照比例分配间隔高度。
Space 经常用于组件之间的缝隙,其
draw()
为空,减少了绘制渲染的过程。组件之间的距离使用 Space 会提高了绘制效率,特别是对于动态设置间距会很方便高效。
正是因为
draw()
为空,对该 view 没有做任务绘制渲染,所以不能对 Space 设置背景色。
4.缺点
因为首页+左滑+右滑,共3个fragment,都是圆盘结构,都需要宽度高度同时适配,所以一共需要3*5=15个layout文件。
5.升级版方案
首页改版为可滑动,通过布局自适应不能满足自动填充一整屏幕的需求,改为通过代码计算高度进行设置。
具体实施——工具类ScreenAutoSizeUtil
将比例写入map里面,将其他元素高度设定之后,用代码通过比例和剩余总高度计算真实高度,设置到代码中。节省了布局文件,可读性变差,灵活性高。(待补充)
AndriodAutoSize原理
原理同今日头条适配方案原理相同。
原因
设计稿宽度是固定的,只有一种宽度,而屏幕的真是宽度其实是有很多的。
虽然dp的出现目的在于取代像素,尽量保证屏幕的宽度统一,但是屏幕碎片化严重,导致以dp为单位,宽度还是有很多种,不能统一。
原理
在屏幕绘制的时候,需要把单位换算成px,也就是最终是使用px为单位进行绘制的。平时我们使用dp为单位写布局之后,最终要使用公式px=dp * density转换为px进行绘制。
这套方案,相当于修改了dp的定义——dp代表的是屏幕宽度平均分成【设计图宽度】的份数之后的1单位长度。
比如屏幕宽度是1080px,用dp为单位时屏幕宽度是480dp,density=2.25。
要使用这个方案的话,假设我们的设计图宽度是360,我们修改了dp的定义,dp的新定义是讲屏幕平分为360份之后的单位长度(也就是3px),按照这个dp的数值计算出density应该修改为3。
我们再使用新的dp去绘制控件的宽度,最终在绘制的时候再用新的density去计算成px:
屏幕宽度 x density=360 x 3 = 1080
在像素这个单位上,宽度没有问题,还是真实宽度。
简单说,方案通过在 Acivity#onCreate 中修改 density 的值,强行把所有不同尺寸分辨率的手机的屏幕宽度dp值改成一个统一的值。
可行性
主要回答3个问题:
- 为什么density可以修改?
- 为什么不会影响其他APP
- 如何做到可以指定Activity不适配、单独适配的?
1. 为什么density可以修改?
先说结论:density不同于dpi,dpi是有物理含义的,表示每英寸的像素数,屏幕的物理宽度确定,像素数量确定,dpi就是确定的;density不是一个固定在系统中的值,而是一个“用于显示的逻辑密度”(The logical density of the display)在程序启动之后通过真实物理中的dpi和规则(160为标准)计算出来的,这个规则是以160为标准,是一个类似约定俗成的参考标准。density是public成员,因此可以很方便的修改。
阅读源码,查看density的值是怎么来的:
density 是 DisplayMetrics 中的成员变量,DisplayMetrics 实例通过 Resources#getDisplayMetrics 可以获得:
public DisplayMetrics getDisplayMetrics() {
return mResourcesImpl.getDisplayMetrics();
}
mResourcesImpl是ResourcesImpl实例,查看ResourcesImpl#getDisplayMetrics :