Android 屏幕適配方案

轉(zhuǎn)載請標(biāo)明出處:
http://blog.csdn.net/lmj623565791/article/details/45460089赊瞬;
本文出自:【張鴻洋的博客】

1恐疲、概述

大家在Android開發(fā)時,肯定會覺得屏幕適配是個尤其痛苦的事玄窝,各種屏幕尺寸適配起來蛋疼無比顷锰。如果我們換個角度我們看下這個問題叫胁,不知道大家有沒有了解過web前端開發(fā),或者說大家對于網(wǎng)頁都不陌生吧,其實適配的問題在web頁面的設(shè)計中理論上也存在钳榨,為什么這么說呢舰罚?電腦的顯示器的分辨率、包括手機分辨率薛耻,我敢說分辨率的種類遠(yuǎn)超過Android設(shè)備的分辨率营罢,那么有一個很奇怪的現(xiàn)象:

為什么Web頁面設(shè)計人員從來沒有說過,尼瑪適配好麻煩饼齿?

那么饲漾,到底是什么原因,讓網(wǎng)頁的設(shè)計可以在千差萬別的分辨率的分辨率中依舊能給用戶一個優(yōu)質(zhì)的體驗?zāi)睾蛐眩繋е@個疑惑能颁,我問了下媳婦(前端人員),媳婦睜大眼睛問我:什么叫適配倒淫?fc伙菊,尼瑪,看來的確沒有這類問題敌土。后來再我仔細(xì)的追問后镜硕,她告訴我,噢返干,這個尺寸呀兴枯,我都是設(shè)置為20%的~~追根到底,其實就是一個原因矩欠,網(wǎng)頁提供了百分比計算大小财剖。

同樣的,大家拿到UI給的設(shè)計圖以后癌淮,是不是抱怨過尼瑪你標(biāo)識的都是px躺坟,我項目里面用dp,這什么玩意乳蓄,和UI人員解釋咪橙,UI妹妹也不理解。那么本例同樣可以解決Android工程師和UI妹妹間的矛盾UI給出一個固定尺寸的設(shè)計稿虚倒,然后你在編寫布局的時候不用思考美侦,無腦照抄上面標(biāo)識的像素值,就能達(dá)到完美適配魂奥,理想豐不豐滿~菠剩。

然而,Android對于不同的屏幕給出的適配方案是dp捧弃,那么dp與百分比的差距到底在哪里赠叼?

2擦囊、dp vs 百分比

  • dp

我們首先看下dp的定義:

Density-independent pixel (dp)獨立像素密度。標(biāo)準(zhǔn)是160dip.即1dp對應(yīng)1個pixel嘴办,計算公式如:px = dp * (dpi / 160)瞬场,屏幕密度越大,1dp對應(yīng) 的像素點越多涧郊。
上面的公式中有個dpi贯被,dpi為DPI是Dots Per Inch(每英寸所打印的點數(shù)),也就是當(dāng)設(shè)備的dpi為160的時候1px=1dp妆艘;

好了彤灶,上述這些概念記不記得住沒關(guān)系,只要記住一點dp是與像素?zé)o關(guān)的批旺,在實際使用中1dp大約等于1/160inch幌陕。

那么dp究竟解決了適配上的什么問題?可以看出1dp = 1/160inch汽煮;那么它至少能解決一個問題搏熄,就是你在布局文件寫某個View的寬和高為160dp*160dp,這個View在任何分辨率的屏幕中暇赤,顯示的尺寸大小是大約是一致的(可能不精確)心例,大概是 1 inch * 1 inch。

但是鞋囊,這樣并不能夠解決所有的適配問題:

  • 呈現(xiàn)效果仍舊會有差異止后,僅僅是相近而已
  • 當(dāng)設(shè)備的物理尺寸存在差異的時候,dp就顯得無能為力了溜腐。為4.3寸屏幕準(zhǔn)備的UI译株,運行在5.0寸的屏幕上,很可能在右側(cè)和下側(cè)存在大量的空白挺益。而5.0寸的UI運行到4.3寸的設(shè)備上古戴,很可能顯示不下。

以上兩點矩肩,來自參考鏈接1

一句話,總結(jié)下肃续,dp能夠讓同一數(shù)值在不同的分辨率展示出大致相同的尺寸大小黍檩。但是當(dāng)設(shè)備的尺寸差異較大的時候,就無能為力了始锚。適配的問題還需要我們自己去做刽酱,于是我們可能會這么做:

<?xml version="1.0" encoding="utf-8"?>  
<resources>  
    <!-- values-hdpi 480X800 -->  
    <dimen name="imagewidth">120dip</dimen>      
</resources>  

