Android-屏幕适配(smallestWidth适配/今日头条屏幕适配方案终极版)

(一)今日头条屏幕适配方案

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

其适配方案的核心原理在于,根据以下公式算出 density (density 的意思就是 1 dp 占当前设备多少像素)

当前设备屏幕总宽度(单位为像素)/ 设计图总宽度(单位为 dp) = density

屏幕总宽度就是 屏幕宽与高相比较最小的那个 例如: 屏幕像素为 1920 * 1080 则屏幕总宽度为 1080
详细介绍:
https://mp.weixin.qq.com/s/d9QCoBP6kV9VSWvVldVVwA
https://www.jianshu.com/p/55e0fca23b4f

另外推荐一个根据今日头条技术封装好的屏幕适配框架:
AndroidAutoSize 框架

使用:
一.

implementation 'me.jessyan:autosize:1.0.0'

使用:
在 AndroidManifest 中填写全局设计图尺寸 (单位 dp),如果使用副单位,则可以直接填写像素尺寸,不需要再将像素转化为 dp


                
        
                   
                

更多详细用于查看 demo-subunits


(二)smallestWidth方式适配

以下内容来自 https://www.jianshu.com/p/2aded8bb6ede 我只是将主要的罗列出来


什么是 smallestWidth

smallestWidth 翻译为中文的意思就是 最小宽度,那这个 最小宽度 是什么意思呢?

系统会根据当前设备屏幕的 最小宽度 来匹配 values-swdp,为什么不是根据 宽度 来匹配,而要加上 最小 这两个字呢?

这就要说到,移动设备都是允许屏幕可以旋转的,当屏幕旋转时,屏幕的高宽就会互换,加上 最小 这两个字,是因为这个方案是不区分屏幕方向的,它只会把屏幕的高度和宽度中值最小的一方认为是 最小宽度,这个 最小宽度 是根据屏幕来定的,是固定不变的,意思是不管您怎么旋转屏幕,只要这个屏幕的高度大于宽度,那系统就只会认定宽度的值为 最小宽度,反之如果屏幕的宽度大于高度,那系统就会认定屏幕的高度的值为 最小宽度



smallestWidth 的值是怎么算的

要先算出当前设备的 smallestWidth 值我们才能知道当前设备该匹配哪个 values-swdp 文件夹

例如:
我们假设设备的屏幕信息是 1920 * 1080、480 dpi

根据上面的规则我们要在屏幕的高度和宽度中选择值最小的一方作为最小宽度,1080 < 1920,明显 1080 px 就是我们要找的 最小宽度 的值,但 最小宽度 的单位是 dp,所以我们要把 px 转换为 dp

使用下面的公式:
px / density = dp
DPI / 160 = density

所以最终的公式是 px / (DPI / 160) = dp

所以我们得到的 最小宽度 的值是 360 dp = 1080 / (480/160)

现在我们已经算出了当前设备的最小宽度是 360 dp,我们晓得系统会根据这个 最小宽度 帮助我们匹配到 values-sw360dp 文件夹下的 dimens.xml 文件,如果项目中没有 values-sw360dp 这个文件夹,系统才会去匹配相近的 values-swdp 文件夹

dimens.xml 文件是整个方案的核心所在,所以接下来我们再来看看 values-sw360dp 文件夹中的这个 dimens.xml 是根据什么原理生成的


dimens.xml 生成原理

因为我们在项目布局中引用的 dimens 的实际值,来源于根据当前设备屏幕的 最小宽度 所匹配的 values-swdp 文件夹中的 dimens.xml,所以搞清楚 dimens.xml 的生成原理,有助于我们理解 smallestWidth 限定符屏幕适配方案
dimens.xml 的生成,就要涉及到两个因数,第一个因素是 最小宽度基准值第二个因素需要生成多少个 values-swdp 文件夹(由项目中需要适配最小宽度个数来定)

第一个因素:

最小宽度基准值 是什么意思呢?简单理解就是您需要把设备的屏幕宽度分为多少份,假设我们现在把项目的 最小宽度基准值 定为 360,最小宽度为刚刚上面计算的 360dp,那这个方案就会理解为您想把所有设备的屏幕宽度都分为 360 份,方案会帮您在 dimens.xml 文件中生成 1 到 360 的 dimens 引用,比如 values-sw360dp 中的 dimens.xml 是长这样的 公式:(最小宽度/最小基准值) 360dp/360dp = 1dp(如果其它的最小宽度除不尽量保留小数位,如果舍去影响精度)

Android-屏幕适配(smallestWidth适配/今日头条屏幕适配方案终极版)_第1张图片

values-sw360dp 指的是当前设备屏幕的 最小宽度 为 360dp (该设备高度大于宽度,则最小宽度就是宽度,所以该设备宽度为 360dp),把屏幕宽度分为 360 份,刚好每份等于 1dp,所以每个引用都递增 1dp,值最大的 dimens 引用 dp_360 值也是 360dp,刚好覆盖屏幕宽度


第二个因素

第二个因数是需要适配哪些 最小宽度?比如您想适配的 最小宽度 有 320dp、360dp、400dp、411dp、480dp,那方案就会为您的项目生成 values-sw320dp、values-sw360dp、values-sw400dp、values-sw411dp、values-sw480dp 这几个资源文件夹(一般根据市面上主流手机的dp来生成就可以),像这样
Android-屏幕适配(smallestWidth适配/今日头条屏幕适配方案终极版)_第2张图片

方案会为您需要适配的 最小宽度,在项目中生成一系列对应的 values-swdp,在前面也说了,如果某个设备没有为它提供对应的 values-swdp,那它就会去寻找相近的 values-swdp,但如果这个相近的 values-swdp 与期望的 values-swdp 差距太大,那适配效果也就会大打折扣


使用

例如:要画一个 10dp * 10dp的图片
Android-屏幕适配(smallestWidth适配/今日头条屏幕适配方案终极版)_第3张图片

dimen文件生成代码(java项目):

代码来自:https://github.com/ladingwu/dimens_sw

自行运行就可以了:

一.

package dimens.constants;


public enum DimenTypes {

    //适配Android 3.2以上   大部分手机的sw值集中在  300-460之间
	 DP_sw__300(300),  // values-sw300
	 DP_sw__310(310),
	 DP_sw__320(320),
	 DP_sw__330(330);
	// 想生成多少自己以此类推
  

    /**
     * 屏幕最小宽度
     */
    private int swWidthDp;




    DimenTypes(int swWidthDp) {

        this.swWidthDp = swWidthDp;
    }

    public int getSwWidthDp() {
        return swWidthDp;
    }

    public void setSwWidthDp(int swWidthDp) {
        this.swWidthDp = swWidthDp;
    }

}

二.

package dimens.utils;


import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigDecimal;

import dimens.constants.DimenTypes;



public class MakeUtils {

    private static final String XML_HEADER = "\r\n";
    private static final String XML_RESOURCE_START = "\r\n";
    private static final String XML_RESOURCE_END = "\r\n";
    private static final String XML_DIMEN_TEMPLETE = "%3$.2fdp\r\n";

   
    private static final String XML_BASE_DPI = "%ddp\r\n";
    private  static final int MAX_SIZE = 720;

    /**
     * 生成的文件名
     */
    private static final String XML_NAME = "dimens.xml";


    public static float px2dip(float pxValue, int sw,int designWidth) {
        float dpValue =   (pxValue/(float)designWidth) * sw;
        BigDecimal bigDecimal = new BigDecimal(dpValue);
        float finDp = bigDecimal.setScale(2, BigDecimal.ROUND_HALF_UP).floatValue();
        return finDp;
    }
    

