9102年了,再谈屏幕适配!(1),Android开发学习视频

也许有人会问,这些还不够吗?而且dp、sp不已经是官方适配过了的单位吗?下面我们就来逐步剖析。

_2_为什么官方需要使用设备独立像素适配

===============================================================================

设备独立像素(dp、sp),又叫逻辑像素,是一种用缩放因子(scale)计算出来的、和像素有一定的换算比例的、不受设备分辨率和密度(ppi)制约的尺寸单位。

那么什么是分辨率,什么是ppi,什么是dpi。

分辨率是指在手机屏幕中横竖都有多少个像素点,所谓的1080x1920即是指,屏幕的高有1920个像素点,宽有1080个像素点。

当我们继续查看手机参数的时候,会看到下一个指标,叫做ppi(Pixels Per Inch),表示每英寸所包含的像素点个数**,ppi越大,屏幕越细腻,但是超过了肉眼的分辨率是没有多大意义的**,以荣耀10为例,它的分辨率是1080x2280,那么对角线所具有的像素点个数为2522.86,而主屏尺寸是5.84英寸,那么我们可以得出每英寸所包含的像素点个数为431.997≈432,即ppi=432。

**那么dpi又是什么?**dpi(Dots per Inch),字面意思是每英寸包含的点数,但是实际上它现在更多的用于表示显示策略中的一个参数,在Android中,它是可以在系统中设置的、是可变的、是用于计算缩放因子的,也许在很多文章中我们都可以看到ppi就是dpi这样的言论,但是其实它们已经和最初的释义有所差异,具体参照WHAT IS DPI,个人认为这篇文章是讲述比较全面、靠谱、符合事实的。

https://blog.kyleduo.com/2019/06/29/what-is-dpi/

而独立像素,为什么不受分辨率和密度制约?

我们首先明白,当我们假定的认为像素点都是趋于正方形时,密度只能影响视觉呈现的物理大小和精细程度,屏幕上高宽都为x个像素所组成的正方形,在相同分辨率不同密度的手机中,他们只是视觉大小不一样,但是占据屏幕的比例是一致的。

那么我们只需要分析为什么独立像素不受分辨率制约。

我们知道,每个手机出厂时都写好了固定的dpi在手机的系统文件中,而dpi是形成独立像素的一个重要参数,我们可以根据dpi计算出dp和px的换算比例,也就是缩放因子(scale),从官方的文档,我们可以得出一个公式

1dp = 1px * scale = 1px * dpi / 160

也就是说,只要按照相同的规则控制dpi的数值,我们在宽度的纬度上,可以做到将所有分辨率都换算成一个数值的设备独立像素。

打个比方,现在大部分720x1280的手机的dpi都等于320,根据公式可得,宽度为360dp,而1080x1920的手机大部分的dpi都等于480,同样根据公式所得宽度为360dp。

那么相同的设备独立像素可以做什么呢?只要我们设置成宽度为180dp,那么它永远是占据屏幕宽度一半的比例。

而若是我们直接使用像素作为控件的单位,那么是无法保证它在不同分辨率的手机是占据相同的比例。

同样举个例子,在720x1280的手机上我们设置宽度为360px,它将占据一半的屏幕,而在1080x1920的手机上只能占据三分之一的屏幕。

这就是为什么官方推荐我们使用设备独立像素作为尺寸的单位。那么问题来了,既然设备独立像素这么优秀,那么我们——

_3_为什么要二次适配

======================================================================

上节举例中说到,大部分720x1280、1080x1920的手机宽度都是360dp,而大部分480x800的手机(dpi=240)宽度是320dp,那么当设计稿是360dp的时候,会发生什么。

举个例子,如下图所示,两台设备分辨率一致,但是dpi不一致,前者是480dpi,后者是540dpi(ps:不要问有没有这种机器,nove4e就是这样的),而设计稿是以360dp为基准,热度排名和贡献排名的宽度比例是3:4,则我们可以看到其在320dp下表现比较差,即使我们再如何布局,如何使用属性,它永远是不完美的,因为它的逻辑宽度永远都比设计稿少40dp。

而除了320dp、360dp,单国内手机的逻辑宽度就还有345.6dp、375.6dp、392.7dp、411.4dp、423.5dp等等。当然,理论上来说更多的逻辑宽度应该显示更多的内容,然而现实的情况往往不允许,这意味着——

  1. 需要设计多套图;

  2. 开发工作繁重、维护困难;

  3. 增大包体积,毕竟不像iOS有App Slicing;

总之,就是人力成本太高。但是通过二次适配,我们可以做到一套设计图适配“所有”设备,一套布局“全家”适用。也许这不是最好的方案,但是综合来看这是最合适的方案,是最具性价比的方案。