<resources>  
    <!-- values-hdpi-1280x800 -->  
    <dimen name="imagewidth">220dip</dimen>      
</resources>  

<?xml version="1.0" encoding="utf-8"?>  
<resources>  
    <!-- values-hdpi  480X320 -->  
    <dimen name="imagewidth">80dip</dimen>      
</resources> 

上述代碼片段來自網(wǎng)絡(luò),也就是說瞧捌,我們?yōu)榱藘?yōu)質(zhì)的用戶體驗棵里,依然需要去針對不同的dpi設(shè)置润文,編寫多套數(shù)值文件。

可以看出殿怜,dp并沒有能解決適配問題典蝌。下面看百分比。

  • 百分比
    這個概念不用說了头谜,web中支持控件的寬度可以去參考父控件的寬度去設(shè)置百分比骏掀,最外層控件的寬度參考屏幕尺寸設(shè)置百分比,那么其實中Android設(shè)備中柱告,只需要支持控件能夠參考屏幕的百分比去計算寬高就足夠了截驮。

比如,我現(xiàn)在以下幾個需求:

  • 對于圖片展示的Banner际度,為了起到該有的效果葵袭,我希望在任何手機上顯示的高度為屏幕高度的1/4
  • 我的首頁分上下兩欄,我希望每個欄目的屏幕高度為11/24乖菱,中間間隔為1/12
  • slidingmenu的寬度為屏幕寬度的80%

當(dāng)然了這僅僅是從一個大的層面上來說坡锡,其實小范圍布局,可能百分比將會更加有用块请。

那么現(xiàn)在不支持百分比娜氏,實現(xiàn)上述的需求,可能需要1墩新、代碼去動態(tài)計算(很多人直接pass了贸弥,太麻煩);2、利用weight(weight必須依賴Linearlayout海渊,而且并不能適用于任何場景)

再比如:我的某個浮動按鈕的高度和寬度希望是屏幕高度的1/12绵疲,我的某個Button的寬度希望是屏幕寬度的1/3。

上述的所有的需求臣疑,利用dp是無法完成的盔憨,我們希望控件的尺寸可以按照下列方式編寫:

   <Button
        android:text="@string/hello_world"
        android:layout_width="20%w"
        android:layout_height="10%h"/>

利用屏幕的寬和高的比例去定義View的寬和高。

好了讯沈,到此我們可以看到dp與百分比的區(qū)別郁岩,而百分比能夠更好的解決我們的適配問題。

  • some 適配tips

我們再來看看一些適配的tips

  1. 多用match_parent
  2. 多用weight
  3. 自定義view解決

其實上述3點tip缺狠,歸根結(jié)底還是利用百分比问慎,match_parent相當(dāng)于100%參考父控件;weight即按比例分配挤茄;自定義view無非是因為里面多數(shù)尺寸是按照百分比計算的如叼;

通過這些tips,我們更加的看出如果能在Android中引入百分比的機制穷劈,將能解決大多數(shù)的適配問題笼恰,下面我們就來看看如何能夠讓Android支持百分比的概念踊沸。

3、百分比的引入

1社证、引入

其實我們的解決方案逼龟,就是在項目中針對你所需要適配的手機屏幕的分辨率各自簡歷一個文件夾。

如下圖:

image

然后我們根據(jù)一個基準(zhǔn)猴仑,為基準(zhǔn)的意思就是:

比如480*320的分辨率為基準(zhǔn)

  • 寬度為320审轮,將任何分辨率的寬度分為320份,取值為x1-x320
  • 高度為480辽俗,將任何分辨率的高度分為480份疾渣,取值為y1-y480

例如對于800*480的寬度480:

image

可以看到x1 = 480 / 基準(zhǔn) = 480 / 320 = 1.5 ;

其他分辨率類似~~
你可能會問,這么多文件崖飘,難道我們要手算榴捡,然后自己編寫?不要怕朱浴,下文會說吊圾。

那么,你可能有個疑問翰蠢,這么寫有什么好處呢项乒?

假設(shè)我現(xiàn)在需要在屏幕中心有個按鈕,寬度和高度為我們屏幕寬度的1/2梁沧,我可以怎么編寫布局文件呢檀何?

<FrameLayout >

    <Button
        android:layout_gravity="center"
        android:gravity="center"
        android:text="@string/hello_world"
        android:layout_width="@dimen/x160"
        android:layout_height="@dimen/x160"/>

</FrameLayout>

可以看到我們的寬度和高度定義為x160,其實就是寬度的50%廷支;
那么效果圖:

image

可以看到不論在什么分辨率的機型频鉴,我們的按鈕的寬和高始終是屏幕寬度的一半。

  • 對于設(shè)計圖

