android UI適配簡單記錄三

作為一個(gè)開發(fā)人員,屁股決定腦袋,偶爾需要換位思考,站在UI設(shè)計(jì)人員的角度考慮下,想想U(xiǎn)I設(shè)計(jì)是怎么來的。

這里可以先簡單看下 Material Design,了解下ui設(shè)計(jì)。
https://material.io/design/layout/understanding-layout.html#usage

Material是一個(gè)適應(yīng)性指南,組件和工具系統(tǒng),支持用戶界面設(shè)計(jì)的最佳實(shí)踐。在開源代碼的支持下,Material簡化了設(shè)計(jì)人員和開發(fā)人員之間的協(xié)作,并幫助團(tuán)隊(duì)快速構(gòu)建精美的產(chǎn)品。

Material上的示例標(biāo)識的單位是dp,但是現(xiàn)實(shí)中可能你拿到的圖片標(biāo)識單位還是px。


例如下圖這種圖。

圖片出處:
Android 應(yīng)用設(shè)計(jì)開發(fā)有沒有更好的 UI 適配方法? - captain的回答 - 知乎
https://www.zhihu.com/question/22181844/answer/58124185

想象一下,如何畫一張這樣簡單的界面的圖并標(biāo)識尺寸。
打開電腦自帶畫圖軟件,新建一個(gè)400x400像素大小的文件(隨便畫畫),單擊查看,勾選標(biāo)尺和狀態(tài)欄,網(wǎng)格線。

image.png

這里只是簡單畫一畫,并不是很精準(zhǔn)。但是在畫圖的過程中加深了對px以及ui設(shè)計(jì)的了解。專業(yè)的畫圖工具功能會更全,但是一般都收費(fèi)。


首先,這張圖上的單位都是px,因?yàn)閳D片設(shè)計(jì)基本單位是px,要如何把px換成dp呢?


image.png
以下部分出處:
安卓UI圖px標(biāo)注適配攻略:批量轉(zhuǎn)化px為dp
https://blog.csdn.net/zengd0/article/details/52464627

px轉(zhuǎn)dp的公式:dp = px/density
density是設(shè)備密度,有了設(shè)備密度,我們才可以將px轉(zhuǎn)為dp。
這時(shí)候你可能會想,Android系統(tǒng)不是提供了api供我們轉(zhuǎn)換嗎,或者Android系統(tǒng)獲取設(shè)備密度:context.getResources().getDisplayMetrics().density,然后將UI圖的px去除以desity?
非也。density值是獲取了,但是請問UI圖上的px值是按照你的手機(jī)來標(biāo)注的嗎?如果UI圖是以iPhone6手機(jī)(4.7寸屏幕,分辨率1334*750)為基準(zhǔn)進(jìn)行設(shè)計(jì)的,那么你用這個(gè)px值去除以你自己安卓手機(jī)的設(shè)備密度,怎么可能對。
也就是說,我們要獲取UI圖的設(shè)備密度(density)。

這里說的設(shè)備密度是指density 。
計(jì)算公式:density = PPI/160。
ppi計(jì)算公式如下圖。

image.png

由于示例圖片在設(shè)計(jì)的時(shí)候是適配640 * 1136機(jī)型的圖,屏幕尺寸應(yīng)該是4.0英寸(根據(jù)分辨率搜索所得)。因此ppi ≈ 326。
density = 326/160 = 2.0。
所以為640 * 1136機(jī)型設(shè)計(jì)的ui圖中, 一個(gè) 寬度為 50 px的圖形轉(zhuǎn)換 dp : dp =50 px/2.0 = 25dp。

640 * 1136并不是主流機(jī)型,這里以市面上較為主流720 * 1280機(jī)型分辨率為例,簡單看下如何適配分辨率720 * 1280的屏幕。
這里計(jì)算下720 * 1280 與640 * 1136的寬高比率。720/640 = 1.125 1280/1136 ≈ 1.127,屏幕分辨率比率幾乎相等,就按1.125 去計(jì)算,進(jìn)行縮放。50 px應(yīng)該 乘以 1.125, 50 px * 1.125 = 56.25px。

640 x 1136中寬度為 50 px的圖形在720 x1280 中對應(yīng)的圖形大小是56.25px。
而720 * 1280的屏幕,縮放因子density一般是2.0,因此
dp = 56.25px / 2.0 ≈ 28.13dp 。因此在控件里寫28.13dp。

