前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android技能树 — 屏幕适配小结

Android技能树 — 屏幕适配小结

作者头像
青蛙要fly
发布2018-08-29 15:13:35
8730
发布2018-08-29 15:13:35
举报
文章被收录于专栏:青蛙要fly的专栏

前言:

Android技能树系列:

Android基础知识

Android技能树 — 动画小结

Android技能树 — View小结

Android技能树 — Activity小结

Android技能树 — View事件体系小结

Android技能树 — Android存储路径及IO操作小结

Android技能树 — 多进程相关小结

Android技能树 — Drawable小结

Android技能树 — 屏幕适配小结

数据结构基础知识

Android技能树 — 数组,链表,散列表基础小结

Android技能树 — 树基础知识小结(一)

算法基础知识

Android技能树 — 排序算法基础小结

Rx系列相关

Android技能树 — RxPermission分析

Android技能树 — Rxjava取消订阅小结(1):自带方式

Android技能树 — Rxjava取消订阅小结(2):RxLifeCycle

关于屏幕适配,几乎每隔一段时间就会看见有人发出来说XXX方案,实现超级简单的适配方式等等。所以我把我目前了解过的常用的适配方案做个总结,并简单说说原理,从而让大家也初步了解各个方案的实现。(其实很多人都是看见别人写的适配方案,虽然可能实际在使用了,但是却从来没有去了解过这个方案的原理,而且遇到一些简单的坑的时候,因为不知道原理,也无法自己解决。)

常见适配方案:

  1. 生成分辨率values文件夹
  2. 生成values -sw 文件夹
  3. 谷歌百分比布局库
  4. AutoLayout
  5. 动态更改density

1. 基础知识

其实本来不想写这块,因为基本大家都懂什么dp, dpi ,px , inch ,density等,但是后面的一些适配都会涉及到这些原理,外加有时候面试别人,都是感觉知道这个知识点,但并不是真正的了解,所以我这边还是重新提一下,我会用通俗易懂的例子来让大家更好的理解。 (PS: 当然想不看的可以直接跳过。)

1.1 px

我们可以看到现在市面上的手机分辨率截止到2018-05月,统计为:

这里额外提一下,类似1080 x 1812,720 x 1184 等看着很奇怪的结尾不是0的分辨率,大部分是因为有虚拟键的原因,虚拟键占去了一部分高度。

以1080 X 1920为例,它代表的是手机上的像素点,

类似这种,表示横着有1080个像素点,竖着有1920个像素点,所以1080 X 1920 代表了手机在横向、纵向上的像素点数总和

所以如果我们写了一个Button,假设高度和宽度都为10px , 则说明在这个屏幕点上高宽都占了10个点。

1.2 inch(屏幕尺寸)

手机屏幕的物理尺寸,我们经常听到有人说我买的是iPhone 8 plus,尺寸是5.5的屏幕,iPhone 8尺寸是 4.7的。其实它们所带的单位都是inch(英寸), 1(inch)≈2.54(cm)

所以屏幕尺寸就是按屏幕对角测量的实际物理尺寸。 为简便起见,Android 将所有实际屏幕尺寸分组为四种通用尺寸:小、 正常、大和超大。

1.3 dpi

屏幕物理区域中的像素量;通常称为 dpi(Dots Per Inch 每英寸 点数)。所以看标题就知道,他更像是在求一个密度。那我们既然知道了手机屏幕对角线的尺寸,我们只要知道了手机对角线上的px数量,除一下就知道了每英寸上的像素点数了。

所以我们只需要通过勾股定理获取对角线上的像素值,再除以屏幕尺寸值就可以了。

为简便起见,Android 将所有屏幕密度分组为六种通用密度: 低、中、高、超高、超超高和超超超高。

六种通用的密度:

  • ldpi(低)~120dpi
  • mdpi(中)~160dpi
  • hdpi(高)~240dpi
  • xhdpi(超高)~320dpi
  • xxhdpi(超超高)~480dpi
  • xxxhdpi(超超超高)~640dpi

1.4 dp 和 density

其实dp 本来是叫dip (Density Independent Pixels),所有有时候面试的别人,面试者会弄错,把dip当做了dpi,所以你问他请说下 dp 和 dip ,他会把 dip说成dpi的内容。

我们举例说下这块知识点: 要画一个 高和宽各为屏幕的一般的按钮,我们假设有二块屏幕,一块是100 X 100 ,一块是 200 X 200 ,那这时候第一块的屏幕上我们写Button 应该为:

代码语言:javascript
复制
<Button 
     layout_height = "50px"
     layout_width = "50px"/>
复制代码

第二个屏幕的Button应该为:

代码语言:javascript
复制
<Button 
     layout_height = "100px"
     layout_width = "100px"/>
复制代码

这样是不是都各自占了屏幕的高宽的一半,但是假如有第三个屏幕 300 X 300 呢,难不成再写一个Button的高宽值? 所以我们可以用一种单位来代替,但是这种单位可以在不同的屏幕环境下,值是不同的。比如我们就把这个单位当做“haha”。

比如我们现在都这么写:

代码语言:javascript
复制
<Button 
     layout_height = "50haha"
     layout_width = "50haha"/>
复制代码

这时候在100 x 100的时候, 50haha = 50px ,在200 X 200 屏幕的时候 , 50 haha = 100px , 在 300 X 300 屏幕的时候,50haha 等150px。

这个感觉就很像你跟别人说我欠你50 money,如果在中国,代表你欠别人50元人民币,但是如果在美国,你这么说,指你欠50美元,也就是欠了三百多元人民币。(这个例子不要跟我较真,我就意思意思而已)

所以dp就是类似我们上面自己定义的haha这个单位。

比如50dp = 50px ,这时候1dp = 1px , 50dp = 100px的时候 是 1dp = 2px ,所以我们可以看到倍数分别为 1 和 2 ,我们用density来代表这个倍数。也就是说: dp * density = px,这时候就是 50 dp * 1 = 50px , 50dp * 2 = 100px

(就像是我说我欠你50 money,在中国,这个density就是1 , 也就是欠你50元人民币,在美国可能就是指300多人民币,这个density也就是 美元换算成人民币的倍数)

那么这个density具体是怎么来的呢?其实很简单,记不记得我们前面说过dpi ,也就是屏幕的密度,我们就用这个密度来做比较,比如我们 把160dpi 作为标准,那另外一个手机是320dpi ,那么这个density就是 (320/160 = 2)。 所以我们再次把公式 : dp * density = px 转变为: dp * (dpi / 160) = px

那么为什么用160dpi作为标准呢,以前看到文章提过:mdpi基于第一款 Android 设备 ″T-Mobile G1″ 的屏幕配置(缩放系数scale=1)。

1.5 基础知识小结

所以假如我们现在的手机分辨率知道了,手机屏幕尺寸也知道了。我们通过公式求出 dpi ,然后 dpi / 160 就是当前手机的density,然后我们就知道我写了1dp 在这台手机上具体是多少px了。

具体的安卓手机尺寸四个分类及6中dpi分类:

我们的某台手机的dpi,density,分辨率等如何获取呢,:

代码语言:javascript
复制
DisplayMetrics mDisplayMetrics = getResources().getDisplayMetrics();    
//横向分辨率
int width = mDisplayMetrics.widthPixels;  
//竖向分辨率
int height = mDisplayMetrics.heightPixels;  
//density值
float density = mDisplayMetrics.density;  
//dpi的值就等于density * 160
float dpi = density * 160;
复制代码

也许有人说,那我们使用dp不是已经完美的实现了各种兼容性吗,就像我们上面提到过的,100 X 100 ,200X200 , 300 X 300的屏幕,我们都只要写50haha, 就分别代表了50,100,150,不是就占了各自屏幕的一半了么。理论上的确是这样,但是我们刚提过我们的density是等于 (dpi / 160),而dpi又由分辨率和屏幕尺寸同时决定,安卓手机的碎片化太过严重,所以很多手机虽然分辨率不同及屏幕尺寸不同,造成最后的dpi一样,所以最后的density也一样,就造成了适配实现不全。假设我们多了一个400X400 的设备,因为它的屏幕尺寸也同时变大了很多,所以最终的density和300X300一样,那这时候我们写了50haha,也就代表了150px,这时候明显在400X400上面并没有显示为一半,甚至当这个400X400的设置的屏幕尺寸超级大,反而可能算下来的density与100X100的一样,那这时候50haha可能就只有50px,则显示差距就更大了。 (其实主要原因就是dpi不是单独由分辨率来决定,同时还有屏幕尺寸影响,所以二个变量同时作用,造成不同分辨率的手机最后的density也可能相同。这样dp转换成的px也就相同了,但是手机的分辨率本身有不同,这时候就会出现适配不对。)

2 各类适配方案

2.1 生成分辨率values文件夹

因为我们上面提过 , px = (dpi / 160) * dp, 但是dpi又是同时由分辨率和屏幕尺寸同时决定,造成了不同的分辨率,dpi可能一样,这样最终得到的px一样,比如都是占屏幕的一半,300X300得到的可能是150,但是400X 400得到的也是150,这时候就不对了。