假設(shè)現(xiàn)在的UI的設(shè)計圖是按照480*320設(shè)計的恋拍,且上面的寬和高的標(biāo)識都是px的值垛孔,你可以直接將px轉(zhuǎn)化為x[1-320],y[1-480]施敢,這樣寫出的布局基本就可以全分辨率適配了周荐。

你可能會問:設(shè)計師設(shè)計圖的分辨率不固定怎么辦?下文會說~

  • 對于上文提出的幾個dp做不到的

你可以通過在引入百分比后僵娃,自己試試~~

好了羡藐,有個最主要的問題,我們沒有說悯许,就是分辨率這么多,尼瑪難道我們要自己計算辉阶,然后手寫先壕?

2瘩扼、自動生成工具

好了,其實這樣的文件夾手寫也可以垃僚,按照你們需要支持的分辨率集绰,然后編寫一套,以后一直使用谆棺。

當(dāng)然了栽燕,作為程序員的我們,怎么能做這么low的工作改淑,肯定要程序來實現(xiàn):

那么實現(xiàn)需要以下步驟:

  1. 分析需要的支持的分辨率

對于主流的分辨率我已經(jīng)集成到了我們的程序中碍岔,當(dāng)然對于特殊的,你可以通過參數(shù)指定朵夏。關(guān)于屏幕分辨率信息蔼啦,可以通過該網(wǎng)站查詢:http://screensiz.es/phone

  1. 編寫自動生成文件的程序

代碼如下

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

/**
 * Created by zhy on 15/5/3.
 */
public class GenerateValueFiles {

    private int baseW;
    private int baseH;

    private String dirStr = "./res";

    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";

    /**
     * {0}-HEIGHT
     */
    private final static String VALUE_TEMPLATE = "values-{0}x{1}";

    private static final String SUPPORT_DIMESION = "320,480;480,800;480,854;540,960;600,1024;720,1184;720,1196;720,1280;768,1024;800,1280;1080,1812;1080,1920;1440,2560;";

    private String supportStr = SUPPORT_DIMESION;

    public GenerateValueFiles(int baseX, int baseY, String supportStr) {
        this.baseW = baseX;
        this.baseH = baseY;

        if (!this.supportStr.contains(baseX + "," + baseY)) {
            this.supportStr += baseX + "," + baseY + ";";
        }

        this.supportStr += validateInput(supportStr);

        System.out.println(supportStr);

        File dir = new File(dirStr);
        if (!dir.exists()) {
            dir.mkdir();

        }
        System.out.println(dir.getAbsoluteFile());

    }

    /**
     * @param supportStr
     *            w,h_...w,h;
     * @return
     */
    private String validateInput(String supportStr) {
        StringBuffer sb = new StringBuffer();
        String[] vals = supportStr.split("_");
        int w = -1;
        int h = -1;
        String[] wh;
        for (String val : vals) {
            try {
                if (val == null || val.trim().length() == 0)
                    continue;

                wh = val.split(",");
                w = Integer.parseInt(wh[0]);
                h = Integer.parseInt(wh[1]);
            } catch (Exception e) {
                System.out.println("skip invalidate params : w,h = " + val);
                continue;
            }
            sb.append(w + "," + h + ";");
        }

        return sb.toString();
    }

    public void generate() {
        String[] vals = supportStr.split(";");
        for (String val : vals) {
            String[] wh = val.split(",");
            generateXmlFile(Integer.parseInt(wh[0]), Integer.parseInt(wh[1]));
        }

    }

