Android 屏幕適配方案

本文出自:【張鴻洋的博客】
原文地址:http://blog.csdn.net/lmj623565791/article/details/45460089

一、概述

大家在Android開發(fā)時(shí),肯定會覺得屏幕適配是個尤其痛苦的事苏潜,各種屏幕尺寸適配起來蛋疼無比艳汽。如果我們換個角度我們看下這個問題抬驴,不知道大家有沒有了解過web前端開發(fā)秤涩,或者說大家對于網(wǎng)頁都不陌生吧陶舞,其實(shí)適配的問題在web頁面的設(shè)計(jì)中理論上也存在,為什么這么說呢撑帖?電腦的顯示器的分辨率蓉坎、包括手機(jī)分辨率,我敢說分辨率的種類遠(yuǎn)超過Android設(shè)備的分辨率胡嘿,那么有一個很奇怪的現(xiàn)象:

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

那么衷敌,到底是什么原因勿侯,讓網(wǎng)頁的設(shè)計(jì)可以在千差萬別的分辨率的分辨率中依舊能給用戶一個優(yōu)質(zhì)的體驗(yàn)?zāi)兀繋е@個疑惑缴罗,我問了下媳婦(前端人員)助琐,媳婦睜大眼睛問我:什么叫適配?fc面氓,尼瑪兵钮,看來的確沒有這類問題。后來再我仔細(xì)的追問后舌界,她告訴我矢空,噢,這個尺寸呀禀横,我都是設(shè)置為20%的~~追根到底屁药,其實(shí)就是一個原因,網(wǎng)頁提供了百分比計(jì)算大小柏锄。

同樣的酿箭,大家拿到UI給的設(shè)計(jì)圖以后,是不是抱怨過尼瑪你標(biāo)識的都是px趾娃,我項(xiàng)目里面用dp缭嫡,這什么玩意,和UI人員解釋抬闷,UI妹妹也不理解妇蛀。那么本例同樣可以解決Android工程師和UI妹妹間的矛盾。UI給出一個固定尺寸的設(shè)計(jì)稿笤成,然后你在編寫布局的時(shí)候不用思考评架,無腦照抄上面標(biāo)識的像素值,就能達(dá)到完美適配炕泳,理想豐不豐滿纵诞。

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

二登刺、dp vs 百分比

  • dp

我們首先看下dp的定義:

Density-independent pixel (dp)獨(dú)立像素密度。標(biāo)準(zhǔn)是160dip.即1dp對應(yīng)1個pixel嗡呼,計(jì)算公式如:px = dp*(dpi / 160)纸俭,屏幕密度越大,1dp對應(yīng) 的像素點(diǎn)越多南窗。
上面的公式中有個dpi掉蔬,dpi為DPI是Dots Per Inch(每英寸所打印的點(diǎn)數(shù)),也就是當(dāng)設(shè)備的dpi為160的時(shí)候1px=1dp矾瘾;好了女轿,上述這些概念記不記得住沒關(guān)系,只要記住一點(diǎn)dp是與像素?zé)o關(guān)的壕翩,在實(shí)際使用中1dp大約等于1/160inch蛉迹。

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

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

  • 呈現(xiàn)效果仍舊會有差異攘宙,僅僅是相近而已;
  • 當(dāng)設(shè)備的物理尺寸存在差異的時(shí)候拐迁,dp就顯得無能為力了蹭劈。為4.3寸屏幕準(zhǔn)備的UI,運(yùn)行在5.0寸的屏幕上线召,很可能在右側(cè)和下側(cè)存在大量的空白铺韧。而5.0寸的UI運(yùn)行到4.3寸的設(shè)備上,很可能顯示不下缓淹。

以上兩點(diǎn)哈打,來自參考鏈接1

一句話,總結(jié)下讯壶,dp能夠讓同一數(shù)值在不同的分辨率展示出大致相同的尺寸大小料仗。但是當(dāng)設(shè)備的尺寸差異較大的時(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ì)的用戶體驗(yàn)肺孵,依然需要去針對不同的dpi設(shè)置,編寫多套數(shù)值文件颜阐∑骄剑可以看出,dp并沒有能解決適配問題凳怨。下面看百分比瑰艘。

  • 百分比

