看完不会Android屏幕适配我跪搓板

本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布

1. 序言

  • 屏幕适配哪家强:
    ① 到底哪一种屏幕适配最合适,仁者见仁智者见智.
    ② 开始我推荐dimens基于px的适配,后来我推荐dimens dp的适配,而如今我推荐今日头条适配(修改手机的设备密度 density)。
  • 效果图多大才更好:
    推荐1倍效果图,即采用 720 * 360 大小( 1280 *720:两倍图 \ 1920 * 1080: 三倍图),最主要的原因就是1px = 1dp,效果图标多大的px,布局就写多大dp。

2. 屏幕各项参数

  • 像素 - px:
一个小黑点就是像素。
  • 尺寸:
屏幕的对角线的长度。
  • 分辨率:
整个屏幕一共有多少个点,也就是像素。
  • 像素密度 - dpi:
1. 每英寸中的像素数。
2. 假如设备分辨率为320*240,屏幕长2英寸宽1.5英寸,dpi=320/2 = 240/1.5 =160。
3. 对应于DisplayMetrics类中属性densityDpi的值。
4. 当然这种宽和高的dpi都相同的情况现在已经很少见。
  • 密度 - density:
1. 每平方英寸中的像素数。
2. density = dpi / 160 。
3. 对应于DisplayMetrics类中属性density的值 。
4. 可用于px与px与dip的互相转换 :dp = px / density 。
  • 设备独立像素 - dip - dp:
- 不同设备有不同的显示效果,不依赖像素。
- dp = px / density
- dp = px / (dpi / 160) 
- dpi(像素密度)为160 的设备上1dp = 1px。
  • 放大像素 - sp:
用于字体显示。
  • dp转px、px转dp
public class Dp2Px {
    public static int dp2px(Context context, int dp) {
        return (int) (dp * context.getResources().getDisplayMetrics().density + 0.5);
    }

    public static int px2dp(Context context, int px) {
        return (int) (px / context.getResources().getDisplayMetrics().density + 0.5);
    }
}
说明:0.5 是为了避免损失精度。
  • 常见设备的dp、px、density的关系

① ldpi:

density:0.75
分辨率:240*320 
关系:dp = px / 0.75

② mdpi:

density:1
分辨率:320 * 480
关系:dp = px / 1

③ hdpi:

density:1.5
分辨率:480 * 800
关系:dp = px / 1.5

④ xhdpi:

density:2.0
分辨率:720 * 1280 
关系:dp = px / 2

⑤ xxhdpi:

density:3
分辨率:1080 * 1920 
关系:dp = px / 3
  • 代码获取参数值:
//以1280*720为基准:
//获取手机屏幕的宽和高
int widthPixels = getResources().getDisplayMetrics().widthPixels;
int heightPixels = getResources().getDisplayMetrics().heightPixels;
//density
float density = getResources().getDisplayMetrics().density;
//dpi
int densityDpi = getResources().getDisplayMetrics().densityDpi;
//1dp = 多少px
int px = Dp2Px.dp2px(this, 1);
//1px  = 多少dp
int dp = Dp2Px.px2dp(this, 1);
04-30 23:29:15.606 2484-2484/com.example.testscreen E/MainActivity: widthPixels: 720
04-30 23:29:15.606 2484-2484/com.example.testscreen E/MainActivity: heightPixels: 1280
04-30 23:29:15.606 2484-2484/com.example.testscreen E/MainActivity: density: 2.0
04-30 23:29:15.606 2484-2484/com.example.testscreen E/MainActivity: densityDpi: 320
04-30 23:29:15.606 2484-2484/com.example.testscreen E/MainActivity: px:2
04-30 23:29:15.606 2484-2484/com.example.testscreen E/MainActivity: dp:1
  • 详解屏幕尺寸、分辨率、像素密度三者关系:


    屏幕尺寸、分辨率、像素密度三者关系.png
  • 举例说明:屏幕分辨率为:1920*1080,屏幕尺寸为5吋的话,那么dpi为440:


    example.png

3. 适配分类

3.1. 图片适配

  • 在AndroidStudio的资源目录res下有五个层级图片文件夹,分别用来存放不同分辨率的图片:
① drawable-ldpi :低分辨率(用的少了,一般不再用)
② drawable-mdpi:中分辨率
③ drawable-hdpi:高分辨率
④ drawable-xdpi:较高分辨率
⑤ drawable-xxdpi:超级高分辨率
⑥ drawable-xxxhpi:顶级分辨率
  • 在对应的文件夹下放置不同分辨率的图片就可以很好的对图片进行适配。
  • 随着屏幕越来越大,推荐xxdpi的一套切图,这样就可以向下和向上兼容,节省资源。
  • 对于图标使用svg格式,对于图片仍然使用png,svg的图标大小约是png的1/4,在很大的项目中,图标有很多,这个时候svg的优势就凸显无疑了。