    private void generateXmlFile(int w, int h) {

        StringBuffer sbForWidth = new StringBuffer();
        sbForWidth.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
        sbForWidth.append("<resources>");
        float cellw = w * 1.0f / baseW;

        System.out.println("width : " + w + "," + baseW + "," + cellw);
        for (int i = 1; i < baseW; i++) {
            sbForWidth.append(WTemplate.replace("{0}", i + "").replace("{1}",
                    change(cellw * i) + ""));
        }
        sbForWidth.append(WTemplate.replace("{0}", baseW + "").replace("{1}",
                w + ""));
        sbForWidth.append("</resources>");

        StringBuffer sbForHeight = new StringBuffer();
        sbForHeight.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
        sbForHeight.append("<resources>");
        float cellh = h *1.0f/ baseH;
        System.out.println("height : "+ h + "," + baseH + "," + cellh);
        for (int i = 1; i < baseH; i++) {
            sbForHeight.append(HTemplate.replace("{0}", i + "").replace("{1}",
                    change(cellh * i) + ""));
        }
        sbForHeight.append(HTemplate.replace("{0}", baseH + "").replace("{1}",
                h + ""));
        sbForHeight.append("</resources>");

        File fileDir = new File(dirStr + File.separator
                + VALUE_TEMPLATE.replace("{0}", h + "")//
                        .replace("{1}", w + ""));
        fileDir.mkdir();

        File layxFile = new File(fileDir.getAbsolutePath(), "lay_x.xml");
        File layyFile = new File(fileDir.getAbsolutePath(), "lay_y.xml");
        try {
            PrintWriter pw = new PrintWriter(new FileOutputStream(layxFile));
            pw.print(sbForWidth.toString());
            pw.close();
            pw = new PrintWriter(new FileOutputStream(layyFile));
            pw.print(sbForHeight.toString());
            pw.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

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

    public static void main(String[] args) {
        int baseW = 320;
        int baseH = 400;
        String addition = "";
        try {
            if (args.length >= 3) {
                baseW = Integer.parseInt(args[0]);
                baseH = Integer.parseInt(args[1]);
                addition = args[2];
            } else if (args.length >= 2) {
                baseW = Integer.parseInt(args[0]);
                baseH = Integer.parseInt(args[1]);
            } else if (args.length >= 1) {
                addition = args[0];
            }
        } catch (NumberFormatException e) {

            System.err
                    .println("right input params : java -jar xxx.jar width height w,h_w,h_..._w,h;");
            e.printStackTrace();
            System.exit(-1);
        }

        new GenerateValueFiles(baseW, baseH, addition).generate();
    }

}

同時我提供了jar包,默認(rèn)情況下仰猖,雙擊即可生成捏肢,使用說明:

image

下載地址見文末,內(nèi)置了常用的分辨率饥侵,默認(rèn)基準(zhǔn)為480*320鸵赫,當(dāng)然對于特殊需求,通過命令行指定即可:

例如:基準(zhǔn) 1280 * 800 躏升,額外支持尺寸:1152 * 735辩棒;4500 * 3200;

image

按照

java -jar xx.jar width height width,height_width,height

上述格式即可煮甥。

到此盗温,我們通過編寫一個工具,根據(jù)某基準(zhǔn)尺寸成肘,生成所有需要適配分辨率的values文件卖局,做到了編寫布局文件時,可以參考屏幕的分辨率双霍;在UI給出的設(shè)計圖砚偶,可以快速的按照其標(biāo)識的px單位進(jìn)行編寫布局∪髡ⅲ基本解決了適配的問題染坯。

本方案思想已經(jīng)有公司投入使用,個人認(rèn)為還是很不錯的丘逸,如果大家有更好的方案來解決屏幕適配的問題单鹿,歡迎留言探討或者直接貼出好文鏈接,大家可以將自己的經(jīng)驗進(jìn)行分享深纲,這樣才能壯大我們的隊伍~~仲锄。

注:本方案思想來自Android Day Day Up 一群的【blue-深圳】劲妙,經(jīng)其同意編寫此文,上述程序也很大程度上借鑒了其分享的源碼儒喊。在此標(biāo)識感謝镣奋,預(yù)祝其創(chuàng)業(yè)成功!

===>后期更新

Google已經(jīng)添加了百分比支持庫怀愧,詳情請看:Android 百分比布局庫(percent-support-lib) 解析與擴展

ok~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末侨颈,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子芯义,更是在濱河造成了極大的恐慌哈垢,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件毕贼,死亡現(xiàn)場離奇詭異温赔,居然都是意外死亡,警方通過查閱死者的電腦和手機鬼癣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門陶贼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人待秃,你說我怎么就攤上這事拜秧。” “怎么了章郁?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵枉氮,是天一觀的道長。 經(jīng)常有香客問我暖庄,道長聊替,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任培廓,我火速辦了婚禮惹悄,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘肩钠。我一直安慰自己泣港,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布价匠。 她就那樣靜靜地躺著当纱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪踩窖。 梳的紋絲不亂的頭發(fā)上坡氯,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機與錄音,去河邊找鬼箫柳。 笑死颓遏,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的滞时。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼滤灯,長吁一口氣:“原來是場噩夢啊……” “哼坪稽!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起鳞骤,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤窒百,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后豫尽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體篙梢,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年美旧,在試婚紗的時候發(fā)現(xiàn)自己被綠了渤滞。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡榴嗅,死狀恐怖妄呕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情嗽测,我是刑警寧澤绪励,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站唠粥,受9級特大地震影響疏魏,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜晤愧,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一大莫、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧养涮,春花似錦葵硕、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至悄谐,卻和暖如春介评,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工们陆, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留寒瓦,地道東北人。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓坪仇,卻偏偏與公主長得像杂腰,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子椅文,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容