那我们就想到了。我们能不能不是同时受到分辨率和屏幕尺寸决定,而是只受一个因素来影响,这样就是真正的按比例来了。比如300X300是150,400X400是200,500X500是250,是只受分辨率的影响,所以分辨率大的,最终得到的结果一定就大。所以我们就不能使用dp了。而是一个新的单位,而这个单位是根据不同的分辨率,得到不同的值,那怎么计算呢,就是穷举法,比如刚才的300X300,我们规定1 haha等于1 px,然后再600 X 600里面,1 haha 等2 px , 1200X1200里面是 1 haha 等于 3 px 。所以我们在不同分辨率下的values文件夹下写上不同的值:

代码语言:javascript
复制
300X300下
<dimens name = "1haha"> 1px </dimens>
600X600下
<dimens name = "1haha"> 2px </dimens>
1200X1200下
<dimens name = "1haha"> 3px </dimens>
复制代码

所以这个就是方案1 ,附上文章链接。

Android 屏幕适配方案 我们可以看下面的图:

我们可以看到列举了所有可能的屏幕分辨率的values,然后手动按照倍数,进行相应的赋值。当然这些文件不可能手写,通过Java自动生成相应的文件:

这样最终影响结果的就只是分辨率的了,分辨率越大的,x1的值越大。

但是这个方案有一个致命的缺陷,那就是需要精准命中才能适配,比如1920x1080的手机就一定要找到1920x1080的限定符,否则就只能用统一的默认的dimens文件了。而使用默认的尺寸的话,UI就很可能变形,简单说,就是容错机制很差。


2.2 生成values -sw 文件夹

可以参考:Android 目前最稳定和高效的UI适配方案

其实这个方式跟上面的2.1方法原理可以说一模一样。唯一的区别就是使用了sw来保证一定的容错性。

我们看到其实就是把上面具体的分辨率values改成了values - sw而已。


2.3 百分比布局库

Android 百分比布局库(percent-support-lib) 解析与扩展 Android 增强版百分比布局库 为了适配而扩展

其实这个也是很简单的,字面意思,我写了这个Button宽度为父布局的百分之50,则在不同手机上,都是占据了百分之50。使用过过百分比布局的人都应该知道,我们写的时候是这么写的:

代码语言:javascript
复制
<android.support.percent. PercentRelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_heightPercent="20%"
        app:layout_widthPercent="50%"
        android:gravity="center"
        />

</PercentRelativeLayout >
复制代码

其实原理很简单,就是动态计算实际的百分之50在不同机器的时候到底占了多少px,2.1,2.2则是等于提前帮我们计算好了具体的px,然后写在了文件里面,然后我们去读数据。

那它的实现原理是什么呢?简单来说就是二步:

  1. 获取用户到底填了多少的百分比数值
  2. 获取父布局的空间,然后乘以用户填的百分比数值,或者一个新数值,然后赋值给该控件。

我们一步步来看源码:

2.3.1 获取用户到底填了多少的百分比数值:

我们知道我们的百分比布局中的核心属性是子控件填写:

代码语言:javascript
复制
app:layout_heightPercent="20%"
app:layout_widthPercent="30%"
复制代码

所以我们需要在PercentRelativeLayout中遍历它下面的子控件,然后分别获取每个子控件的百分比数值。 其实很简单,写过自定义View的人应该都知道,因为这个其实就是自定义属性而已。

代码语言:javascript
复制
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.PercentLayout_Layout);
float value = array.getFraction(R.styleable.PercentLayout_Layout_layout_widthPercent, 1, 1,-1f);
复制代码
2.3.2 获取计算后的值并且赋值:

因为要动态获取父控件的控件,同时把新的值赋值给子控件,所以该行为在onMeasure方法中执行。

代码语言:javascript
复制
//传入的ViewGroup.LayoutParams params是遍历的每个子View的LayoutParams
public void fillLayoutParams(ViewGroup.LayoutParams params, int widthHint,
                int heightHint) {
            // Preserve the original layout params, so we can restore them after the measure step.
            mPreservedParams.width = params.width;
            mPreservedParams.height = params.height;

            if (widthPercent >= 0) {
                params.width = (int) (widthHint * widthPercent);
            }
            if (heightPercent >= 0) {
                params.height = (int) (heightHint * heightPercent);
            }
            if (Log.isLoggable(TAG, Log.DEBUG)) {
                Log.d(TAG, "after fillLayoutParams: (" + params.width + ", " + params.height + ")");
            }
}
复制代码

当然具体源码会更多,我不会大篇幅完整讲流程,更多的是讲解思路。


2.4 AutoLayout

Android AutoLayout全新的适配方式 堪称适配终结者

