美文网首页程序员Android技术知识
小猪浅谈Android屏幕适配

小猪浅谈Android屏幕适配

作者: coder_pig | 来源:发表于2017-10-10 16:16 被阅读667次

    tags: Tutorial


    引言

          国庆前在微信群里看到有人在问Android屏幕适配的问题,凑巧自己最近时间
    略有闲暇,索性来谈谈Android中屏幕适配相关的一些内容吧,鄙人才疏学浅,
    所说的都是自己认知范围以内的,不可能面面俱到,如有疏漏不妥之处,还请
    不吝指出,谢谢!配上最近被玩坏的Gif~


    Android中的单位与名词


    1.相对单位与绝对单位

    对于计量单位,人们日常习惯性分为「相对单位」与「绝对单位」两类,前者
    根据不同的情景表现出不同的大小,比如Android里的dp,px与sp等;而后者则是
    制定了一个标准,cm(厘米),写死了,就那么多,1cm什么情况下都是一样大。

    说到这个绝对单位,顺带提下两个单位:

    • in(英寸)1 in = 2.54cm
    • pt(磅,印刷行业常用单位):1 pt = 1 / 72 in

    2.px (pixel,像素)

    就是一个个的像素点图像的最小组成单元


    3.dp (dip,密度无关像素)

    Density-independent pixel,抽象意义上的像素与设备的实际物理像素点无关
    可以保证在不同的像素密度的设备上显示相同的效果,也是Android独有的长度单位

    1dp表示在屏幕像素密度为160dpi的屏幕上1dp = 1px
    类推,在320dpi上,1dp = 2px,不难看出这样的转换公式:px = dp * (dpi / 160)


    4.sp (sip,独立比例像素)

    scale-independent pixel,字体大小专用单位会根据系统设置的字体大小进行
    缩放
    ,推荐使用12sp以上的字体(12sp以下太小),不推荐用奇数和小数,容易造成
    精度丢失问题。

    Tip

    尽管官方建议我们字体都用sp作为单位,但是sp字体会根据系统设置的字体大小进行
    缩放,假如用户修改了系统字体大小,可能会导致我们APP的UI受到影响,比如字体
    大了,然后各种显示不全,个人还是建议使用dp来做为字体单位


    5.手机外观尺寸

    整个手机的尺寸不止屏幕,一般在中关村或者其他评测网站都可以查到。
    一般是用mm为单位,依次为长 x 宽 x 厚度,比如我的老掉牙的moto x 的尺寸就是:
    159.3mm × 83mm × 10.1mm。


    6.屏幕尺寸

    屏幕对角线的长度,单位是英寸,计算公式如下:

    其实就是勾股定理求对角线长度,比如长4寸,宽3寸的手机,他的屏幕

    尺寸计算:

    7.分辨率(Resolution)

    屏幕竖直方向与水平方向的像素个数,比如:1280*720 就说明屏幕的
    竖直方向上有1280个像素点,而水平方向上有720个像素点,单位px。


    8.dpi与ppi

    • dpi:dot per inch,点密度每英寸多少个点,一般用作表示印刷品点密度;
    • ppi:pixels per inch,像素密度每英寸所包含的像素数目
      一般用作表示显示设备的点密度。

    两者的值近乎相等,不用过于纠结!这个值的计算公式如下:


    9.density(屏幕密度)

    这个单位感觉是用来表示不同dpi的倍数关系,计算公式:density = dpi/160


    10.常用单位转换工具类

    public class DensityUtil {
        public static int dp2px(Context context, float dpValue) {
            final float scale = context.getResources().getDisplayMetrics().density;
            return (int) (dpValue * scale + 0.5f);
        }
    
        public static int px2dp(Context context, float pxValue) {
            final float scale = context.getResources().getDisplayMetrics().density;
            return (int) (pxValue / scale + 0.5f);
        }
    
        public static int px2sp(Context context, float pxValue) {
            final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
            return (int) (pxValue / fontScale + 0.5f);
        }
    
        public static int sp2px(Context context, float spValue) {
            final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
            return (int) (spValue * fontScale + 0.5f);
        }
    }
    
    

    11.获取屏幕尺寸与密度的三种方法

    public class GetScreenParameter {  
        //方法一:已过时,可使用,但不建议使用  
        public static void getResolution1(Context mContext) {  
            Display mDisplay = ((Activity) mContext).getWindowManager()  
                    .getDefaultDisplay();  
            int W = mDisplay.getWidth();  
            int H = mDisplay.getHeight();  
        }  
        
        //方法二:通过getWindowManager来获取屏幕尺寸的  
        public static void getResolution2(Context mContext) {  
            DisplayMetrics mDisplayMetrics = new DisplayMetrics();  
            ((Activity) mContext).getWindowManager().getDefaultDisplay()  
                    .getMetrics(mDisplayMetrics);  
            int W = mDisplayMetrics.widthPixels;  
            int H = mDisplayMetrics.heightPixels;  
            // 屏幕密度(0.75 / 1.0 / 1.5)        
            float density = mDisplayMetrics.density;
            // 就是屏幕密度 * 160而已,屏幕密度DPI(120 / 160 / 240)
            int densityDpi = mDisplayMetrics.densityDpi; 
        }  
        
        //方法三:通过getResources来获取屏幕尺寸的,大部分用这个
        public static void getResolution3(Context mContext) {  
            DisplayMetrics mDisplayMetrics = new DisplayMetrics();  
            mDisplayMetrics = mContext.getResources().getDisplayMetrics();  
            int W = mDisplayMetrics.widthPixels;  
            int H = mDisplayMetrics.heightPixels;  
            float density = mDisplayMetrics.density;   
            int densityDpi = mDisplayMetrics.densityDpi; 
        }  
    }  
    

    Tip(可以不看,现在基本不会用这么小屏的手机了):

    对于屏幕密度很低的小屏手机,比如240320,计算出来的尺寸,可能为:320427
    原因是:没有设置多分辨率支持的话,Android系统会将240x320的低密度(120)尺寸
    转换为中等密度160dpi对应的尺寸,如果你想获得正确的物理尺寸,需要在
    AndroidManifest.xml里添加下述代码:

    <supports-screens  
        android:smallScreens="true"  
        android:normalScreens="true"  
        android:largeScreens="true"  
        android:resizeable="true"  
        android:anyDensity="true"/>
    

    Android适配准备


    1.什么是Android适配

    答:因为Android系统的开放性,很多厂商都喜欢对Android系统和硬件进行个性化定制,
    以达到他们想要的样子,这种结果带来的「Android系统」和「手机屏幕」的碎片化问题
    只对市场占用率较高的720和1080进行适配显然是不够的,为了让我们的Android应用在各种
    各样的手机上保证界面效果一致,各种手机屏幕的适配显得非常重要,也是每个开发者
    为之头痛的问题。


    2.Android中的六种通用屏幕密度

    屏幕密度 范围(dpi) 标准分辨率 dp与px 图标尺寸
    ldpi(QVGA) ~ 120 240 * 320 1dp=0.75px 36 * 36
    mdpi(HVGA) 120 ~ 160 320 * 480 1dp=1px 48 * 48
    hdpi(WVGA) 160 ~ 240 480 * 800 1dp=1.5px 72 * 72
    xhdpi(720P) 240 ~ 320 720 * 1280 1dp=2px 96 * 96
    xxhdpi(1080p) 320 ~ 480 1080 * 1920 1dp=3px 144 * 144
    xxxhdpi(2K) 480 ~ 640 1440 × 2560 1dp=4px 192 * 192

    Tip:图标大小 = px数 * 4 * 12


    3.关于UI设计稿的适配

    我们可以通过友盟的 全域罗盘 知道当前国内的移动设备使用情况,
    以此了解需要适配的趋势,以八月份的统计为例,截取屏幕尺寸与分辨率占比
    拍醒前五的数据如下:

      

    更多详细信息可自行查看,从上面两个表格不难看出这样的趋势:

    5.5寸5寸 屏幕尺寸市场占比最高,而分辨率:1920x10801280x720
    依旧是当前主流

    也就是说让设计师按照720p和1080p出两套设计稿就可以适配大部分的
    设备了,但是不同的公司因为人员或者时间等外部因素,可能有这几
    种情况:

    • 三套:720p,1080p,1440p,这是最理想的情况;
    • 两套:720p,1080p,主流,大部分公司都或走两套;
    • 一套:720p,又或者和iOS共用一套 750x1334,可以当做720p的设计稿
      来编写UI,尽管比例和接近16:9,但是还是有些偏差,需要Android对UI进行微调!

    UI能做的基本就到这里了,至于其他分辨率的手机则需要我们开通过
    相关的手段来适配了。


    Android适配开始


    1.最简单一些适配技巧

    先来说几个烂大街的适配技巧吧

    1) 使用dp而非px

    dp是像素无关的,而在实际使用中1dp大约等于1/160 in,比如一个160dp * 160dp
    的控件,在大多数的屏幕上都能保持1 in * 1 in 的大小。

    但是,并不是能解决所有问题的,以下两点要注意:

    • 1.实际效果还是会有些差距的,仅仅是相近而已;
    • 2.当设备的物理尺寸存在差异的时候,dp就显得无能为力了。
      比如,为4.3寸屏幕准备的UI,运行在5.0寸的屏幕上,很可能在
      右侧和下侧存在大量的空白;而5.0寸的UI运行到4.3寸的设备上,很可能显示不下。

    2) 少写固定尺寸

    少写固定尺寸,而使用 wrap_content, match_parentweight 权重 」


    3) 使用相对布局,不要使用绝对布局

    常识,而且绝对布局基本退出历史舞台了,可以忽略...


    4) 自动拉伸的.9图

    常识,.9图的作用是:拉伸的时候特定的区域不会发生图片失真,而不失真的区域
    可以由我们自己绘制,从而实现图片适配。


    5) 使用shape代替纯色图片

    常识,一些纯色的矩形,圆角,圆都可以通过编写shape文件来替换,比起png,
    xml文件小太多。


    6)使用SVG矢量图替换位图

    可能有些朋友对SVG矢量图有些陌生,其实和普通的位图最大的区别就是:
    SVG是通过「XML文件」来定义一个图形,通过一些特定的语法和规则来绘制
    出我们所需的图像,而不是位图那样通过存储图像中每一点的像素值来保存
    与使用图形。

    SVG是已经定义好怎么画这个图,需要的时候再去画,因为是按照特定的语法
    和规则,理论上支持任何级别的缩放,而且不会失真,相比起多套同样的位图
    文件,方便太多。

    矢量图虽好,但是有几点要注意的:

    • 1.适用于Android 5.0以上,尽管官方有兼容包,低版本还是会有些问题的!
    • 2.不适合细节过于复杂的图片
    • 3.因为是用到的时候才画,所以加载图片所消耗的时间和资源可能会增加。

    至于怎么用这个矢量图,你可以让美工在PS里把图片导出为SVG/PSD格式,然后
    AS里右键drawable文件夹:

    选中本地文件,确定

    然后AS会帮你自动生成一个矢量xml文件,比如我的一个tab图标

    右侧可以看到预览图:

    然后要用到图片的地方直接引用即可。
    至于SVG的规则以及如何自行编写不在本节重点,有兴趣的可自行搜索用法~


    2.多套资源文件的套路

    常用的套路依旧不能解决大部分的屏幕适配问题,Android给我们提供了
    备用资源这个东西,详细可见官网:提供备用资源
    简单说就是按照规范,创建屏幕对应的资源文件夹和资源文件,
    Android会自动去加载对应文件夹里的资源的值,可能表述得有些不清晰,
    举个简单的例子:

    比如我们xxhdpi,padding为3dp,而在xxxhdpi下的padding为5dp
    我们就可以创建一个对应的values文件夹和dimens.xml文件

    相信你看到这里就明了,官网写的这个也就清晰了

    而这个qualifier限定符怎么拼接可以看官网,或者看下面的简表:

    看到这里,你可能会有问题,我要写多少套?
    讲真,我也不知道,只能说看需要,但是想跟你说限定符里一个很常用的:smallestWidth
    屏幕区域的最小尺寸,比如 values-sw320dp,只有在屏幕宽度不小于
    320dp的才会使用这个文件夹里的dimens.xml文件,我们还可以另外建立一个
    values-sw360dp,然后320dp那个文件夹只有在屏幕宽度在320dp到360dp
    内才回去调用了,drawable,layout等资源文件也是同样的套路。


    3.使用脚本对长度按照不同分辨率进行比例转换

    就是拿屏幕宽度,去除以360,得出比例去乘以对应的dp值
    比如:sw720dp里一个单位的1 dp = 2dp,而sw800dp里的1 dp = 2.22dp:

    Github地址:https://github.com/mengzhinan/PhoneScreenMatch

    具体的自己看文档和代码吧,可能有的疑问就是不是win电脑执行不了bat文件
    的问题,比如mac,直接命令行,cd到 PhoneScreenMatch/app/src/main
    目录下,键入下述命令即可:

    java -jar screenMatchPX.jar
    

    项目中还提供了一个px适配的,分开宽高做等值缩放的,对于长宽比很奇葩
    的机子可以试试。当然这种适配方式带来的最明显的问题就是资源文件增加
    的问题,这需要自行权衡。

    类似的方案还有:https://github.com/paulyung541/EasyScreen


    4.[过时] 百分比布局库支持库

    这个库API 26.0.0 后的版本已经弃用了,在官方仓库可以看到

    [DEPRECATED in support lib v26] you should now use ConstraintLayout widget

    如果你想使用百分比来编写建议使用约束布局 ConstraintLayout
    所以android-percent-support怎么用并不讲,想通过看看源码的方式
    来了解百分比的实现套路,没兴趣的可以跳过了,或者看其他关于
    百分比布局支持库的其他使用扩展文章:

    源码过一过 (流程参照CSDN张鸿洋的博文)

    lib库里的东西不多,就三个核心的类而已

    如果让我们来做套路可能是:

    • 通过LayoutParams获取布局中设置的与百分比有关的属性
    • 父布局拿到这些属性值后进行动态计算,比如宽*宽占百分比
    • 子控件调用measure(计算后值)进行绘制。

    看下代码具体是怎么做的,首先是获取Layout里子控件的属性,打开
    PercentFrameLayout,重写了generateLayoutParams方法,
    然后新建了一个LayoutParams对象

    看看这个LayoutParams里卖的什么药:

    通过PercentLayoutHelpergetPercentLayoutInfo方法构造一个
    PercentLayoutInfo对象,而getPercentLayoutInfo里做的事则
    是把attrs里的东东都拿出来,然后塞到PercentLayoutInfo对象用于返回:

    而这个PercentLayoutInfo里定义了一堆百分比相关的属性,
    还有一些方法,字面上大概可以知道是设置LayoutParams相关
    的方法,就是把onMeasure里的逻辑操作抽取到了这里。

    好的,属性拿到了,接着就是跟onMeasure方法了,调用了
    PercentLayoutHelper对象里的adjustChildren方法

    方法里做的东西也比较简单,拿到布局的宽高,然后
    循环遍历布局里的子控件,如果params是百分比类型的
    取出PercentLayoutInfo里info,然后去设置宽,高和margin。

    再跟下fillLayoutParams方法,保存原本宽高后(后面用来重置),重新设置宽高,
    fillMarginLayoutParams也是类似,百分比的基本套路就已经完了。

    不过项目中还多了两个细节,计算值过小的情况以及重置原布局尺寸
    onMeasure最后还调用handleMeasuredStateTooSmall方法,而方法做的
    事是出去容器中所有的子控件,如果params是百分比类型的,则调用
    shouldHandleMeasuredWidthTooSmall方法判断值是不是太小,太小则
    把宽或高设置为ViewGroup.LayoutParams.WRAP_CONTENT(图中 = -2那里)
    needsSecondMeasure设置为true,代表需要二次重绘。

    重置布局尺寸的代码则是写在onLayout方法里(onMeasure后会回调)

    一样是遍历子控件,然后把参数都重置为之前存mPreservedParams里的值,
    而这个值是布局里设置的0dp。(感觉没什么用处...)

    因为onMeasure的关键代码都写到Helper类里,扩展一个百分比的线性
    布局也很简单,拷贝下PercentFrameLayout改点东西就好。
    另外,因为依赖于父容器,导致ScrollView,ListView等容器内高度无法使用百分比
    而鸿洋还另外对官方的百分比进行了扩展,写了一个android-percent-support-extend
    有兴趣可以移步到他的博客阅读:Android 增强版百分比布局库 为了适配而扩展


    5.[作者停止维护] AndroidAutoLayout

    又是鸿洋大神的库,不过作者已经不维护了,使用之前还是三思!!!
    博客链接:http://blog.csdn.net/lmj623565791/article/details/49990941

    大概套路是

    定好设计稿尺寸,直接选择对应分辨率的预览,AndroidAutoLayout 库会根据
    占屏比例,自动计算转换当前屏幕下适配的大小。加载布局其实是把各自的
    Layout都转换为对应的AutoLayout,从而不用在所有的xml中进行更改。

    思路很gay,但是也有一些弊端,在onMeasure的时候进行数值计算,存在性能问题
    扩展性差,用到的ViewGroup都需要自行对AutoLayout进行扩展。
    最重要的是:

    issues较多,而且作者不维护,应用到实际项目中还需自行权衡...


    6.ConstraintLayout 约束布局

    使用约束的方式来指定各个控件的位置和关系的,可以看做更加强大的
    RelativeLayout,通过图形化界面拖拽的方式来编写界面,当然也可以
    直接用XML进行编写,毕竟拖拽的背后也是XML来实现的,不过属性比较
    多,直接编写显得有些繁琐,建议还是拖拽后自己在手动修改参数

    ConstraintLayout约束布局除了支持百分比外,相比传统布局一层
    套一层的嵌套布局结构,编写的XML元素层次结构更简单(一个外层
    ConstraintLayout包全部),而且性能更优。
    至于问题的话,可能就是布局中的元素过于复杂时,拖拽可能会有少许问题;

    其他的话暂时没发现,关于ConstraintLayout与传统布局方式的性能对比可见:
    解析ConstraintLayout的性能优势

    而关于图形化拖拽的,郭霖大神有篇详细讲解的博文:
    Android新特性介绍,ConstraintLayout完全解析

    而关于属性讲解和具体实例的,可以翻看鸿洋的博文:
    ConstraintLayout 完全解析 快来优化你的布局吧

    附:记下关键点,方便自己日后回忆

    • Inspector:右侧Properties区域的上半部分,竖直和水平的轴用于确定
      位置;图中的四个16确定的是间距;中间的四个 >>> 的箭头确定的是大小
      有三种可选的模式: wrap_content、 固定值、 any size
      用于填充满当前控件的约束规则,和match_parent是不同的!

     

    • Guidelines:参照线 ,就是弄个线给别的控件提供约束,有垂直和水平
      两种,用来实现一些百分比的效果很赞,比如下图这种两个控件并排居中的布局:

       
    • 自动添加约束:拖拽控件后,一个个去添加约束非常繁琐,ConstraintLayout中
      支持自动添加约束,有两种自动添加约束的方式:
      AutoConnect:默认关闭,启用后拖拉控件到布局里会自动生成约束

      Infer Constraints:控件拖拉得差不多后,点击后会为所有控件生成约束

      当然,这两种自动生成的约束可能有些问题,我们可以自己另外调整下~

    7.等比例缩放的另一种实现方案

    同样是等比例缩放的套路,原文可见:一种粗暴快速的Android全屏幕适配方案

    上面也说了AndroidAutoLayout在onMeasure中进行数值运算可能会有性能问题。
    而这个方案则是规避了这个问题,直接在Android进行长度计算的时候就进行换算;

    系统长度计算的入口是TypedValue里的applyDimension方法:

    public static float applyDimension(int unit, float value, DisplayMetrics metrics){
        switch (unit) {
            case COMPLEX_UNIT_PX:
                return value;
            case COMPLEX_UNIT_DIP:
                return value * metrics.density;
            case COMPLEX_UNIT_SP:
                return value * metrics.scaledDensity;
            case COMPLEX_UNIT_PT:
                return value * metrics.xdpi * (1.0f/72);
            case COMPLEX_UNIT_IN:
                return value * metrics.xdpi;
            case COMPLEX_UNIT_MM:
                return value * metrics.xdpi * (1.0f/25.4f);
            }
            return 0;
        }
    }
    

    而作者选择比较冷门的pt单位,从而避免对正常的px,dp,sp造成影响,
    PT的计算为:value * metrics.xdpi * (1.0f/72),我们可以动前面
    metrics.xdpi,这个值应改为:缩放比例7272的原因是想转px,
    metrics可以通过context.getResources().getDisplayMetrics()
    拿到。作者另外写了一个Helper类,通过构造方法传入设计稿的宽度除以
    当前设备的分辨率得出比例,然后乘以72即可得出对应分辨率下的尺寸。

    调用也很简单,直接在Application类启用即可:

    new RudenessScreenHelper(this, 750).activate();
    

    等比缩放套路换汤不换药,不过很机智的规避了复杂的计算问题,想法是挺赞的,
    有兴趣的可以试试。


    小结


          在本节中,笔者对于自己所知的Android原生适配套路都一一进行了复述,相信会对
    你在开发中的屏幕适配有所帮助,当然具体要怎么适配还是看需求吧,比如我在的
    小作坊,也是只是适配720p,2333;当然有其他的套路也欢迎告知,万分感激~
    (PS:说来惭愧,本来想着国庆能发的,后面因为各种琐事拖到今天...)


    相关文章

      网友评论

        本文标题:小猪浅谈Android屏幕适配

        本文链接:https://www.haomeiwen.com/subject/kutbyxtx.html