    /**
     * 生成所有的尺寸数据
     *
     * @param type
     * @return
     */
    private static String makeAllDimens(DimenTypes type, int designWidth) {
        float dpValue;
        String temp;
        StringBuilder sb = new StringBuilder();
        try {
            sb.append(XML_HEADER);
            sb.append(XML_RESOURCE_START);
            //备份生成的相关信息
            temp = String.format(XML_BASE_DPI, type.getSwWidthDp());
            sb.append(temp);
            for (int i = 0; i <= MAX_SIZE; i++) {
            	
                dpValue = px2dip((float) i,type.getSwWidthDp(),designWidth);
                temp = String.format(XML_DIMEN_TEMPLETE,"", i, dpValue);
                sb.append(temp);
            }


            sb.append(XML_RESOURCE_END);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sb.toString();
    }



    /**
     * 生成的目标文件夹
     * 只需传宽进来就行
     *
     * @param type 枚举类型
     * @param buildDir 生成的目标文件夹
     */
    public static void makeAll(int designWidth, dimens.constants.DimenTypes type, String buildDir) {
        try {
            //生成规则
            final String folderName;
            if (type.getSwWidthDp() > 0) {
                //适配Android 3.2+
                folderName = "values-sw" + type.getSwWidthDp() + "dp";
            }else {
            	return;
            }
            
            //生成目标目录
            File file = new File(buildDir + File.separator + folderName);
            if (!file.exists()) {
                file.mkdirs();
            }

            //生成values文件
            FileOutputStream fos = new FileOutputStream(file.getAbsolutePath() + File.separator + XML_NAME);
            fos.write(makeAllDimens(type,designWidth).getBytes());
            fos.flush();
            fos.close();


        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

三.

package dimens.generator;

import dimens.constants.DimenTypes;
import dimens.utils.MakeUtils;
import java.io.File;

public class DimenGenerator {

    /**
     * 设计稿尺寸(将自己设计师的设计稿的宽度填入)
     */
    private static final int DESIGN_WIDTH = 375;

    /**
     * 设计稿的高度  (将自己设计师的设计稿的高度填入)
     */
    private static final int DESIGN_HEIGHT = 667;

    public static void main(String[] args) {
        int smallest = DESIGN_WIDTH>DESIGN_HEIGHT? DESIGN_HEIGHT:DESIGN_WIDTH;  //     求得最小宽度
        DimenTypes[] values = DimenTypes.values();
        for (DimenTypes value : values) {
            File directory = new File("");//为了方便,设定为当前文件夹,dimens文件将会生成项目所在文件夹中,用户可自行更改
            MakeUtils.makeAll(smallest, value, directory.getAbsolutePath());
        }

    }

}

优点

1.非常稳定,极低概率出现意外

2.不会有任何性能的损耗

3.适配范围可自由控制,不会影响其他三方库

4.在插件的配合下,学习成本低

缺点

1.在布局中引用 dimens 的方式,虽然学习成本低,但是在日常维护修改时较麻烦

2.侵入性高,如果项目想切换为其他屏幕适配方案,因为每个 Layout 文件中都存在有大量 dimens 的引用,这时修改起来工作量非常巨大,切换成本非常高昂

3.无法覆盖全部机型,想覆盖更多机型的做法就是生成更多的资源文件,但这样会增加 App 体积,在没有覆盖的机型上还会出现一定的误差,所以有时需要在适配效果和占用空间上做一些抉择

4.如果想使用 sp,也需要生成一系列的 dimens,导致再次增加 App 的体积

5.不能自动支持横竖屏切换时的适配,如上文所说,如果想自动支持横竖屏切换时的适配,需要使用 values-wdp 或 屏幕方向限定符 再生成一套资源文件,这样又会再次增加 App 的体积

6.不能以高度为基准进行适配,考虑到这个方案的名字本身就叫 最小宽度限定符适配方案,所以在使用这个方案之前就应该要知道这个方案只能以宽度为基准进行适配,为什么现在的屏幕适配方案只能以高度或宽度其中的一个作为基准进行适配,请看 https://github.com/JessYanCoding/AndroidAutoSize/issues/8


以上两种方案我个人感觉 AndroidAutoSize 框架所以用的今日头条适配技术 侵入性比 SmallestWidth要低些,以后如果要更改适配方式比较方便, 使用 SmallestWidth 方案的话以后更该维护起来比较费劲(这纯属与个人看法,如有得罪的地方请海涵,毕竟偶还是个小白)

参考:https://www.jianshu.com/p/2aded8bb6ede
参考:https://mp.weixin.qq.com/s/X-aL2vb4uEhqnLzU5wjc4Q

今日头条屏幕适配方案终极版:https://github.com/JessYanCoding/AndroidAutoSize

你可能感兴趣的:(Android)