編輯:關於Android編程
大家在Android開發時,肯定會覺得屏幕適配是個尤其痛苦的事,各種屏幕尺寸適配起來蛋疼無比。如果我們換個角度我們看下這個問題,不知道大家有沒有了解過web前端開發,或者說大家對於網頁都不陌生吧,其實適配的問題在web頁面的設計中理論上也存在,為什麼這麼說呢?電腦的顯示器的分辨率、包括手機分辨率,我敢說分辨率的種類遠超過Android設備的分辨率,那麼有一個很奇怪的現象:
為什麼Web頁面設計人員從來沒有說過,尼瑪適配好麻煩?
那麼,到底是什麼原因,讓網頁的設計可以在千差萬別的分辨率的分辨率中依舊能給用戶一個優質的體驗呢?帶著這個疑惑,我問了下媳婦(前端人員),媳婦睜大眼睛問我:什麼叫適配?fc,尼瑪,看來的確沒有這類問題。後來再我仔細的追問後,她告訴我,噢,這個尺寸呀,我都是設置為20%的~~追根到底,其實就是一個原因,網頁提供了百分比計算大小。
同樣的,大家拿到UI給的設計圖以後,是不是抱怨過尼瑪你標識的都是px,我項目裡面用dp,這什麼玩意,和UI人員解釋,UI妹妹也不理解。那麼本例同樣可以解決Android工程師和UI妹妹間的矛盾~UI給出一個固定尺寸的設計稿,然後你在編寫布局的時候不用思考,無腦照抄上面標識的像素值,就能達到完美適配,理想豐不豐滿~~。
然而,Android對於不同的屏幕給出的適配方案是dp,那麼dp與百分比的差距到底在哪裡?
我們首先看下dp的定義:
Density-independent pixel (dp)獨立像素密度。標准是160dip.即1dp對應1個pixel,計算公式如:px = dp * (dpi / 160),屏幕密度越大,1dp對應 的像素點越多。
上面的公式中有個dpi,dpi為DPI是Dots Per Inch(每英寸所打印的點數),也就是當設備的dpi為160的時候1px=1dp;
好了,上述這些概念記不記得住沒關系,只要記住一點dp是與像素無關的,在實際使用中1dp大約等於1/160inch。
那麼dp究竟解決了適配上的什麼問題?可以看出1dp = 1/160inch;那麼它至少能解決一個問題,就是你在布局文件寫某個View的寬和高為160dp*160dp,這個View在任何分辨率的屏幕中,顯示的尺寸大小是大約是一致的(可能不精確),大概是 1 inch * 1 inch。
但是,這樣並不能夠解決所有的適配問題:
以上兩點,來自參考鏈接1
一句話,總結下,dp能夠讓同一數值在不同的分辨率展示出大致相同的尺寸大小。但是當設備的尺寸差異較大的時候,就無能為力了。適配的問題還需要我們自己去做,於是我們可能會這麼做:
120dip 220dip 80dip
上述代碼片段來自網絡,也就是說,我們為了優質的用戶體驗,依然需要去針對不同的dpi設置,編寫多套數值文件。
可以看出,dp並沒有能解決適配問題。下面看百分比。
百分比比如,我現在以下幾個需求:
當然了這僅僅是從一個大的層面上來說,其實小范圍布局,可能百分比將會更加有用。
那麼現在不支持百分比,實現上述的需求,可能需要1、代碼去動態計算(很多人直接pass了,太麻煩);2、利用weight(weight必須依賴Linearlayout,而且並不能適用於任何場景)
再比如:我的某個浮動按鈕的高度和寬度希望是屏幕高度的1/12,我的某個Button的寬度希望是屏幕寬度的1/3。
上述的所有的需求,利用dp是無法完成的,我們希望控件的尺寸可以按照下列方式編寫:
利用屏幕的寬和高的比例去定義View的寬和高。
好了,到此我們可以看到dp與百分比的區別,而百分比能夠更好的解決我們的適配問題。
我們再來看看一些適配的tips
其實上述3點tip,歸根結底還是利用百分比,match_parent相當於100%參考父控件;weight即按比例分配;自定義view無非是因為裡面多數尺寸是按照百分比計算的;
通過這些tips,我們更加的看出如果能在Android中引入百分比的機制,將能解決大多數的適配問題,下面我們就來看看如何能夠讓Android支持百分比的概念。
其實我們的解決方案,就是在項目中針對你所需要適配的手機屏幕的分辨率各自簡歷一個文件夾。
如下圖:
然後我們根據一個基准,為基准的意思就是:
比如480*320的分辨率為基准
例如對於800*480的寬度480:
可以看到x1 = 480 / 基准 = 480 / 320 = 1.5 ;
其他分辨率類似~~
你可能會問,這麼多文件,難道我們要手算,然後自己編寫?不要怕,下文會說。
那麼,你可能有個疑問,這麼寫有什麼好處呢?
假設我現在需要在屏幕中心有個按鈕,寬度和高度為我們屏幕寬度的1/2,我可以怎麼編寫布局文件呢?
<framelayout></framelayout>
可以看到我們的寬度和高度定義為x160,其實就是寬度的50%;
那麼效果圖:
可以看到不論在什麼分辨率的機型,我們的按鈕的寬和高始終是屏幕寬度的一半。
假設現在的UI的設計圖是按照480*320設計的,且上面的寬和高的標識都是px的值,你可以直接將px轉化為x[1-320],y[1-480],這樣寫出的布局基本就可以全分辨率適配了。
你可能會問:設計師設計圖的分辨率不固定怎麼辦?下文會說~
你可以通過在引入百分比後,自己試試~~
好了,有個最主要的問題,我們沒有說,就是分辨率這麼多,尼瑪難道我們要自己計算,然後手寫?
好了,其實這樣的文件夾手寫也可以,按照你們需要支持的分辨率,然後編寫一套,以後一直使用。
當然了,作為程序員的我們,怎麼能做這麼low的工作,肯定要程序來實現:
那麼實現需要以下步驟:
對於主流的分辨率我已經集成到了我們的程序中,當然對於特殊的,你可以通過參數指定。關於屏幕分辨率信息,可以通過該網站查詢:http://screensiz.es/phone
代碼如下
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 = "{1}px \n"; private final static String HTemplate = "{1}px \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("{C}\n"); sbForWidth.append(""); 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(" "); StringBuffer sbForHeight = new StringBuffer(); sbForHeight.append("{C}\n"); sbForHeight.append(""); 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(" "); 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包,默認情況下,雙擊即可生成,使用說明:
下載地址見文末,內置了常用的分辨率,默認基准為480*320,當然對於特殊需求,通過命令行指定即可:
例如:基准 1280 * 800 ,額外支持尺寸:1152 * 735;4500 * 3200;
按照
Java-jar xx.jar width height width,height_width,height
上述格式即可。
到此,我們通過編寫一個工具,根據某基准尺寸,生成所有需要適配分辨率的values文件,做到了編寫布局文件時,可以參考屏幕的分辨率;在UI給出的設計圖,可以快速的按照其標識的px單位進行編寫布局。基本解決了適配的問題。
本方案思想已經有公司投入使用,個人認為還是很不錯的,如果大家有更好的方案來解決屏幕適配的問題,歡迎留言探討或者直接貼出好文鏈接,大家可以將自己的經驗進行分享,這樣才能壯大我們的隊伍~~。
注:本方案思想來自Android Day Day Up 一群的【blue-深圳】,經其同意編寫此文,上述程序也很大程度上借鑒了其分享的源碼。在此標識感謝,預祝其創業成功!
===>後期更新
Android Scroll詳解(一):基礎知識 在前邊的文章中,我們已經對Android觸摸事件處理有了大致的了解,並且詳細探討了MotionEvent
ViewSwitcher 的作用簡單來說就是:在兩個視圖間轉換時顯示動畫它的兩個子類應該很熟悉,ImageSwitcher:轉換圖片時增加動畫效果;TextSwitche
最近的項目,有個需求需要使用條狀圖顯示比例,並且右對齊,見下圖: 我想到了使用進度條,這樣不就不需要在代碼動態繪制條狀了,省了很多活。 那麼進度條怎樣從右向左顯示呢?
1.android為什麼要簽名 所有的Android應用程序都要求開發人員用一個證書進行數字簽名,anroid系統不會安裝沒有進行簽名的由於程序。平時我們的程序可