Android 屏幕适配详细讲解

一、前言

屏幕适配

① 到底哪一种屏幕适配最合适,仁者见仁智者见智.

② 开始我推荐dimens基于px的适配,而后来我推荐dimens dp的适配,而如今我推荐修改今日头条的适配(修改手机的设备密度 density)。

屏幕尺寸

屏幕的对角线的长度。

屏幕分辨率

屏幕宽和高两者的像素值之积。

二、正文

切图

之前我我推荐切图依照1280 * 720的分辨率进行切图,既适用于更高分辨率的手机,也适用于更低分辨率的手机,而且节省资源,占用更少的空间。考虑屏幕越来越大,全面屏占据了半壁江山,可以考虑更高分辨率的切图,比如1920 * 1080。

如今我推荐1倍图的切图,对应Android的720,360设计图大小(360*2=720 两倍 360*3=1080 三倍图),最主要的原因就是设计图1px = 1dp,设计图写多大的px,写布局就写多大dp。

图片适配

在AndroidStudio的资源目录res下有五个层级图片文件夹,分别用来存放不同分辨率的图片:

  • drawable-ldpi :低分辨率(用的少了,一般不再用)

  • drawable-mdpi:  中分辨率

  • drawable-hdpi:   高分辨率

  • drawable-xdpi:    较高分辨率

  • drawable-xxdpi:  超级高分辨率

  • drawable-xxxhpi:顶级分辨率

在对应的文件夹下放置不同分辨率的图片就可以很好的对图片进行适配。

随着屏幕越来越大,推荐xxdpi的一套切图,这样就可以向下和向上兼容,节省资源。

对于图标使用svg格式,对于图片仍然使用png,svg的图标大小约是png的1/4,在很大的项目中,图标有很多,这个时候svg的优势就凸显无疑了。

布局适配

布局适配适用于手机大小不一样的手机用不同的布局,比如说创建两个文件夹:

  • layout-800 * 480

  • layout-1280 * 720

手机会根据分辨率去找设定的不同大小的layout的布局。实际开发中这种使用的情况非常少,因为占用太多资源,慎用。

权重适配(weight)

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

尺寸适配(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

代码适配

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控件的大小。

获取屏幕参数

  • 像素 - px

一个小黑点就是像素。

  • 尺寸

屏幕的对角线的长度。

  • 分辨率

整个屏幕一共有多少个点,也就是像素。

  • 像素密度 - dpi

每英寸中的像素数。假如设备分辨率为320*240,屏幕长2英寸宽1.5英寸,dpi=320/2 = 240/1.5 =160。对应于DisplayMetrics类中属性densityDpi的值。

  • 密度 - density

1. 每平方英寸中的像素数。

2. density = dpi / 160 。

3. 对应于DisplayMetrics类中属性density的值 。

4. 可用于px与px与dip的互相转换 :dp = px / density 。

  • 设备独立像素 - dip - dp

1. 不同设备有不同的显示效果,不依赖像素。

2. dp = px / density

3. dp = px / (dpi / 160) 

4. 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

常见适配

  • 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资源文件需要拉很多再能到达。

如果你觉得dimen 基于 px 适配没有问题,以下地址中的工具提供各种分辨率文件夹值得生成

链接:

https://pan.baidu.com/s/1jLXpQXs898hzIensZKV7gg 

密码:6xgs。

详解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下面即可

  • 美女设计师给我们设计图(标记好了各个控制的大小距离等等),肯定是以px为标记的,那假如宽50px,高80px,我们只需要把宽高写为@dimen/x50,@dimen/y80即可

推荐适配

  • 现状

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中添加一句话即可。适配就是这么简单。

  • 适配效果

猜你喜欢

转载自blog.csdn.net/jinmie0193/article/details/82180233