那么我们要:

_4_怎么做二次适配

=====================================================================

做二次适配的方法有多种,大体可以分为穷举和Hook。

注:因为当下大部分app的应用场景只在于竖屏,即使有横屏的界面也只需要保持高度不变,宽度自适应。退一步讲,真的有个别页面的高度也需要自适应时,可以具体场景具体分析,即使不做二次适配,也是ok的。因此以下的适配方法只从宽度的纬度来讲述。

穷举宽高限定符

我们都知道,宽高限定符的匹配规则是,双边都小于屏幕分辨率的最接近的值。

根据这个规则,我们尽可能的枚举出所有的分辨率(虽然分辨率有很多,但是我们只需要按照宽度来枚举即可,高度设置成略大于宽度)。

而根据testin、wetest云真机的分辨率,我们可以得出文件结构如下:

±- res

| ±- values

| ±- values-330x320

| ±- values-490x480

| ±- values-550x540

| ±- values-650x640

| ±- values-730x720

| ±- values-780x768

| ±- values-810x800

| ±- values-1100x1080

| ±- values-1160x1152

| ±- values-1210x1200

| ±- values-1450x1440

| ±- values-2170x2160

然后以1080px为基准,计算出1px在其他分辨率下的等比值 (注:默认values=values-1100x1080),假设目标分辨率的宽为W,则公式为:

px’ = W/1080

举个例子,720px的分辨率的dimens值为:

0.66px

1.33px

2.0px

2.66px

3.33px

4.0px

4.66px

5.33px

6.0px

6.66px

.

.

.

720px

配置好后,我们从以上分辨率中选择9种采样,看看实际运行效果如何:

由上图可见,运行结果是非常符合我们预期值的,占一半屏幕的还是占一半,热度排名和贡献排名的间距也基本差不多,唯一比较明显的是每行的文字字数±1,这是由于换算之后的像素有小数点造成的,但是这是可以接受的。

然后我们再来分析一下极端情况,首先因为我们是穷举分辨率,所以此处不需要考虑dpi,又因为我们穷举的分辨率的宽是320-2160,所以我们可以从这个角度考虑边界值:

  1. 宽低于320px,匹配默认values,此种情况可以认为不存在;

  2. 宽等于某一个枚举值(假设为720px),高度恰好小于730px,匹配大一级的values,但是因为我们设置的高度只是略大于宽,可以认为此种情况不存在;

  3. 宽大于某一个枚举值,但是小于下一级的宽度(假设为1600px),匹配values-1450x1440;

  4. 宽小于某一个枚举值,但是大于上一级的宽度(假设为1300px),匹配values-1210x1200,等同于第3点;

  5. 宽大于2160px,匹配values-2170x2160,等同于第3点;

我们再看看3、4、5的运行效果如何:

从结果可以看出,我们出现的极端情况都是比预期值要宽,这是因为我们分辨率限定符是向下匹配的。

综上所述,我们得出结论:

  1. 在已知分辨率的设备中,此方法基本可以完美适配机型;

  2. 所有地方都建议统一使用px’,包括字体、自定义控件等,否则就会不兼容;

  3. 因为字体也需要使用px’,所以app字体大小不会受到系统设置——字体显示大小的影响;

  4. 因为使用了px’,代码中动态设置大小间距等等要额外注意单位;

  5. 因为非1080px是换算比例,必然存在小数点,因此会存在一丢丢误差;

  6. 枚举分辨率太多,导致dimens文件过多,包体积会增大一点,如果1080个px全部做映射的话,以示例中的枚举值大概要多0.5MB左右;

  7. 因为存在场外分辨率,即使使用了此适配,依然不可以盲目的用绝对值,还是要配合其他控件属性一并使用;

  8. 侵入性比较高,依赖技术人员的素养;

_5_穷举最小宽度限定符

=======================================================================

最小宽度限定符,是指在逻辑宽度上限定使用小于并且最接近于屏幕宽度的资源。而逻辑宽度(W’)可以从分辨率(W)和dpi得知:

W’ = W / ( dpi / 160 )

我们可以和穷举分辨率限定符一样,穷举出所有可能的逻辑宽度。为了分析,我们暂定文件结构如下:

±- res

| ±- values

| ±- values-sw320dp

| ±- values-sw360dp

| ±- values-sw411dp

然后以360dp为基准,计算出1dp在其他逻辑宽度下的等比值 (注:默认values=values-sw360dp),假设目标逻辑宽度为W,则公式为:

dp’ = W/360

同样的举个例子,320dp的逻辑宽度的dimens值为:

0.89dp

1.78dp

2.67dp

3.56dp

4.44dp

5.33dp

6.22dp

7.11dp

8.00dp

8.89dp

.

.

.

320dp

