Android分辨率適配小試牛刀
概述
大家在Android開發(fā)時,肯定會覺得屏幕適配是個尤其痛苦的事,各種屏幕尺寸適配起來蛋疼無比。如果我們換個角度我們看下這個問題,不知道大家有沒有了解過web前端開發(fā),或者說大家對于網(wǎng)頁都不陌生吧,其實適配的問題在web頁面的設計中理論上也存在,為什么這么說呢?電腦的顯示器的分辨率、包括手機分辨率,我敢說分辨率的種類遠超過Android設備的分辨率,那么有一個很奇怪的現(xiàn)象:
為什么Web頁面設計人員從來沒有說過,尼瑪適配好麻煩?
那么,到底是什么原因,讓網(wǎng)頁的設計可以在千差萬別的分辨率的分辨率中依舊能給用戶一個優(yōu)質(zhì)的體驗呢?帶著這個疑惑,我問了下媳婦(前端人員),媳婦睜大眼睛問我:什么叫適配?fc,尼瑪,看來的確沒有這類問題。后來再我仔細的追問后,她告訴我,噢,這個尺寸呀,我都是設置為20%的~~追根到底,其實就是一個原因,網(wǎng)頁提供了百分比計算大小。
同樣的,大家拿到UI給的設計圖以后,是不是抱怨過尼瑪你標識的都是px,我項目里面用dp,這什么玩意,和UI人員解釋,UI妹妹也不理解。那么本例同樣可以解決Android工程師和UI妹妹間的矛盾~UI給出一個固定尺寸的設計稿,然后你在編寫布局的時候不用思考,無腦照抄上面標識的像素值,就能達到完美適配,理想豐不豐滿~~。
然而,Android對于不同的屏幕給出的適配方案是dp,那么dp與百分比的差距到底在哪里?
dp vs 百分比
dp
我們首先看下dp的定義:
Density-independent pixel (dp)獨立像素密度。標準是160dip.即1dp對應1個pixel,計算公式如:px = dp * (dpi / 160),屏幕密度越大,1dp對應 的像素點越多。
上面的公式中有個dpi,dpi為DPI是Dots Per Inch(每英寸所打印的點數(shù)),也就是當設備的dpi為160的時候1px=1dp;
好了,上述這些概念記不記得住沒關(guān)系,只要記住一點dp是與像素無關(guān)的,在實際使用中1dp大約等于1/160inch。
那么dp究竟解決了適配上的什么問題?可以看出1dp = 1/160inch;那么它至少能解決一個問題,就是你在布局文件寫某個View的寬和高為160dp160dp,這個View在任何分辨率的屏幕中,顯示的尺寸大小是大約是一致的(可能不精確),大概是 1 inch 1 inch。
但是,這樣并不能夠解決所有的適配問題:
呈現(xiàn)效果仍舊會有差異,僅僅是相近而已
當設備的物理尺寸存在差異的時候,dp就顯得無能為力了。為4.3寸屏幕準備的UI,運行在5.0寸的屏幕上,很可能在右側(cè)和下側(cè)存在大量的空白。而5.0寸的UI運行到4.3寸的設備上,很可能顯示不下。
以上兩點,來自參考鏈接1
一句話,總結(jié)下,dp能夠讓同一數(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)絡,也就是說,我們?yōu)榱藘?yōu)質(zhì)的用戶體驗,依然需要去針對不同的dpi設置,編寫多套數(shù)值文件。
可以看出,dp并沒有能解決適配問題。下面看百分比。
百分比
這個概念不用說了,web中支持控件的寬度可以去參考父控件的寬度去設置百分比,最外層控件的寬度參考屏幕尺寸設置百分比,那么其實中Android設備中,只需要支持控件能夠參考屏幕的百分比去計算寬高就足夠了。
比如,我現(xiàn)在以下幾個需求:
對于圖片展示的Banner,為了起到該有的效果,我希望在任何手機上顯示的高度為屏幕高度的1/4
我的首頁分上下兩欄,我希望每個欄目的屏幕高度為11/24,中間間隔為1/12
slidingmenu的寬度為屏幕寬度的80%
當然了這僅僅是從一個大的層面上來說,其實小范圍布局,可能百分比將會更加有用。
那么現(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
多用match_parent
多用weight
自定義view解決
其實上述3點tip,歸根結(jié)底還是利用百分比,match_parent相當于100%參考父控件;weight即按比例分配;自定義view無非是因為里面多數(shù)尺寸是按照百分比計算的;
通過這些tips,我們更加的看出如果能在Android中引入百分比的機制,將能解決大多數(shù)的適配問題,下面我們就來看看如何能夠讓Android支持百分比的概念。
百分比的引入
1、引入
其實我們的解決方案,就是在項目中針對你所需要適配的手機屏幕的分辨率各自簡歷一個文件夾。
如下圖:
然后我們根據(jù)一個基準,為基準的意思就是:
比如480*320的分辨率為基準
寬度為320,將任何分辨率的寬度分為320份,取值為x1-x320
高度為480,將任何分辨率的高度分為480份,取值為y1-y480
例如對于800*480的寬度480:
可以看到x1 = 480 / 基準 = 480 / 320 = 1.5 ;其他分辨率類似~~
你可能會問,這么多文件,難道我們要手算,然后自己編寫?不要怕,下文會說。
那么,你可能有個疑問,這么寫有什么好處呢?
假設我現(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%;
那么效果圖:
可以看到不論在什么分辨率的機型,我們的按鈕的寬和高始終是屏幕寬度的一半。
對于設計圖
假設現(xiàn)在的UI的設計圖是按照480*320設計的,且上面的寬和高的標識都是px的值,你可以直接將px轉(zhuǎn)化為x[1-320],y[1-480],這樣寫出的布局基本就可以全分辨率適配了。
你可能會問:設計師設計圖的分辨率不固定怎么辦?下文會說~
對于上文提出的幾個dp做不到的
你可以通過在引入百分比后,自己試試~~
好了,有個最主要的問題,我們沒有說,就是分辨率這么多,尼瑪難道我們要自己計算,然后手寫?
2、自動生成工具
好了,其實這樣的文件夾手寫也可以,按照你們需要支持的分辨率,然后編寫一套,以后一直使用。
當然了,作為程序員的我們,怎么能做這么low的工作,肯定要程序來實現(xiàn):
那么實現(xiàn)需要以下步驟:
1).分析需要的支持的分辨率
對于主流的分辨率我已經(jīng)集成到了我們的程序中,當然對于特殊的,你可以通過參數(shù)指定。關(guān)于屏幕分辨率信息,可以通過該網(wǎng)站查詢:http://screensiz.es/phone| 00daae956ab82373e1f8431e7cd28c3516 |
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();
- }
- }
同時我提供了jar包,默認情況下,雙擊即可生成,使用說明:
下載地址見文末,內(nèi)置了常用的分辨率,默認基準為480*320,當然對于特殊需求,通過命令行指定即可:
例如:基準 1280 800 ,額外支持尺寸:1152 735;4500 * 3200;
按照
Java -jar xx.jar width height width,height_width,height
上述格式即可。
結(jié)束語
到此,我們通過編寫一個工具,根據(jù)某基準尺寸,生成所有需要適配分辨率的values文件,做到了編寫布局文件時,可以參考屏幕的分辨率;在UI給出的設計圖,可以快速的按照其標識的px單位進行編寫布局?;窘鉀Q了適配的問題。歡迎大家點贊和評論,也歡迎大家去作者博客評論交流。