如果不能理解可以看下下面的例子。
打開window自帶畫圖工具,新建一個(gè)200 x 200 像素大小的圖,以中心為原點(diǎn)畫一個(gè)寬度100px的正方形,中心坐標(biāo)(100,100),左上角坐標(biāo)(50,50),右下角坐標(biāo)(150,150)。


image.png

然后重新調(diào)整大小至800 x 800 px,擴(kuò)大4倍。



此時(shí),正方形寬度變成了400px ,中心坐標(biāo)(400,400),左上角坐標(biāo)(200,200),右下角坐標(biāo)(600,600)。隨著圖片大小放大,正方形大小也放大了。不同分辨率中,正方形的寬度不一致(可以把正方形想象成如TextView、Button之類的控件)。
image.png

在有su權(quán)限的設(shè)備上,執(zhí)行命令,將屏幕分辨率調(diào)整至

adb shell wm size 200x200

由于這臺設(shè)備帶有底部虛擬鍵并且android默認(rèn)帶狀態(tài)欄和工具欄,所以需要這里先隱藏下。

//因此狀態(tài)欄和底部導(dǎo)航欄
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        View decorView = getWindow().getDecorView();
        int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_FULLSCREEN;
        decorView.setSystemUiVisibility(uiOptions);
    }
}

因此工具欄則需要修改manifest文件中,application節(jié)點(diǎn)下的 android:theme,修改結(jié)果如下所示。

 android:theme="@style/Theme.AppCompat.Light.NoActionBar"

xml文件如所示。由于設(shè)備density為1.0,所以layout_width寫100dp。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center">

  <TextView
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:text="test"
        android:textSize="12sp"
        android:gravity="center"
        android:background="@color/colorPrimary"/>

</LinearLayout>

運(yùn)行結(jié)果如下所示。白色部分內(nèi)為界面內(nèi),黑色部分為界外,黑色部分可忽略。


此時(shí)再將屏幕分辨率調(diào)到800x800

adb shell wm size 800x800

屏幕上會變成如下所示。



可以發(fā)現(xiàn)控件寬度為100px,要想使控件寬度為400px,此時(shí)就需要修改xml文件了。將layout_width和textSize都放大4倍。

  <TextView
        android:layout_width="400dp"
        android:layout_height="400dp"
        android:text="test"
        android:textSize="48sp"
        android:gravity="center"
        android:background="@color/colorPrimary"/>

運(yùn)行結(jié)果如下所示


將截屏圖片與設(shè)計(jì)圖片簡單對比,大小基本一致,控件的寬度達(dá)到了設(shè)計(jì)圖的寬度為400px的效果。


這是比較極端的情況,僅用于舉例。

但是其實(shí)剛剛將屏幕分辨率調(diào)到800x800后,要想使控件寬度為400px,除了修改xml文件,還有另一種修改方法:修改屏密度。
首先將layout_width和layout_height改回100dp,textSize改回12sp。

    <TextView
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:text="test"
        android:textSize="12sp"
        android:gravity="center"
        android:background="@color/colorPrimary"/>

然后運(yùn)行以下命令修改dpi。

//160*4 = 640 這是設(shè)置android中的Dpi 值
adb shell wm density 640

運(yùn)行該命令可以修改屏幕密度。
此時(shí)縮放因子density = dpi/160 = 4。
又 px = dp * density
所以當(dāng)layout_width為100dp時(shí), 控件的實(shí)際物理大小為100dp*4 即 400px。
運(yùn)行結(jié)果如下所示,與方法一結(jié)果一致。


這個(gè)例子很直觀的顯示出,UI受density和分辨率的影響。

我們先控制單一變量去思考適配ui。
先以分辨率為變量為例進(jìn)行說明。
由于UI受屏幕分辨率影響,屏幕分辨率限定符法就出現(xiàn)了。這個(gè)方法我真的沒有找到出處。
屏幕分辨率限定符法,即每種屏幕分辨率的設(shè)備需要定義一套 dimens.xml 文件。這種方法也有人稱之為,百分比適配方法。
以某一分辨率為基準(zhǔn),生成所有分辨率對應(yīng)像素?cái)?shù)列表。
現(xiàn)在我們以200x200的分辨率為基準(zhǔn):
將屏幕的寬度分為200份,取值為x1~x200
將屏幕的高度分為200份,取值為y1~y200
在values目錄下新建lay_x.xml和lay_y.xml文件。