我们来看看实际的运行效果:

显而易见,又是符合我们预期的,但是不可避免的是逻辑宽度依旧存在刺头(原因见文章开头——为什么要二次适配),比如384dp(Nexus 4)、392dp(XiaoMi MIX2),所以我们再次来看看极端情况:

  1. 逻辑宽度小于320dp,虽然没有数据支撑,但是我们假定的认为这已经是最小的宽度了,或者说,总有一个最小值(后续等统计出来,补上相关数据);

  2. 逻辑宽度位于两个枚举值之间,比如384dp;

  3. 逻辑宽度大于411dp;

好了,因为最小宽度限定符依旧是向下匹配的,从而又回到了和上一节一模一样的情况——极端情况比预期值要宽,所以此处我们不再重复贴图。

那么我们总结一下此节:

  1. 在已经枚举的逻辑宽度中,基本可以完美匹配设备;

  2. xml、代码、包括自定义控件中都需要使用dp’的引用来保持一致;

  3. 需要再配置一套TextSize,至于是用dp还是sp,仁者见仁智者见智(微信没有用sp);

  4. 枚举值映射的dp’值比宽高限定符少;

  5. 相比宽高限定符兼容性略高,即使有些地方直接写成了dp,也是可以的;

  6. 同样因为是换算比例,必然存在小数点,最好应用成px时,也可能存在一丢丢的误差;

  7. 包体积和dimens的枚举数量成正比;

  8. 同样存在场外dpi,不可以盲目的用绝对值;

  9. 侵入性比较高,依赖技术人员的素养;

上面说了如何穷举来进行适配,但是如何穷举的既完整又简洁是一个难点,那么可不可能有一个测量的终点,所有的间距、大小、尺寸都会通过这里,我们在这个终点进行自动化适配就好了?当然是有的。

_6_选择onMeasure进行Hook

===============================================================================

我们知道,view是需要先measure然后layout然后才draw的,那么切入点就来了——onMeasure。

典型的例子是AndroidAutoLayout,它的使用方法详见ReadMe,这里不再赘述。

https://github.com/hongyangAndroid/AndroidAutoLayout

其核心思想是便是通过重写其onMeasure,在调用super.onMeasure(widthMeasureSpec, heightMeasureSpec)之前重新根据屏幕宽度及高度设置了相关属性的值,如padding、margin、height、width、textSize。

当然,AndroidAutoLayout的上一次提交代码已经是在4 yeas ago,在它的设计之初,是假定的认为所有的手机的高宽比都是在一个恰当的范围,比如720x1280,所以它的高宽都分别进行了不同比例的缩放适配。

然而,9102年,手机的高宽比显然已经多种多样。所以,AndroidAutoLayout已经进入了它的局限性。

但是,它依旧是我们可以借鉴的目标,我们只需要将其高按照宽的缩放比例来缩放,或高或矮的手机自适应高度即可。(有兴趣的同学可以尝试一下,这里只讲如何hook~)

那么,onMeasure中怎么hook呢?

在AndroidAutoLayout中,它写了很多的自定义ViewGroup,比如AutoLinearLayout、AutoRelativeLayout、AutoFrameLayout,其实里面的代码都大同小异,我们以AutoLinearLayout为例——

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

if (!isInEditMode()) { //这句代码不用管,用来判断是否是IDE预览模式的

mHelper.adjustChildren();

}

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

}

可以看到,AndroidAutoLayout在super.onMeasure之前只做了一件事,就是adjustChildren,修改控件属性值。

public void adjustChildren() {

AutoLayoutConifg.getInstance().checkParams(); //这句话不用管,用来检查库配置的

for (int i = 0, n = mHost.getChildCount(); i < n; i++) {

View view = mHost.getChildAt(i);

ViewGroup.LayoutParams params = view.getLayoutParams();

if (params instanceof AutoLayoutParams) {

AutoLayoutInfo info = ((AutoLayoutParams) params).getAutoLayoutInfo();

if (info != null) {

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
img

最后

由于文章篇幅原因,我只把面试题列了出来,详细的答案,我整理成了一份PDF文档,这份文档还包括了还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 ,帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习。

进入阿里一直到现在。**

深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-kIOLh12A-1711284196441)]
[外链图片转存中…(img-P6rWk2kk-1711284196441)]
[外链图片转存中…(img-vyNKKh8d-1711284196442)]
[外链图片转存中…(img-bIElHy49-1711284196442)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
[外链图片转存中…(img-d2pMHH7m-1711284196443)]

最后

由于文章篇幅原因,我只把面试题列了出来,详细的答案,我整理成了一份PDF文档,这份文档还包括了还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 ,帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习。

需要的朋友可以私信我【答案】或者点击这里免费领取

  • 9
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值