這個概念不用說了,web中支持控件的寬度可以去參考父控件的寬度去設(shè)置百分比肤舞,最外層控件的寬度參考屏幕尺寸設(shè)置百分比紫新,那么其實(shí)中Android設(shè)備中,只需要支持控件能夠參考屏幕的百分比去計(jì)算寬高就足夠了李剖。

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

  • 對于圖片展示的Banner,為了起到該有的效果篙顺,我希望在任何手機(jī)上顯示的高度為屏幕高度的1/4偶芍;
  • 我的首頁分上下兩欄,我希望每個欄目的屏幕高度為11/24德玫,中間間隔為1/12匪蟀;
  • slidingmenu的寬度為屏幕寬度的80%。

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

那么現(xiàn)在不支持百分比琴儿,實(shí)現(xiàn)上述的需求查刻,可能需要:1、代碼去動態(tài)計(jì)算(很多人直接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解決

其實(shí)上述3點(diǎn)tip绵跷,歸根結(jié)底還是利用百分比膘螟,match_parent相當(dāng)于100%參考父控件成福;weight即按比例分配;自定義view無非是因?yàn)槔锩娑鄶?shù)尺寸是按照百分比計(jì)算的荆残;

通過這些tips奴艾,我們更加的看出如果能在Android中引入百分比的機(jī)制,將能解決大多數(shù)的適配問題内斯,下面我們就來看看如何能夠讓Android支持百分比的概念蕴潦。

三、百分比的引入

1俘闯、引入

其實(shí)我們的解決方案潭苞,就是在項(xiàng)目中針對你所需要適配的手機(jī)屏幕的分辨率各自簡歷一個文件夾。

如下圖:

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

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

  • 寬度為320此疹,將任何分辨率的寬度分為320份,取值為x1-x320
  • 高度為480蜜猾,將任何分辨率的高度分為480份秀菱,取值為y1-y480

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

可以看到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篡悟,其實(shí)就是寬度的50%谜叹;

那么效果圖:

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

  • 對于設(shè)計(jì)圖

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

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

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

你可以通過在引入百分比后乔外,自己試試。

好了锭碳,有個最主要的問題袁稽,我們沒有說勿璃,就是分辨率這么多擒抛,尼瑪難道我們要自己計(jì)算,然后手寫补疑?

2歧沪、自動生成工具

好了,其實(shí)這樣的文件夾手寫也可以莲组,按照你們需要支持的分辨率诊胞,然后編寫一套,以后一直使用锹杈。

當(dāng)然了撵孤,作為程序員的我們,怎么能做這么low的工作竭望,肯定要程序來實(shí)現(xiàn):

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

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

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

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

代碼如下:

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();
    }

}

同時(shí)我提供了jar包,默認(rèn)情況下旧烧,雙擊即可生成影钉,使用說明:

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

例如:基準(zhǔn) 1280*800夺谁,額外支持尺寸:1152*735廉赔;4500*3200

按照

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

上述格式即可予权。

到此昂勉,我們通過編寫一個工具,根據(jù)某基準(zhǔn)尺寸扫腺,生成所有需要適配分辨率的values文件岗照,做到了編寫布局文件時(shí),可以參考屏幕的分辨率;在UI給出的設(shè)計(jì)圖攒至,可以快速的按照其標(biāo)識的px單位進(jìn)行編寫布局厚者。基本解決了適配的問題迫吐。

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

下載地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末烫止,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子戳稽,更是在濱河造成了極大的恐慌馆蠕,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件惊奇,死亡現(xiàn)場離奇詭異互躬,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)颂郎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門吼渡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人祖秒,你說我怎么就攤上這事诞吱。” “怎么了竭缝?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵房维,是天一觀的道長。 經(jīng)常有香客問我抬纸,道長咙俩,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任湿故,我火速辦了婚禮阿趁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘坛猪。我一直安慰自己脖阵,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布墅茉。 她就那樣靜靜地躺著命黔,像睡著了一般呜呐。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上悍募,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天蘑辑,我揣著相機(jī)與錄音,去河邊找鬼坠宴。 笑死洋魂,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的喜鼓。 我是一名探鬼主播副砍,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼颠通!你這毒婦竟也來了址晕?” 一聲冷哼從身側(cè)響起膀懈,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤顿锰,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后启搂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體硼控,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年胳赌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了牢撼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡疑苫,死狀恐怖熏版,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情捍掺,我是刑警寧澤撼短,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站挺勿,受9級特大地震影響曲横,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜不瓶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一禾嫉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蚊丐,春花似錦熙参、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽讲竿。三九已至,卻和暖如春弄屡,著一層夾襖步出監(jiān)牢的瞬間题禀,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工膀捷, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留迈嘹,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓全庸,卻偏偏與公主長得像秀仲,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子壶笼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355

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