簡單來說,就是將 android:layout_width,android:layout_height, android:textSize等尺寸大小,按不同的分辨率,定義在不同的values下。

在android studio中新建MakeXml .java文件,根據(jù)MakeXml 生成xml文件。
代碼如下。由于我手上屏幕為橫屏,所以稍微修改了一點(diǎn),以480x320為基準(zhǔn)分辨率。

代碼出處:
Android-屏幕適配全攻略(絕對詳細(xì))(二)
http://www.itdecent.cn/p/ad563d169871

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintWriter;

public class MakeXml {
    private final static String rootPath = "D:\\layoutroot\\values-{0}x{1}\\";

  /*  private final static float dw = 320f;
    private final static float dh = 480f;*/
  private final static float dw = 480f;
    private final static float dh = 320f;


    private final static String WTemplate = "<dimen name=\"x{0}\">{1}px</dimen>\n";
    private final static String HTemplate = "<dimen name=\"y{0}\">{1}px</dimen>\n";

    public static void main(String[] args) {
/*        makeString(320, 480);
        makeString(480, 800);
        makeString(480, 854);
        makeString(540, 960);
        makeString(600, 1024);
        makeString(720, 1184);
        makeString(720, 1196);
        makeString(720, 1280);
        makeString(768, 1024);
        makeString(800, 1280);
        makeString(1080, 1812);
        makeString(1080, 1920);
        makeString(1440, 2560);*/
        makeString(480, 320);
        makeString(800, 480);
        makeString(854, 480);
        makeString(960, 540);
        makeString(1024 ,600 );
        makeString(1184 ,720 );
        makeString(1196 ,720 );
        makeString(1280 , 720);
        makeString( 1024 ,768 );
        makeString(1280 ,800 );
        makeString( 1812 ,1080 );
        makeString(1920 ,1080 );
        makeString(2560 ,1440 );
    }

    public static void makeString(int w, int h) {

        StringBuffer sb = new StringBuffer();
        sb.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
        sb.append("<resources>");
        float cellw = w / dw;
        for (int i = 1; i < 480; i++) {
            sb.append(WTemplate.replace("{0}", i + "").replace("{1}",
                    change(cellw * i) + ""));
        }
        sb.append(WTemplate.replace("{0}", "480").replace("{1}", w + ""));
        sb.append("</resources>");

        StringBuffer sb2 = new StringBuffer();
        sb2.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
        sb2.append("<resources>");
        float cellh = h / dh;
        for (int i = 1; i < 320; i++) {
            sb2.append(HTemplate.replace("{0}", i + "").replace("{1}",
                    change(cellh * i) + ""));
        }
        sb2.append(HTemplate.replace("{0}", "320").replace("{1}", h + ""));
        sb2.append("</resources>");

        String path = rootPath.replace("{0}", w + "").replace("{1}", h + "");
        File rootFile = new File(path);
        if (!rootFile.exists()) {
            rootFile.mkdirs();
        }
        File layxFile = new File(path + "lay_x.xml");
        File layyFile = new File(path + "lay_y.xml");
        try {
            PrintWriter pw = new PrintWriter(new FileOutputStream(layxFile));
            pw.print(sb.toString());
            pw.close();
            pw = new PrintWriter(new FileOutputStream(layyFile));
            pw.print(sb2.toString());
            pw.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

    }

    public static float change(float a) {
        int temp = (int) (a * 100);
        return temp / 100f;
    }

}

選中java文件后右鍵,然后選擇run 'MakeXml.main()'。


image.png

將生成的文件copy至src\main\res目錄下。


image.png

image.png

又由于必須在默認(rèn)values里面也創(chuàng)建對應(yīng)默認(rèn)lay_x.xml和lay_y.xml文件。
由于我這里以480x320為基準(zhǔn)分辨率,因此將values-480x320下的lay_x.xml和lay_y.xml中的px先全部通過notepad++替換成dp,再放到values目錄下。


image.png

在activity中增加測試textview控件大小的代碼。

 mTextView.post(new Runnable() {
            @Override
            public void run() {
                int width = mTextView.getWidth();
                int height = mTextView.getHeight();
                Log.d(TAG,"width = " + width + " , height =" + height);
            }
        });

然后將TextView屬性改為如下所示。