3.2. 布局适配

  • 不同分辨率,创建不同的布局文件夹:
① layout-800 * 480
② layout-1280 * 720
......
  • 手机会根据分辨率去找设定的不同大小的layout的布局。
  • 实际开发中这种使用的情况非常少,因为占用太多资源,慎用。

3.3. 权重适配

  • 当两个或者更多布局占满屏幕宽或高的时候,子布局可以使用权重适配,常见于LinearLayout线性布局中。

3.4. 尺寸适配(dimens百分比适配)

  • 这种适配的原理就是将宽和高通过百分比的原理分割为若干份:
以400 * 320为基准,以宽举例:宽分为了320份:每份1.0px,那1280*720的宽的每份就是2.25px。
  • 创建values-400*320文件夹,创建dimens.xml文件,设置代码如下:
<resources>
<dimen name="x2">2.0px</dimen>
</resources>
  • 创建values-800*480文件夹,创建dimens.xml文件,设置代码如下:
<resources>
<dimen name="x2">3.0px</dimen>
</resources>
  • 代码中使用:
R.dimen.x2
  • 布局中使用:
@dimen/x2
  • 详解dimens-px 适配:
    原理:根据市面上手机分辨率的占比分析,我们把1280和720设定为一个基准,然后其他分辨率根据这个基准做适配。基准的意思(比如320*480的分辨率为基准)是:
    ① 宽为320,将任何分辨率的宽度分为320份,取值为x1到x320
    ② 长为480,将任何分辨率的高度分为480份,取值为y1到y480
    例如对于800 * 480的宽度480:
<?xml version="1.0" encoding="utf-8"?>
<resources><dimen name="x1">1.5/dimen>
<dimen name="x2">3.0px</dimen>
<dimen name="x3">4.5</dimen>
<dimen name="x4">6.0px</dimen>
<dimen name="x5">7.5px</dimen>

可以看到x1 = 480 / 基准 = 480 / 320 = 1.5 ;它的意思就是同样的1px,在320/480分辨率的手机上是1px,在480/800的分辨率的手机上就是1*1.5px,px会根据我们指定的不同values文件夹自动适配为合适的大小。

  • 自动生成 dimen-px 文件夹
    ① 首先下载jar包:
    链接: http://pan.baidu.com/s/1crbwwI 密码: dxwa
    ② 其次解压查看jar运行说明.txt文件,定制以1280/720为基准的分辨率,操作方法:
    在你下载后的文件夹里面 按住Shift+鼠标右击进入命令行对话框,输入
    java -jar autolayout.jar 720 1280就会自动生成res文件夹(默认的是以1080/1280为基准,所以需要自己设置),假如你觉得这些value文件夹里面没有你想要的分辨率可以在制定基准分辨率的同时,添加额外的分辨率(比如400/600),输入java -jar autolayout.jar 720 1280 400,600,假如想多添加几个额外的分辨率(又想添加500*700)只需把额外的分辨率用下划线隔开即可,输入java -jar autolayout.jar 720 1280 400,600_500,700
    ③ 接着把res里面的value文件夹放到res下面即可
    ④ 标注图假如宽50px,高80px,我们只需要把宽高写为@dimen/x50,@dimen/y80即可
  • 已经生成的dimen-px文件夹地址:
链接:https://pan.baidu.com/s/1jLXpQXs898hzIensZKV7gg 密码:6xgs

3.5 . 代码适配

Button button = (Button) findViewById(R.id.bt_main_button);
//获取手机屏幕的宽和高
int widthPixels = getResources().getDisplayMetrics().widthPixels;
int heightPixels = getResources().getDisplayMetrics().heightPixels;
//给button设置宽和高
ViewGroup.LayoutParams layoutParams = button.getLayoutParams();
layoutParams.width = widthPixels / 2;
layoutParams.height = heightPixels / 2;
button.setLayoutParams(layoutParams);

举例:资源是图片宽和长不定的一些图片,需求是宽度一定,对图片进行展示,思路就是:根据图片的宽高比,以及效果图的高度,来设置ImageView控件的长度。

