• Android屏幕适配和方案【整理】


    版权声明:本文为HaiyuKing原创文章,转载请注明出处!

    前言

    这里只是根据参考资料整理下,具体内容请阅读参考资料。

    原型设计图

    推荐1倍效果图,即采用 720 * 360 大小( 1280 *720:两倍图 1920 * 1080: 三倍图),最主要的原因就是1px = 1dp,效果图标多大的px,布局就写多大dp。

    屏幕各项参数

    • 手机像素(px):一个小黑点就是像素;
    • 手机尺寸:屏幕的对角线的长度;
    • 手机分辨率:整个屏幕一共有多少个点(像素),常见取值 480X800 ,320X480等;
    • 像素密度(dpi)

      1. 每英寸中的像素数(假如设备分辨率为320*240,屏幕长2英寸宽1.5英寸,dpi=320/2 = 240/1.5 =160)
      2. 对应于DisplayMetrics类中属性densityDpi的值;
      3. 当然这种宽和高的dpi都相同的情况现在已经很少见,所以实际计算方式见下图

      像素密度(dpi)、屏幕尺寸、分辨率三者关系:

      

           举例说明:屏幕分辨率为:1920*1080,屏幕尺寸为5吋的话,那么dpi为440:

         

    • 密度(density)

      1. 每平方英寸中的像素数(density = dpi / 160 );
      3. 对应于DisplayMetrics类中属性density的值;
      4. 可用于px与px与dip的互相转换 :dp = px / density ;

      

      720P,和1080P的手机,dpi是不同的,这也就意味着,不同的分辨率中,1dp对应不同数量的px(720P中,1dp=2px,1080P中1dp=3px)。

    • 设备独立像素(dp、dip)

      1.不同设备有不同的显示效果,不依赖像素(dp = px / density =  px / (dpi / 160) )
      2.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 是为了避免损失精度。

    适配分类

    一、图片适配

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

    • drawable-ldpi :低分辨率(用的少了,一般不再用)
    • drawable-mdpi:中分辨率
    • drawable-hdpi:高分辨率
    • drawable-xdpi:较高分辨率
    • drawable-xxdpi:超级高分辨率
    • drawable-xxxhpi:顶级分辨率

    在对应的文件夹下放置不同分辨率的图片就可以很好的对图片进行适配。
    随着屏幕越来越大,推荐xxdpi的一套切图,这样就可以向下和向上兼容,节省资源。
    建议图标使用svg格式,图片仍然使用png格式,svg的图标大小约是png的1/4,在很大的项目中,图标有很多,这个时候svg的优势就凸显无疑了。

    二、布局适配

    在layout之外,根据不同分辨率,创建不同的布局文件夹:

    • layout-800 * 480
    • layout-1280 * 720

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

    三、权重适配 

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

    四、屏幕适配

    详细内容请阅读参考资料。以下内容均是引用!

    1、dp+自适应布局+weight比例布局直接适配

    这基本是最原始的Android适配方案。

    wrap_content,match_parent,layout_weight等,我们就要毫不犹豫的使用,而且在高这个维度上,我们要依照情况设计为可滑动的方式,或者match_parent,尽量不要写死。总之,所有的适配方案都不是用来取代match_parent,wrap_content的,而是用来完善他们的。

    缺点

    (1)这只能保证我们写出来的界面适配绝大部分手机,部分手机仍然需要单独适配;

      为什么dp只解决了90%的适配问题,因为并不是所有的1080P的手机dpi都是480,比如Google 的Pixel2(1920*1080)的dpi是420,也就是说,在Pixel2中,1dp=2.625px,这样会导致相同分辨率的手机中,这样,一个100dp*100dp的控件,在一般的1080P手机上,可能都是300px,而Pixel 2 中 ,就只有262.5px,这样控件的实际大小会有所不同。

    (2)这种方式无法快速高效的把设计师的设计稿实现到布局代码中,通过dp直接适配,我们只能让UI基本适配不同的手机,但是在设计图和UI代码之间的鸿沟,dp是无法解决的,因为dp不是真实像素。而且,设计稿的宽高往往和Android的手机真实宽高差别极大,以我们的设计稿为例,设计稿的宽高是375px*750px,而真实手机可能普遍是1080*1920;那么在日常开发中我们是怎么跨过这个鸿沟的呢?基本都是通过百分比啊,或者通过估算,或者设定一个规范值等等。总之,当我们拿到设计稿的时候,设计稿的ImageView是128px*128px,当我们在编写layout文件的时候,却不能直接写成128dp*128dp。在把设计稿向UI代码转换的过程中,我们需要耗费相当的精力去转换尺寸,这会极大的降低我们的生产力,拉低开发效率。

    2、dimens基于px的适配(宽高限定符适配)

    原理

    根据市面上手机分辨率的占比分析,我们选定一个占比例值大的(比如1280*720)设定为一个基准,然后其他分辨率根据这个基准做适配。

    基准的意思(比如320*480的分辨率为基准)是:
    宽为320,将任何分辨率的宽度分为320份,取值为x1到x320
    长为480,将任何分辨率的高度分为480份,取值为y1到y480

    例如对于800 * 480的分辨率设备来讲,需要在项目中values-800x480目录下的dimens.xml文件中的如下设置(当然了,可以通过工具自动生成):

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
    <dimen name="x1">1.5px</dimen>
    <dimen name="x2">3.0px</dimen>
    <dimen name="x3">4.5px</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文件夹自动适配为合适的大小。

     

    缺点

    第一,Android不同分辨率的手机实在太多了,可能你说主流就可以,的确小公司主流就可以,淘宝这种App肯定不能只适配主流手机。
    第二,控件在设计图上显示的大小以及控件之间的间隙在小分辨率和大分辨率手机上天壤之别,你会发现大屏幕手机上控件超级大。可能你会觉得正常,毕竟分辨率不同。但实际效果大的有些夸张。
    第三,设计图(比如360640)上的内容占据屏幕的2/3,按照这种适配,特长手机(29601440 S8)上的内容也会占据2/3,这肯定不合理,控件之间的间隙会特别大,一看就不符合设计效果,手机长,内容占据低于2/3才正常,比如可能占据1/3.第四,占据资源大:好几百KB,甚至多达1M或跟多。

    3、dimen 基于dp的适配(smallestWidth适配)

    原理

    这种适配依据的是最小宽度限定符。指的是Android会识别屏幕可用高度和宽度的最小尺寸的dp值(其实就是手机的宽度值),然后根据识别到的结果去资源文件中寻找对应限定符的文件夹下的资源文件。这种机制和上文提到的宽高限定符适配原理上是一样的,都是系统通过特定的规则来选择对应的文件。

    举个例子,小米5的dpi是480,横向像素是1080px,根据px=dp(dpi/160),横向的dp值是1080/(480/160),也就是360dp,系统就会去寻找是否存在value-sw360dp的文件夹以及对应的资源文件。

    smallestWidth限定符适配和宽高限定符适配最大的区别在于,有很好的容错机制,如果没有value-sw360dp文件夹,系统会向下寻找,比如离360dp最近的只有value-sw350dp,那么Android就会选择value-sw350dp文件夹下面的资源文件。这个特性就完美的解决了上文提到的宽高限定符的容错问题。

    缺点

    • Android 私人订制的原因,宽度方面参差不齐,不可能适配所有的手机。
    • sp和dp值有些值不全(这个应该是可以解决的),姑且算是一个小问题。
    • 项目中增加了N个文件夹,上拉下拉查看文件非常不方便:想看string或者color资源文件需要拉很多再能到达。
    • 通过宽度限定符就近查找的原理,可以看出来匹配出来的大小不够准确。
    • 是在Android 3.2 以后引入的,Google的本意是用它来适配平板的布局文件(但是实际上显然用于diemns适配的效果更好),不过目前所有的项目应该最低支持版本应该都是4.0了(糗事百科这么老的项目最低都是4.0哦),所以,这问题其实也不重要了。

    4、今日头条适配(修改手机的设备密度 density)

    这套方案对老项目是不太友好的,因为修改了系统的density值之后,整个布局的实际尺寸都会发生改变,如果想要在老项目文件中使用,恐怕整个布局文件中的尺寸都可能要重新按照设计稿修改一遍才行。因此,如果你是在维护或者改造老项目,使用这套方案就要三思了。

    原理

      通过修改density值,强行把所有不同尺寸分辨率的手机的宽度dp值改成一个统一的值,这样就解决了所有的适配问题。

      比如,设计稿宽度是360px,那么开发这边就会把目标dp值设为360dp,在不同的设备中,动态修改density值,从而保证(手机像素宽度)px/density这个值始终是360dp,这样的话,就能保证UI在不同的设备上表现一致了。

      UI设计图是按屏幕宽度为360dp来设计的,那么在上述设备上,屏幕宽度其实为1080/(440/160)=392.7dp,也就是屏幕是比设计图要宽的。这种情况下, 即使使用dp也是无法在不同设备上显示为同样效果的。 同时还存在部分设备屏幕宽度不足360dp,这时就会导致按360dp宽度来开发实际显示不全。加上16:9、4:3甚至其他宽高比层出不穷,宽高比不同,显示完全一致就不可能了。

      通常下,我们只需要以宽或高一个维度去适配,比如我们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;
    
        }

    只需要在baseActivity中添加一句话即可。适配就是这么简单。

    DisplayUtil.setCustomDensity(this, getApplication());

    缺点

    • 只需要修改一次 density,项目中的所有地方都会自动适配,这个看似解放了双手,减少了很多操作,但是实际上反应了一个缺点,那就是只能一刀切的将整个项目进行适配,但适配范围是不可控的。这样不是很好吗?这样本来是很好的,但是应用到这个方案是就不好了,因为我上面的原理也分析了,这个方案依赖于设计图尺寸,但是项目中的系统控件、三方库控件、等非我们项目自身设计的控件,它们的设计图尺寸并不会和我们项目自身的设计图尺寸一样。当这个适配方案不分类型,将所有控件都强行使用我们项目自身的设计图尺寸进行适配时,这时就会出现问题,当某个系统控件或三方库控件的设计图尺寸和和我们项目自身的设计图尺寸差距非常大时,这个问题就越严重。

    开源库地址

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

    AndroidAutoSize

    参考资料

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

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

    Android 目前最稳定和高效的UI适配方案

    骚年你的屏幕适配方式该升级了!-今日头条适配方案

    骚年你的屏幕适配方式该升级了!-smallestWidth 限定符适配方案

    dpi 、 dip 、分辨率、屏幕尺寸、px、density 关系以及换算

  • 相关阅读:
    C#数组
    找不到命名空间命名空间:System.Windows.Forms
    WebService 的创建,部署和使用
    动态调用webservice时 ServiceDescriptionImporter类在vs2010无法引用的解决方法
    SQL语句新建表,同时添加主键、索引、约束
    SQLServer Ansi_Padding的用法
    浅析jQuery删除节点的三个方法
    C# JSON 序列化和反序列化——JavaScriptSerializer实现
    命令行启动tomcat,怎么配置
    ojective-c convert to pascal pattern
  • 原文地址:https://www.cnblogs.com/whycxb/p/9755012.html
Copyright © 2020-2023  润新知