   <TextView
        android:id="@+id/test_text_view"
        android:layout_width="@dimen/x240"
        android:layout_height="@dimen/y160"
        android:background="@color/colorPrimary"
        android:gravity="center"
        android:text="test" />

運(yùn)行程序后,不斷通過wm size命令修改屏幕分辨率,并且運(yùn)行程序。運(yùn)行結(jié)果如下。

07-05 17:19:06.547 3859-3859/com.demo.myapplication D/MainActivity: screenWidth = 480 , screenHeight =320
07-05 17:19:06.641 3859-3859/com.demo.myapplication D/MainActivity: width = 240 , height =160
07-05 17:19:31.083 3859-3859/com.demo.myapplication D/MainActivity: screenWidth = 480 , screenHeight =800
07-05 17:19:31.132 3859-3859/com.demo.myapplication D/MainActivity: width = 240 , height =160
07-05 17:19:31.468 3859-3859/com.demo.myapplication D/MainActivity: screenWidth = 800 , screenHeight =480
07-05 17:19:31.517 3859-3859/com.demo.myapplication D/MainActivity: width = 240 , height =160
07-05 17:20:11.736 3859-3859/com.demo.myapplication D/MainActivity: screenWidth = 854 , screenHeight =480
07-05 17:20:11.782 3859-3859/com.demo.myapplication D/MainActivity: width = 240 , height =160
07-05 17:20:26.221 3859-3859/com.demo.myapplication D/MainActivity: screenWidth = 960 , screenHeight =540
07-05 17:20:26.259 3859-3859/com.demo.myapplication D/MainActivity: width = 427 , height =240
07-05 17:20:51.721 3859-3859/com.demo.myapplication D/MainActivity: screenWidth = 1024 , screenHeight =600
07-05 17:20:51.774 3859-3859/com.demo.myapplication D/MainActivity: width = 480 , height =270
07-05 17:21:23.098 3859-3859/com.demo.myapplication D/MainActivity: screenWidth = 1024 , screenHeight =768
07-05 17:21:23.138 3859-3859/com.demo.myapplication D/MainActivity: width = 512 , height =300
07-05 17:21:57.911 3859-3859/com.demo.myapplication D/MainActivity: screenWidth = 1184 , screenHeight =720
07-05 17:21:57.959 3859-3859/com.demo.myapplication D/MainActivity: width = 512 , height =300
07-05 17:22:36.078 3859-3859/com.demo.myapplication D/MainActivity: screenWidth = 1196 , screenHeight =720
07-05 17:22:36.128 3859-3859/com.demo.myapplication D/MainActivity: width = 512 , height =300
07-05 17:22:51.951 3859-3859/com.demo.myapplication D/MainActivity: screenWidth = 1280 , screenHeight =720
07-05 17:22:52.002 3859-3859/com.demo.myapplication D/MainActivity: width = 512 , height =300
07-05 17:22:59.092 3859-3859/com.demo.myapplication D/MainActivity: screenWidth = 1280 , screenHeight =800
07-05 17:22:59.142 3859-3859/com.demo.myapplication D/MainActivity: width = 640 , height =360
07-05 17:23:18.623 3859-3859/com.demo.myapplication D/MainActivity: screenWidth = 1812 , screenHeight =1080
07-05 17:23:18.678 3859-3859/com.demo.myapplication D/MainActivity: width = 640 , height =400
07-05 17:23:48.159 3859-3859/com.demo.myapplication D/MainActivity: screenWidth = 1920 , screenHeight =1080
07-05 17:23:48.200 3859-3859/com.demo.myapplication D/MainActivity: width = 640 , height =400
07-05 17:24:16.942 3859-3859/com.demo.myapplication D/MainActivity: screenWidth = 2560 , screenHeight =1440
07-05 17:24:16.997 3859-3859/com.demo.myapplication D/MainActivity: width = 960 , height =540

但是從運(yùn)行結(jié)果上來看,效果并不理想。
screenWidth = 800 , screenHeight =480時(shí), width = 240 , height =160。
但是我定義的values-800x480下。

values-800x480
lay_x.xml
<dimen name="x240">400.0px</dimen>
lay_y.xml
<dimen name="y160">240.0px</dimen>

按定義去工作,結(jié)果應(yīng)該是 width = 400 , height =240。這里values-800x480并沒有生效。
而且wm size 2560x1440時(shí), width = 960 , height =540,匹配的是values-1920x1080下的dimen。

values-1920x1080
lay_x.xml
<dimen name="x240">960.0px</dimen>
lay_y.xml
<dimen name="y160">540.0px</dimen>
values-2560x1440
lay_x.xml
<dimen name="x240">1280.0px</dimen>
lay_y.xml
<dimen name="y160">720.0px</dimen>

分辨率限定符根本沒有按我的想象去工作?。?!
后來我找到了解釋,一直以來其實(shí)都誤用了分辨率限定符。
詳細(xì)解釋可以看這里。
被誤用的屏幕分辨率限定符
http://www.itdecent.cn/p/b0253e457031

這個(gè)方案后面基本上也沒有人推薦了,基本上退出歷史舞臺了。

現(xiàn)在來看看以density為變量為例進(jìn)行ui適配。這種方案最近比較火,由 字節(jié)跳動技術(shù)團(tuán)隊(duì) 提出。
https://mp.weixin.qq.com/s/d9QCoBP6kV9VSWvVldVVwA

可以參考這個(gè)鏈接,簡單看看下效果。
Android今日頭條UI適配完善版
http://www.itdecent.cn/p/41930fde7aac
這個(gè)方案從豎屏測試結(jié)果上來看,效果還不錯(cuò)。不過橫屏仍需要另外布局。

上面介紹了屏幕分辨率和density適配ui2種方法,其實(shí)還有一種寫法,就是把TextView嵌套進(jìn)LinearLayout然后通過android:layout_weight這個(gè)屬性讓其控件大小自適應(yīng)。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="2">

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1" />

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="2">