4. demins 基于px和dp适配的缺点

  • dimen 基于px:
    宽和高都经过百分比的计算得到对应的值,通过手机分辨率进行适配,个人看来存在的问题是:
    第一,Android不同分辨率的手机实在太多了,可能你说主流就可以,的确小公司主流就可以,淘宝这种App肯定不能只适配主流手机。
    第二,控件在设计图上显示的大小以及控件之间的间隙在小分辨率和大分辨率手机上天壤之别,你会发现大屏幕手机上控件超级大。可能你会觉得正常,毕竟分辨率不同。但实际效果大的有些夸张。
    第三,设计图(比如360640)上的内容占据屏幕的2/3,按照这种适配,特长手机(29601440 S8)上的内容也会占据2/3,这肯定不合理,控件之间的间隙会特别大,一看就不符合设计效果,手机长,内容占据低于2/3才正常,比如可能占据1/3.第四,占据资源大:好几百KB,甚至多达1M或跟多。
  • dimen 基于dp:
    这种适配依据的是最小宽度限定符。个人看来存在的问题是:
    第一,Android 私人订制的原因,宽度方面参差不齐,不可能适配所有的手机。
    第二,sp和dp值有些值不全(这个应该是可以解决的),姑且算是一个小问题。
    第三,项目中增加了N个文件夹,上拉下拉查看文件非常不方便:想看string或者color资源文件需要拉很多再能到达。
    第四,通过宽度限定符就近查找的原理,可以看出来匹配出来的大小不够准确。

5. 推荐今日头条适配:

  • 现状:
    UI设计图是按屏幕宽度为360dp来设计的,那么在上述设备上,屏幕宽度其实为1080/(440/160)=392.7dp,也就是屏幕是比设计图要宽的。这种情况下, 即使使用dp也是无法在不同设备上显示为同样效果的。 同时还存在部分设备屏幕宽度不足360dp,这时就会导致按360dp宽度来开发实际显示不全。加上16:9、4:3甚至其他宽高比层出不穷,宽高比不同,显示完全一致就不可能了,不信你去看看pixel 2的density就明白了。
  • 解决方案:
    通常下,我们只需要以宽或高一个维度去适配,比如我们Feed是上下滑动的,只需要保证在所有设备中宽的维度上显示一致即可,再比如一个不支持上下滑动的页面,那么需要保证在高这个维度上都显示一致,尤其不能存在某些设备上显示不全的情况。
  • 实质:
    ① 支持以宽或者高一个维度去适配,保持该维度上和设计图一致;
    ② 支持dp和sp单位,控制迁移成本到最小。
  • 具体代码:
    px = dp * density dp是360dp,想要px正好是屏幕宽度的话,只能修改density。
  /**
     * 适配:修改设备密度
     */
    private static float sNoncompatDensity;
    private static float sNoncompatScaledDensity;

    public static void setCustomDensity(@NonNull Activity activity, @NonNull final Application application) {
        DisplayMetrics appDisplayMetrics = application.getResources().getDisplayMetrics();
        if (sNoncompatDensity == 0) {
            sNoncompatDensity = appDisplayMetrics.density;
            sNoncompatScaledDensity = appDisplayMetrics.scaledDensity;
            // 防止系统切换后不起作用
            application.registerComponentCallbacks(new ComponentCallbacks() {
                @Override
                public void onConfigurationChanged(Configuration newConfig) {
                    if (newConfig != null && newConfig.fontScale > 0) {
                        sNoncompatScaledDensity = application.getResources().getDisplayMetrics().scaledDensity;
                    }
                }

                @Override
                public void onLowMemory() {

                }
            });
        }
        float targetDensity = appDisplayMetrics.widthPixels / 360;
        // 防止字体变小
        float targetScaleDensity = targetDensity * (sNoncompatScaledDensity / sNoncompatDensity);
        int targetDensityDpi = (int) (160 * targetDensity);

        appDisplayMetrics.density = targetDensity;
        appDisplayMetrics.scaledDensity = targetScaleDensity;
        appDisplayMetrics.densityDpi = targetDensityDpi;

        final DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
        activityDisplayMetrics.density = targetDensity;
        activityDisplayMetrics.scaledDensity = targetScaleDensity;
        activityDisplayMetrics.densityDpi = targetDensityDpi;

    }
DisplayUtil.setCustomDensity(this, getApplication());

说明:今日头条的这种适配,只需要在baseActivity中添加一句话即可。适配就是这么简单。

  • 适配效果:


    适配效果.png

    说明:虽然图片是今日头条博客里面的,但是我也是亲身测过后才推荐大家的,至于没有发实测图片,公司项目多有不便,还请理解。

6. 后续

如果大家喜欢这篇文章,欢迎点赞;如果想看更多移动端方面的技术,欢迎关注!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容