使用方式很简单:

  1. 注册设计图尺寸

autolayout引入

代码语言:javascript
复制
dependencies {
    compile project(':autolayout')
}
复制代码

在你的项目的AndroidManifest中注明你的设计稿的尺寸。

代码语言:javascript
复制
<meta-data android:name="design_width" android:value="768"></meta-data>
<meta-data android:name="design_height" android:value="1280"></meta-data>
复制代码
  1. Activity中开启设配 让你的Activity去继承AutoLayoutActivity

我们想到的原理,肯定也是把填在AndroidManifest.xml里面的数值读取出来,然后作为参考值。然后在不同手机上动态的计算出来数值,是不是感觉和百分比布局有点相似。

我们来看下AutoLayoutActivity源码:

代码语言:javascript
复制
public class AutoLayoutActivity extends AppCompatActivity
{
    private static final String LAYOUT_LINEARLAYOUT = "LinearLayout";
    private static final String LAYOUT_FRAMELAYOUT = "FrameLayout";
    private static final String LAYOUT_RELATIVELAYOUT = "RelativeLayout";


    @Override
    public View onCreateView(String name, Context context, AttributeSet attrs)
    {
        View view = null;
        if (name.equals(LAYOUT_FRAMELAYOUT))
        {
            view = new AutoFrameLayout(context, attrs);
        }

        if (name.equals(LAYOUT_LINEARLAYOUT))
        {
            view = new AutoLinearLayout(context, attrs);
        }

        if (name.equals(LAYOUT_RELATIVELAYOUT))
        {
            view = new AutoRelativeLayout(context, attrs);
        }

        if (view != null) return view;

        return super.onCreateView(name, context, attrs);
    }
}
复制代码

我们发现把我们写在Layout.xml里面的布局控件替换成AutoXXXX等自定义控件。那我们以AutoLinearLayout来分析:其实看过百分比布局的源码,就会发现基本架构都一样,所以百分比布局的代码看得懂,再去看AutoLayout相关代码会很快。


2.5 动态更改density

一种极低成本的Android屏幕适配方式

Android屏幕适配很麻烦吗?不!太简单了。

Android 屏幕适配从未如斯简单

  1. 假如设计图是按1920px * 1080px来设计,以density为3来标注,也就是屏幕其实是640dp * 360dp。这时候如果我们的Button想要占据一半,是不是宽度需要设置成180dp。
  2. 那假如我们的手机屏幕是1280X 720,density是2 ,则宽度是360dp,的确当设置成180dp的时候也正好占据一半。
  3. 但是万一1280X 720的手机的density是3呢,则宽度为240dp, 这时候设置成180dp,实际的px值为: 180 * 3 = 540px ,但是我们想要的是360px ,也就是 180 * density = 360px , 既然我们设置成的180dp不能改变(也就是设置一个值,适配各种手机),那么我们只能改变这个density值。
  4. 换成公式就是: 180 * density = 360,那么density是多少。哈哈。没错是2 ,我们动态把density从 3变成2,是不是就符合了。
  5. 比如960X540 的手机,density是2 ,因为我们的Button宽度设置成了180dp,宽度为180 X 2 = 360px,超过了一半,我们只需要动态更改density满足 180X density = 270px即可,所以我们的density算出来是1.5。

那么density具体怎么得出来呢,很简单,我们刚才假设的是有一个按钮,占了屏幕的一半,那我们假设占了整个手机屏幕不就可以了。 设计图的宽度是360dp,而960X540的手机,只要540/360 = 1.5就可以得到,所以 density = 设备真实宽(单位px) / 360

代码语言:javascript
复制
if (orientation.equals("height")) {
        targetDensity = (appDisplayMetrics.heightPixels - barHeight) / 667f;
} else {
        targetDensity = appDisplayMetrics.widthPixels / 360f;
}
复制代码

所以本方案就是动态更改density以满足设计图方案。

结语:

emm.......大家轻喷即可。。。。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018年07月21日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言:
  • 1. 基础知识
    • 1.1 px
      • 1.2 inch(屏幕尺寸)
        • 1.3 dpi
          • 1.4 dp 和 density
            • 1.5 基础知识小结
            • 2 各类适配方案
              • 2.1 生成分辨率values文件夹
                • 2.2 生成values -sw 文件夹
                  • 2.3 百分比布局库
                    • 2.3.1 获取用户到底填了多少的百分比数值:
                    • 2.3.2 获取计算后的值并且赋值:
                  • 2.4 AutoLayout
                    • 2.5 动态更改density
                    • 结语:
                    相关产品与服务
                    对象存储
                    对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档