            <TextView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="@color/colorPrimary"
                android:gravity="center"
                android:text="test" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />
</LinearLayout>

200x200和800x800分辨率時(shí)運(yùn)行結(jié)果分別如下圖所示。


200x200
800x800

運(yùn)行結(jié)果控件大小雖然效果一致,但是字體大小效果不一致,可以代碼中動態(tài)調(diào)節(jié),或者去定義 不同dimens.xml 文件。
先來看看通過代碼去適配的做法。
大致思路是以200x200為基準(zhǔn),設(shè)置一個(gè)大小,例如12sp.
在values下新建一個(gè)dimens文件。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <dimen name="text_size">12sp</dimen>
</resources>

然后再獲取屏幕分辨率,例如800x800,去計(jì)算放大或縮小比例,例如這里是倍數(shù)是4,通過setTextSize設(shè)置大小為48sp。

        DisplayMetrics dm = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getRealMetrics(dm);
        int screenWidth = dm.widthPixels;
        int screenHeight = dm.heightPixels;
        Log.d(TAG,"screenWidth = " + screenWidth + " , screenHeight =" + screenHeight);

        int dimen = getResources().getDimensionPixelSize(R.dimen.text_size);
        if (screenWidth != 200){
            dimen = dimen * screenWidth/200;
            Log.d(TAG,"dimen = " + dimen);
        }
        mTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX,dimen);

運(yùn)行結(jié)果如下,字體變大了。



看完代碼中動態(tài)調(diào)節(jié)的方式,下面來看看通過定義不同dimens.xml 文件去適配的方式。
除了前面的 根據(jù) 屏幕分辨率限定符 定義 dimens,還可以用另一種方式定義dimens,那就是 android中的 sw(smallestWidth)限定符 (這個(gè)限定符android官網(wǎng)是有介紹的)。
下面來介紹下給 smallestWidth(最小寬度) 限定符方式定義dimens.xml 文件,這種方式定義dimens可以使apk體積更小。
先看看什么是smallestWidth。
android中定義了配置修飾符-sw。例如-sw600dp。

//以下部分出處:android編程權(quán)威指南
配置修飾符-sw600dp的作用是:如果設(shè)備尺寸大于某個(gè)指定值,就使用對應(yīng)的資源文件。
sw是smallest width(最小寬度)的縮寫。雖然字面上是寬度的含義,但它實(shí)際指的是屏幕最小尺寸(dimension),因而sw與設(shè)備的當(dāng)前方向無關(guān)。
在確定可選資源時(shí),-sw600dp配置修飾符表明:對任何最小尺寸為600dp或更高dp的設(shè)備,都使用該資源。

