android屏幕适配

1、基础概念

屏幕尺寸

  • 含义:指的屏幕对角线的物理长度,单位一般采用英寸(1英寸≈2.53cm)

目前市面常见的有5.0、5.5、5.7等尺寸。

屏幕分辨率

  • 含义:屏幕纵横向的像素数量,一般描述成屏幕的"宽x高”=AxB
  • 例子:1080x1920,即宽度方向上有1080个像素点,在高度方向上有1920个像素点
  • Android手机常见的分辨率:320x480、480x800、720x1280、1080x1920等等

屏幕像素密度

  • 含义:每英寸长度包含的像素点数
  • 单位:dpi(dots per ich)
  • 例子:假设设备内每英寸有160个像素,那么该设备的屏幕像素密度=160dpi,这个160dpi在安卓中,也被当做一个基准的屏幕像素密度,此情况下1dp=1px,我们常用的px和dp互相转,用到的一个安卓api中的逻辑密度density在160dpi的时候,density=1,同理如果是320dpi,density=2;以此类推。
    • desity = 当前屏幕像素密度/160
    • **dp = px/desity **
/**
     * px转dp
     *
     * @param context
     * @param pxValue
     * @return
     */
    public static int px2dip(Context context, float pxValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (pxValue / scale + 0.5f);
    }

  • 安卓常用的像素密度


    image.png

dp 介绍

  • 含义:density-independent pixel,叫dp或dip,与终端上的实际物理像素点无关。
  • 例子:场景:假如同样都是画一条长度是屏幕一半的线,如果使用px作为计量单位,那么在240dpi手机上设置应为240px;在160dpi的手机上应设置为160px,二者设置就不同了;如果使用dp为单位,在这两种分辨率下,160dp都显示为屏幕一半的长度。

sp 介绍

  • 含义:scale-independent pixels,与dp类似,但是可以根据文字大小首选项进行放缩,是设置字体大小的御用单位。
  • sp 需要注意的事项
    • 1、当修改系统字体大小时,字体大小以dp为单位时,大小不变;

    • 2、当修改系统字体大小时,字体大小以sp为单位时,大小跟随变化;


      image.png
      • 为何会有这种差异性。
        参考文章关于设置文字大小,最终会调用下面这个方法:
public static float applyDimension(int unit, float value, DisplayMetrics metrics){
    switch (unit) {    
        case COMPLEX_UNIT_PX:        
            return value;    
        case COMPLEX_UNIT_DIP://------->>dp 基于 density      
            return value * metrics.density;  
        case COMPLEX_UNIT_SP://------->>sp  基于 scaledDensity     
            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;
}

从代码上看 dp 和 sp的差别就是 density 和 scaleDensity的区别,关于这两个字段在源码中可以看到注释scaledDensity会收到用户配置的影响,而density是基于屏幕dpi的,不会受到用户配置影响。
这也就是为什么设置sp单位时字体大小会受到用户配置系统字体的影响.

关于字体大小到底什么时候用dp 什么时候用sp

  • 使用sp会锁着系统文字的变化而呈现正相关变化,这个设计是符合安卓最初设计思想的,比如一些眼睛不好的人,可能就会把字体放大来看,如果app上的文字没有跟着变大,这可能就会影响用户体验。
    • 简单测试了下腾讯系的很多app多数页面都不会跟随系统字体大小变化,知乎会受到系统字体的影响。
    • 其中使用apktool简单查看了下qq的一些布局文件和dimens,发现字体大小,dp、sp都有引用。
  • 还有就是如果使用sp 写的话,一些注意事项。
    • 首先布局高度尽量不要写死,可以通过设置padding或者margin来控制高度,避免文字变大时候导致的展示不全的现象。
    • 相对来说对于这种适配,相对布局体验会更好一些,可以定位控件之间的相对关系,避免遮挡问题。

贴几张图:(系统设置文字变大)
比如下面的父控件限死高度的,导致展示不全、文字过大自动换行、没有设置相关关系导致的重叠。
整体来说需要根据实际需求具体调整,尽量在保证业务逻辑正常的情况下,调优展示效果。


image.png

屏幕尺寸大小、分辨率、像素密度之间的关系:

<span id="jump1">像素密度计算方法</span>

实际像素密度算法

image.png

计算结果不一致?

  • 问题:为什么按照上面算法获取的dpi值,和api方法获取的dpi不一致?
    比如我的手机360n5,在开发时我使用DisplayMetrics获取手机的densityDpi,这个densityDpi的大小为480,但是我在官网看见的却是PPI为401。他的分辨率为1080*1920,屏幕大小为5.5inch,按照上面的算法计算的话,大约就是401,因此可以肯定sdk上获取的dpi应该不是手机的真实dpi,那么android的dpi的计算方式又是怎样的?
  • dpi 和 ppi到底有何区别

DPI(dots per inch)和 PPI(pixels per inch)这两个措辞的差别,表面上看来只在于是在谈「dot」还是「pixel」。
* 但实际上 dot 可以指半调印刷的墨点,可以指喷墨打印的墨点,可以指扫描仪的采样点,可以指数字图像的最小单位(即 pixel),可以指屏幕的物理像素,可以指操作系统的抽象像素……在不同的语境下可以指不同的概念。
* 而 pixel 也可以指数字图像的数据 pixel,可以指屏幕物理像素,也可以指代操作系统的抽象像素……在不同语境下的意义也不同。
* 两者经常混用,所以关于dpi和ppi的区别,在不同的用途上面,意思也不同。

结论:在安卓机上面,我们可以理解ppi就是安卓机的真实像素密度,而dpi则是系统的一个内置的值,它接近真实值,但并不是真实的像素密度,它的存在就是为了换算dp、sp的 。

  • 安卓的dpi进一步解释
    • Android系统目录 /system/build.prop 里面有一行 ro.sf.lcd_density=480,这个就是dpi,这个值是可以变的,改变后通过sdk api拿到的dpi值就变化了,所以觉得dpi是个定义值,同一款设备,最大分辨率已经定了,而dpi其实并不是一定的,但是这个dpi决定了我们dp、sp和px的换算关系,这点很重要,也是屏幕适配的关键。其实,每部安卓手机屏幕都有一个初始的固定密度,这些数值是120、160、240、320、480,这些就是android为不同设备设定的系统密度。 得到实际密度以后,一般会选择一个最近的密度作为系统密度,系统密度是出厂预置的,如440dpi的系统密度就是和它最接近的480dpi;如果是330dpi的设备,它的系统密度就是320dpi。但是,现在很多手机不一定会选择这些值作为系统密度,而是选择实际的dpi作为系统密度,这就导致了很多手机的dpi也不是在这些值内。例如小米Note这样的xxhdpi的设备他的系统密度并不是480,而是它的实际密度440。
    • dp到px的转换公式:px = dp * (dpi / 160)

2、适配方案

布局适配

多套布局

针对不同的屏幕尺寸,如果差异过大的话,就需要考虑多套布局的方式来处理

  • 首先需要针对不同屏幕写布局
  • 然后需要使用尺寸限定符
    • 尺寸限定符相关简要介绍:

      • 限定符示例:


        image.png
      • 如图示上面同名文件夹后面标注都属于限定符

      限定符使用方法:只需要用横线加限定符的方式即可使用,xx-限定符。

      • 常见限定符:
      限定符(mdpi,tvdpi,hdpi)可以帮助我们判断屏幕密度 
      限定符(land,port)可以帮助我们区分屏幕横竖屏状态 
      限定符(en,fr…)可以帮助我们语言和地区 
      限定符(v3,v4…)可以帮助我们区分安卓版本 
      等等
      

      具体参考文章:http://blog.csdn.net/wzy_1988/article/details/52932875

      • 关于屏幕适配我们需要重点关注的限定符
        • sw(n)dp:(最窄边限制符,不受屏幕方向的影响)
          屏幕最小尺寸限定符:就是屏幕可用区域的最小尺寸,是指屏幕可用高度或宽度的最小值,例如,如果你的布局在运行时需要的屏幕最窄边是600dp,则你可以利用这个限定符创建布局资源目录res/layout-sw600dp.只有当屏幕的最小宽度或最小高度大于等于600dp时,系统才会使用这些布局文件或者资源文件
        • w(n)dp:(屏幕最小宽度限定符,受到屏幕方向的影响)
          指定资源使用时需要的最小宽度.当屏幕方向发生变化时,系统会调整这个值,使其始终为你UI显示的宽度.
      • 怎么计算限定符应该是多少dp
        • 计算方法 dp = px/(当前屏幕像素密度/160 )
        • 范例:
          • 密度:480 dp / xxhdpi / 3.0x 屏幕分辨率:1080 x 1920 px 屏幕尺寸:2.8" x 5.0" / 5.7 英寸
          • 1080px /(480/160) = 360dp,所以对应文件夹后缀应该是xxx-w360dp

定义布局时候注意事项

  • 多使用相对关系定义控件之间的关联
  • 使用"wrap_content"、"match_parent"和"weight“来控制视图组件的宽度和高度
  • 布局中引用的dp sp一定使用引用关系,便于后期可能存在的针对性修改。

一般提前生成一套dimens文件,例如0-400dp,0-30sp,方便使用。

dp sp 适配

dp sp能够让同一数值在不同的分辨率展示出大致相同的尺寸大小。但是当设备差异较大的时候,就无能为力了。适配的问题还需要我们自己去做,一般是生成带标识符的多套dimens文件。

  • 适配方案一:dp方法:{"320","360", "384", "400", "411", "533", "640", "720", "768", "820"};
    • 缺点:如果没有默认的dimens.xml,那就黄昏依斜阳了,还好有提供。
    • 优点:没有枚举全部的item,可以省一些apk空间;有字体sp的适配。
  • 适配方案二:px百分比方法
    • 优点:针对性适配效果更精确,体验更好。
    • 缺点:屏幕px的种类远多于dp的种类,文件数量多;程序for循环枚举item项,有部分用不上的px项;width和height都适配了,给控件写width尺寸时要用dimens_x.xml里面的变量值,写height尺寸时需要用dimens_y.xml里面的变量值。

图片适配

使用点九图

  • 9patch图片的作用就是在图片拉伸的时候保证其不会失真,让图片在指定的位置拉伸和在指定的位置显示内容,这样图片的边边角角就不会出现失真了。

范例:


image.png

具体参考:点九图的制作

多套图片

  • 传统方式切多套图片,放在对应标识符的文件夹里面(缺点:会导致apk包过大)
  • 只放一套xhdpi的图(Android会根据屏幕密度自动选择对应的资源文件进行渲染加载)
    比如说,SDK检测到你手机的分辨率是320x480(dpi=160),会优先到drawable-mdpi文件夹下找对应的图片资源;但假设你只在xhpdi文件夹下有对应的图片资源文件(mdpi文件夹是空的),那么SDK会去xhpdi文件夹找到相应的图片资源文件,然后将原有大像素的图片自动缩放成小像素的图片,于是大像素的图片照样可以在小像素分辨率的手机上正常显示。
    具体请看http://blog.csdn.net/xiebudong/article/details/37040263
    所以理论上来说只需要提供一种分辨率规格的图片资源就可以了。
    一般选择xhdpi的,这一套可以适配市面上大多数手机,向下缩放,向上扩展表现都不会有太大问题。

注意事项:

  • 通过apktool简单分析几个知名的app,看看别人到底使用了几套。
    • 微信基本都是xxh为主了
    • 拼多多还是xh xxh都有
  • 随着安卓设备的发展,分辨率越来越高,可能后期xxh更为合适
    • 可以同时放入xxh的图标,避免后期加入的麻烦。
    • 为了防止包体过大,打包时候可以临时删除xxh文件夹
    • 也可以推广期一套图片资源,用户主动升级时候可以升级携带xxh资源,体验可能更好一些。

代码动态适配

某些布局要求严格宽高比的地方,可以考虑代码的动态设置宽高;或者为了保持美观又防止过高的一些pop、dialog也需要代码动态按照当前屏幕比例动态设置宽高。

  • 范例
view.getLayoutParams().height = Utils.getRealHeight(mContext, 720, 300);

  /**
     * 获取控件准确的高度(针对满屏的情况)
     *
     * @param context
     * @param width   宽度(可以是相对值,仅仅用来计算宽高比例)
     * @param height  高度(可以是相对值,仅仅用来计算宽高比例)
     * @return 真正的高度
     */
    public static int getRealHeight(Context context, int width, int height) {
        //宽高比
        float aspectRatio = (float) width / (float) height;
        return (int) (Utils.getScreenWidth(context) / aspectRatio);
    }

 /**
     * 根据当前宽度基准算真实高度
     *
     * @param context
     * @param height     宽度基准下的高度
     * @param totalWidth 宽度基准
     * @return
     */
    public static int getRealHeightWithBenchmark(Context context, float height, float totalWidth) {
        return (int) ((Utils.getScreenWidth(context) / totalWidth) * height);
    }


  • 范例2:(针对自适应的一些布局,防止过高情况)
//布局渲染完成后回调设置,防止获取不到宽高度
ViewTreeObserver vto = mRecyclerView.getViewTreeObserver();
        vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                mRecyclerView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                if (mRecyclerView.getHeight() > Utils.getScreenHeight(mContext) / 2) {
                    mRecyclerView.getLayoutParams().height = Utils.getScreenHeight(mContext) / 2;
                }
            }
        });

3、实际适配过程

参考文章:http://blog.csdn.net/wangwangli6/article/details/63258270

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容