天天看點

Android-螢幕适配(smallestWidth适配/今日頭條螢幕适配方案終極版)(一)今日頭條螢幕适配方案(二)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

<manifest>
    <application>            
        <meta-data
            android:name="design_width_in_dp"
            android:value="360"/>
        <meta-data
            android:name="design_height_in_dp"
            android:value="640"/>           
     </application>           
</manifest>
           

更多詳細用于檢視 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适配/今日頭條螢幕适配方案終極版)(一)今日頭條螢幕适配方案(二)smallestWidth方式适配

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适配/今日頭條螢幕适配方案終極版)(一)今日頭條螢幕适配方案(二)smallestWidth方式适配

方案會為您需要适配的 最小寬度,在項目中生成一系列對應的 values-swdp,在前面也說了,如果某個裝置沒有為它提供對應的 values-swdp,那它就會去尋找相近的 values-swdp,但如果這個相近的 values-swdp 與期望的 values-swdp 差距太大,那适配效果也就會大打折扣

使用

例如:要畫一個 10dp * 10dp的圖檔

Android-螢幕适配(smallestWidth适配/今日頭條螢幕适配方案終極版)(一)今日頭條螢幕适配方案(二)smallestWidth方式适配

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 = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n";
    private static final String XML_RESOURCE_START = "<resources>\r\n";
    private static final String XML_RESOURCE_END = "</resources>\r\n";
    private static final String XML_DIMEN_TEMPLETE = "<dimen name=\"qb_%1$spx_%2$d\">%3$.2fdp</dimen>\r\n";

   
    private static final String XML_BASE_DPI = "<dimen name=\"base_dpi\">%ddp</dimen>\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