在res目錄下新建values-sw800dp文件夾,再新建一個(gè)dimens文件。


image.png
    <dimen name="text_size">48sp</dimen>

在TextView中增加屬性 android:textSize="@dimen/text_size",當(dāng)屏幕分辨率為800x800時(shí)(這里是隨便測試用的分辨率),android會自動適配values-sw800dp下的dimens文件。

 <TextView
                android:id="@+id/test_text_view"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="@color/colorPrimary"
                android:gravity="center"
                android:text="test"
                android:textSize="@dimen/text_size"/>

這種最后運(yùn)行結(jié)果與通過代碼去適配一致。

這種sw限定符 相對于 屏幕分辨率限定符 ,所需的dimens.xml文件更少,匹配度也更好一些。
因此這種sw限定符 的方法廣泛使用。
但是上面的這種定義xml文件的方法,布局嵌套層級太深了,會增加渲染層次,致使性能下降。針對這種情況google為我們提供了一個(gè)百分比布局兼容庫:Android Percent Support Library。


但是后面這個(gè)方法又被廢棄了,推薦我們?nèi)ナ褂肅onstraintLayout 。

所以我們到底是為了什么一直在學(xué)習(xí)這種要被廢棄的東西??我們一直在不停地使用輪子,然后再這個(gè)過程中無數(shù)的輪子都被拋棄了。如果只會用輪子我們還剩什么?
em。。還是要學(xué)會思想,原理,理論結(jié)合實(shí)踐,轉(zhuǎn)換成自己的東西。多寫文章,好記性不如爛筆頭。
下面來粗略看下谷歌提供的百分比庫的簡單使用。
首先在gradle中添加依賴。

implementation 'com.android.support:percent:28.0.0'

定義xml文件。

<android.support.percent.PercentRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:id="@+id/one"
        android:background="#f0f000"
        app:layout_heightPercent="30%"
        app:layout_widthPercent="70%"
        android:text="Hello World!"
         />
    <TextView
        android:layout_width="0dp"
        android:layout_toRightOf="@id/one"
        android:layout_height="0dp"
        app:layout_heightPercent="30%"
        app:layout_widthPercent="30%"
        android:background="#ff0000"
        android:text="Hello World!"
        />
    <TextView
          android:layout_width="0dp"
          android:layout_below="@id/one"
          android:layout_height="0dp"
          app:layout_heightPercent="70%"
          app:layout_widthPercent="100%"
          android:background="#ff00ff"
          android:text="Hello World!"
            />

</android.support.percent.PercentRelativeLayout>

預(yù)覽圖如下。



在3臺設(shè)備上運(yùn)行結(jié)果如下。


1
2
3

在橫豎屏中運(yùn)行效果一致??雌饋硎潜容^理想的方法。官方推了一段時(shí)間這個(gè),但是最后還是將其廢棄,原因未知。因此隨著android系統(tǒng)的更新,后續(xù)的使用會有問題。這個(gè)是別人在使用中遇到的一些問題。

關(guān)于使用百分比布局導(dǎo)致布局設(shè)定失效的記錄
https://blog.csdn.net/wzgl708937822/article/details/80880492

廢棄百分比布局兼容庫后又推出了ConstraintLayout 。到今天(2019.7.8)為止使用android studio新建project都會默認(rèn)生成以ConstraintLayout 為根節(jié)點(diǎn)的xml文件。
思考:上面寫了多種使用方法,那么到底什么時(shí)候該用什么方法呢?各自的使用場景最佳使用?

