本文出自:【張鴻洋的博客】
原文地址: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:
- 多用match_parent
- 多用weight
- 自定義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ì)伍~~溉浙。