先不管了,看看ConstraintLayout (約束布局)再說。
ConstraintLayout 目前為止并沒有完全代替LinearLayout 、 RelatvieLayout等,并且學(xué)習(xí)成本較高,我經(jīng)歷過入門到放棄的過程。
而且屬性特別多,很容易迷失在一堆資料中,思緒亂掉。應(yīng)對這種情況我的解決辦法是找簡單的demo去練習(xí)(越簡單的demo越好,太復(fù)雜真的不容易入門,容易勸退的,這篇文章入門我覺得還可以。哲♂學(xué)三幻神帶你學(xué)習(xí)ConstraintLayout(約束布局) http://www.itdecent.cn/p/639fa7377cc0),然后再自己寫一遍,再對照官方文檔查屬性。
//從寫這篇文開始到寫到這里其實(shí)已經(jīng)過了一周多了雖然字不多但是真的很難憋出來

這里可以先通過使用ConstraintLayout 代替代替常見布局LinearLayout 、 RelatvieLayout 、 PercentLayout等,來簡單對比他們之間的差異。
先使用LinearLayout 、 RelatvieLayout實(shí)現(xiàn)如下兩種經(jīng)典布局。


image.png

首先使用LinearLayout 。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    tools:context=".MainActivity">
    <TextView
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:text="1"
        android:gravity="center"
        android:textSize="30sp"
        android:background="#f0f000"/>
    <TextView
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="2"
        android:text="2"
        android:gravity="center"
        android:textSize="30sp"
        android:background="#ff0000"/>
    <TextView
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:text="3"
        android:gravity="center"
        android:textSize="30sp"
        android:background="#ff00ff"/>
</LinearLayout>
image.png

這個(gè)在一個(gè)橫屏和2個(gè)豎屏設(shè)備上顯示效果基本一致。
然后再來看RelatvieLayout。
下面的xml是針對我手中分辨率為1080x1920,density為3的設(shè)備去寫的。
但是在分辨率為 720x1280,density為2的時(shí)候顯示效果也基本一致。
但是在橫屏上這個(gè)xml顯示效果就不太行了。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <TextView
        android:id="@+id/text_view_1"
        android:layout_width="match_parent"
        android:layout_height="500dp"
        android:text="1"
        android:gravity="center"
        android:textSize="30sp"
        android:background="#f0f000"/>

    <TextView
        android:id="@+id/text_view_2"
        android:layout_width="240dp"
        android:layout_height="140dp"
        android:layout_below="@id/text_view_1"
        android:text="2"
        android:gravity="center"
        android:textSize="30sp"
        android:background="#ff0000"/>
    <TextView
        android:id="@+id/text_view_3"
        android:layout_width="120dp"
        android:layout_height="140dp"
        android:layout_below="@id/text_view_1"
        android:layout_toEndOf="@id/text_view_2"
        android:text="3"
        android:gravity="center"
        android:textSize="30sp"
        android:background="#ff00ff"/>
</RelativeLayout>

運(yùn)行結(jié)果如下。


豎屏
橫屏

PercentLayout這里就不用看了,前文有寫?,F(xiàn)在就來看ConstraintLayout 要怎么去寫這2種布局。
先看LinearLayout 。
首先在根目錄下寫上ConstraintLayout,然后切到design界面。
選中TextView控件,拖到3個(gè)TextView下面的區(qū)域區(qū)中去。



image.png

添加2條Guideline。


image.png

選中Guideline,可以直接設(shè)置layout_constraintGuide_percent。



填0.25就是25%的位置。


image.png

或者可以選中Guideline,點(diǎn)擊Cycle Guideline,切到百分比。


image.png
image.png

然后分別手動拖到25%和75%的地方。


然后把最左邊的TextView控件的右邊連到25%那條Guideline,最右邊的TextView控件的左邊連到75%那條Guideline,中間的TextView控件左右分別連到25%和75%的Guideline,其余部分連離各自最近的邊框。


image.png

然后再設(shè)置每個(gè)TextView控件大小以及邊距。修改箭頭所示部分。


image.png

修改后如下。


image.png

然后每個(gè)都這樣改,最后為了更好區(qū)分,給TextView加上背景色并且調(diào)大字體,并且把字改成123。

最終xml文件如下所示。

<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/textView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="#f0f000"
        android:text="1"
        android:textSize="30sp"
        android:gravity="center"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/guideline"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="#ff0000"
        android:text="2"
        android:textSize="30sp"
        android:gravity="center"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/guideline2"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="@+id/guideline"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textView3"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="#ff00ff"
        android:text="3"
        android:textSize="30sp"
        android:gravity="center"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="@+id/guideline2"
        app:layout_constraintTop_toTopOf="parent" />

    <android.support.constraint.Guideline
        android:id="@+id/guideline"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.25" />

    <android.support.constraint.Guideline
        android:id="@+id/guideline2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.75" />
</android.support.constraint.ConstraintLayout>

在3臺設(shè)備上運(yùn)行結(jié)果如下所示。

1
2

3

這個(gè)布局與LinearLayout 相比,寫的過程中沒感覺到有什么優(yōu)勢,可能勝在性能吧。
為了減少代碼量,一般這種統(tǒng)一風(fēng)格的屬性,可以通過定義一個(gè)style,讓TextView等控件去引用就好。
在values下的styles.xml文件中,定義如下style。

    <style name="style_main_text">
        <item name="android:gravity">center</item>
        <item name="android:textSize">30sp</item>
    </style>

然后再在TextView中加上一句

style="@style/style_main_text"

再來看RelativeLayout 。
新建一個(gè)horizontal方向的Guideline,設(shè)置百分比為0.78125(500/640),一個(gè)vertical方向的Guideline,設(shè)置百分比為667(240/360),再拖3個(gè)Textview,并且將其與Guideline連接起來。
最后xml文件如下。

<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">


    <TextView
        android:id="@+id/textView1"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="#f0f000"
        android:text="1"
        android:textSize="30sp"
        android:gravity="center"
        app:layout_constraintBottom_toTopOf="@+id/guideline4"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="#ff0000"
        android:text="2"
        android:textSize="30sp"
        android:gravity="center"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/guideline11"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/guideline4" />

    <TextView
        android:id="@+id/textView3"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="#ff00ff"
        android:text="3"
        android:textSize="30sp"
        android:gravity="center"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="@+id/guideline11"
        app:layout_constraintTop_toTopOf="@+id/guideline4" />

    <android.support.constraint.Guideline
        android:id="@+id/guideline4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.78125" />

    <android.support.constraint.Guideline
        android:id="@+id/guideline11"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.6667" />

</android.support.constraint.ConstraintLayout>

從運(yùn)行結(jié)果上來看,橫豎屏效果基本一致,效果比RelativeLayout 要好。
由此可見,ConstraintLayout使用場景上來說應(yīng)該更適用于復(fù)雜布局,用來代替RelativeLayout 效果還不錯(cuò)。
總結(jié):ConstraintLayout+sw限定符方式應(yīng)該是不錯(cuò)的ui適配組合。
以上只是個(gè)人分析,具體情況具體操作。

參考鏈接:
屏幕兼容性概覽
https://developer.android.com/guide/practices/screens_support?hl=zh-CN

支持不同屏幕尺寸
https://developer.android.com/training/multiscreen/screensizes.html?hl=zh-CN#flexible-layout

Android UI 設(shè)計(jì)規(guī)范
http://www.itdecent.cn/p/b38e81be51ca

安卓UI圖px標(biāo)注適配攻略:批量轉(zhuǎn)化px為dp
https://blog.csdn.net/zengd0/article/details/52464627

Material Design
https://material.io/design/layout/understanding-layout.html#usage

iPhone不同設(shè)備屏幕尺寸和分辨率
http://www.itdecent.cn/p/0bb66e4cc732

小豬淺談Android屏幕適配
https://blog.csdn.net/coder_pig/article/details/78193938

Android adb 修改手機(jī)分辨率與DPI
https://blog.csdn.net/Jumenglo/article/details/79140071

Android中的工具欄(ActionBar和ToolBar)
http://www.itdecent.cn/p/4df8709a76fa

Android屏幕適配之百分比布局
http://www.itdecent.cn/p/0c2a8db91bda

https://developer.android.com/training/constraint-layout/index.html

百分比布局支持庫
https://mrfu.me/android/2015/08/31/percent_support_library/

屏幕適配(一)--屏幕分辨率限定
http://www.itdecent.cn/p/8a67cd0f6ccb

一種非常好用的Android屏幕適配
http://www.itdecent.cn/p/1302ad5a4b04

Android 屏幕適配:最全面的解決方案
http://www.itdecent.cn/p/ec5a1a30694b

被誤用的屏幕分辨率限定符
http://www.itdecent.cn/p/b0253e457031

Android今日頭條UI適配完善版
http://www.itdecent.cn/p/41930fde7aac

哲♂學(xué)三幻神帶你學(xué)習(xí)ConstraintLayout(約束布局)
http://www.itdecent.cn/p/639fa7377cc0

[譯] ConstraintLayout深入系列之代替常見布局
https://biaomingzhong.github.io/2017/constraintlayout-layouts-common/

https://developer.android.com/reference/android/support/constraint/ConstraintLayout

ConstraintLayout使用指南
http://tinycoder.cc/2018/06/01/ConstraintLayout%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容