相關閱讀:
[乾貨]2017已來,最全面試總結——這些Android面試題你一定需要
Android常用開源框架的源碼解讀套路--教你怎麼讀開源框架源碼
關注回覆:Android,iOS,PHP,js,HTML5,Python,Hadoop,c++,J2EE等關鍵字就能免費獲取學習資料視頻
前言
Android的屏幕適配一直以來都在折磨着我們Android開發者,本文將結合:
-
Google的官方權威適配文檔
-
凱子: Android屏幕適配全攻略(最權威的官方適配指導)
-
自身的思考&實踐
給你帶來一種全新、全面而邏輯清晰的Android屏幕適配思路,只要你認真閱讀,保證你能解決Android的屏幕適配問題!
目錄
Android屏幕適配解決方案
定義
使得某一元素在Android不同尺寸、不同分辨率的手機上具備相同的顯示效果
相關重要概念
屏幕尺寸
-
含義:手機對角線的物理尺寸
-
單位:英寸(inch),1英寸=2.54cm
Android手機常見的尺寸有5寸、5.5寸、6寸等等
屏幕分辨率
-
含義:手機在橫向、縱向上的像素點數總和
-
一般描述成屏幕的"寬x高”=AxB
-
含義:屏幕在橫向方向(寬度)上有A個像素點,在縱向方向
(高)有B個像素點 -
例子:1080x1920,即寬度方向上有1080個像素點,在高度方向上有1920個像素點
-
-
單位:px(pixel),1px=1像素點
UI設計師的設計圖會以px作爲統一的計量單位
-
Android手機常見的分辨率:320x480、480x800、720x1280、1080x1920
屏幕像素密度
-
含義:每英寸的像素點數
-
單位:dpi(dots per ich)
假設設備內每英寸有160個像素,那麼該設備的屏幕像素密度=160dpi
-
安卓手機對於每類手機屏幕大小都有一個相應的屏幕像素密度:
密度類型 | 代表的分辨率(px) | 屏幕像素密度(dpi) |
---|---|---|
低密度(ldpi) | 240x320 | 120 |
中密度(mdpi) | 320x480 | 160 |
高密度(hdpi) | 480x800 | 240 |
超高密度(xhdpi) | 720x1280 | 320 |
超超高密度(xxhdpi) | 1080x1920 | 480 |
屏幕尺寸、分辨率、像素密度三者關係
一部手機的分辨率是寬x高,屏幕大小是以寸爲單位,那麼三者的關係是:
三者關係示意圖
數學不太差的人應該能懂.....吧?
不懂沒關係,在這裏舉個例子:
假設一部手機的分辨率是1080x1920(px),屏幕大小是5寸,問密度是多少?
解:請直接套公式
解答過程
密度無關像素
-
含義:density-independent pixel,叫dp或dip,與終端上的實際物理像素點無關。
-
單位:dp,可以保證在不同屏幕像素密度的設備上顯示相同的效果
-
Android開發時用dp而不是px單位設置圖片大小,是Android特有的單位
-
場景:假如同樣都是畫一條長度是屏幕一半的線,如果使用px作爲計量單位,那麼在480x800分辨率手機上設置應爲240px;在320x480的手機上應設置爲160px,二者設置就不同了;如果使用dp爲單位,在這兩種分辨率下,160dp都顯示爲屏幕一半的長度。
-
-
dp與px的轉換
因爲ui設計師給你的設計圖是以px爲單位的,Android開發則是使用dp作爲單位的,那麼我們需要進行轉換:
密度類型 | 代表的分辨率(px) | 屏幕密度(dpi) | 換算(px/dp) | 比例 |
---|---|---|---|---|
低密度(ldpi) | 240x320 | 120 | 1dp=0.75px | 3 |
中密度(mdpi) | 320x480 | 160 | 1dp=1px | 4 |
高密度(hdpi) | 480x800 | 240 | 1dp=1.5px | 6 |
超高密度(xhdpi) | 720x1280 | 320 | 1dp=2px | 8 |
超超高密度(xxhdpi) | 1080x1920 | 480 | 1dp=3px | 12 |
在Android中,規定以160dpi(即屏幕分辨率爲320x480)爲基準:1dp=1px
獨立比例像素
-
含義:scale-independent pixel,叫sp或sip
-
單位:sp
-
Android開發時用此單位設置文字大小,可根據字體大小首選項進行縮放
-
推薦使用12sp、14sp、18sp、22sp作爲字體設置的大小,不推薦使用奇數和小數,容易造成精度的丟失問題;小於12sp的字體會太小導致用戶看不清
-
請把上面的概念記住,因爲下面講解都會用到!
爲什麼要進行Android屏幕適配
由於Android系統的開放性,任何用戶、開發者、OEM廠商、運營商都可以對Android進行定製,於是導致:
-
Android系統碎片化:小米定製的MIUI、魅族定製的flyme、華爲定製的EMUI等等
當然都是基於Google原生系統定製的
-
Android機型屏幕尺寸碎片化:5寸、5.5寸、6寸等等
-
Android屏幕分辨率碎片化:320x480、480x800、720x1280、1080x1920
據友盟指數顯示,統計至2015年12月,支持Android的設備共有27796種
當Android系統、屏幕尺寸、屏幕密度出現碎片化的時候,就很容易出現同一元素在不同手機上顯示不同的問題。
試想一下這麼一個場景:
爲4.3寸屏幕準備的UI設計圖,運行在5.0寸的屏幕上,很可能在右側和下側存在大量的空白;而5.0寸的UI設計圖運行到4.3寸的設備上,很可能顯示不下。
爲了保證用戶獲得一致的用戶體驗效果:
使得某一元素在Android不同尺寸、不同分辨率的手機上具備相同的顯示效果
於是,我們便需要對Android屏幕進行適配。
屏幕適配問題的本質
-
使得“佈局”、“佈局組件”、“圖片資源”、“用戶界面流程”匹配不同的屏幕尺寸
使得佈局、佈局組件自適應屏幕尺寸;
根據屏幕的配置來加載相應的UI佈局、用戶界面流程 -
使得“圖片資源”匹配不同的屏幕密度
解決方案
-
問題:如何進行屏幕尺寸匹配?
-
答:
屏幕尺寸適配解決方案
“佈局”匹配
本質1:使得佈局元素自適應屏幕尺寸
-
做法
使用相對佈局(RelativeLayout),禁用絕對佈局(AbsoluteLayout)
開發中,我們使用的佈局一般有:
-
線性佈局(Linearlayout)
-
相對佈局(RelativeLayout)
-
幀佈局(FrameLayout)
-
絕對佈局(AbsoluteLayout)
由於絕對佈局(AbsoluteLayout)適配性極差,所以極少使用。
對於線性佈局(Linearlayout)、相對佈局(RelativeLayout)和幀佈局(FrameLayout)需要根據需求進行選擇,但要記住:
-
RelativeLayout
佈局的子控件之間使用相對位置的方式排列,因爲RelativeLayout講究的是相對位置,即使屏幕的大小改變,視圖之前的相對位置都不會變化,與屏幕大小無關,靈活性很強 -
LinearLayout
通過多層嵌套LinearLayout和組合使
用"wrap_content"和"match_parent"已經可以構建出足夠複雜的佈局。但是LinearLayout無法準確地控制子視圖之間的位置關係,只能簡單的一個挨着一個地排列
所以,對於屏幕適配來說,使用相對佈局(RelativeLayout)將會是更好的解決方案
本質2:根據屏幕的配置來加載相應的UI佈局
應用場景:需要爲不同屏幕尺寸的設備設計不同的佈局
-
做法:使用限定符
-
作用:通過配置限定符使得程序在運行時根據當前設備的配置(屏幕尺寸)自動加載合適的佈局資源
-
限定符類型:
-
尺寸(size)限定符
-
最小寬度(Smallest-width)限定符
-
佈局別名
-
屏幕方向(Orientation)限定符
-
尺寸(size)限定符
-
使用場景:當一款應用顯示的內容較多,希望進行以下設置:
-
在平板電腦和電視的屏幕(>7英寸)上:實施“雙面板”模式以同時顯示更多內容
-
在手機較小的屏幕上:使用單面板分別顯示內容
-
因此,我們可以使用尺寸限定符(layout-large)通過創建一個文件
res/layout-large/main.xml
來完成上述設定:
讓系統在屏幕尺寸>7英寸時採用適配平板的雙面板佈局
反之(默認情況下)採用適配手機的單面板佈局
文件配置如下:
-
適配手機的單面板(默認)佈局:res/layout/main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <fragment android:id="@+id/headlines" android:layout_height="fill_parent" android:name="com.example.android.newsreader.HeadlinesFragment" android:layout_width="match_parent" />
</LinearLayout>
-
適配尺寸>7寸平板的雙面板佈局::res/layout-large/main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="horizontal"> <fragment android:id="@+id/headlines" android:layout_height="fill_parent" android:name="com.example.android.newsreader.HeadlinesFragment" android:layout_width="400dp" android:layout_marginRight="10dp"/> <fragment android:id="@+id/article" android:layout_height="fill_parent" android:name="com.example.android.newsreader.ArticleFragment" android:layout_width="fill_parent" /></LinearLayout>
請注意:
兩個佈局名稱均爲main.xml,只有佈局的目錄名不同:第一個佈局的目錄名爲:layout,第二個佈局的目錄名爲:layout-large,包含了尺寸限定符(large)
被定義爲大屏的設備(7寸以上的平板)會自動加載包含了large限定符目錄的佈局,而小屏設備會加載另一個默認的佈局
但要注意的是,這種方式只適合Android 3.2版本之前。
最小寬度(Smallest-width)限定符
背景:上述提到的限定符“large”具體是指多大呢?似乎沒有一個定量的指標,這便意味着可能沒辦法準確地根據當前設備的配置(屏幕尺寸)自動加載合適的佈局資源
例子:比如說large同時包含着5寸和7寸,這意味着使用“large”限定符的話我沒辦法實現爲5寸和7寸的平板電腦分別加載不同的佈局
於是,在Android 3.2及之後版本,引入了最小寬度(Smallest-width)限定符。
定義:通過指定某個最小寬度(以 dp 爲單位)來精確定位屏幕從而加載不同的UI資源
-
使用場景
你需要爲標準 7 英寸平板電腦匹配雙面板佈局(其最小寬度爲 600 dp),在手機(較小的屏幕上)匹配單面板佈局
解決方案:您可以使用上文中所述的單面板和雙面板這兩種佈局,但您應使用 sw600dp 指明雙面板佈局僅適用於最小寬度爲 600 dp 的屏幕,而不是使用 large 尺寸限定符。
sw xxxdp,即small width的縮寫,其不區分方向,即無論是寬度還是高度,只要大於 xxxdp,就採用次此佈局
例子:使用了layout-sw 600dp的最小寬度限定符,即無論是寬度還是高度,只要大於600dp,就採用layout-sw 600dp目錄下的佈局
代碼展示:
-
適配手機的單面板(默認)佈局:res/layout/main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <fragment android:id="@+id/headlines" android:layout_height="fill_parent" android:name="com.example.android.newsreader.HeadlinesFragment" android:layout_width="match_parent" /></LinearLayout>
-
適配尺寸>7寸平板的雙面板佈局:res/layout-sw600dp/main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="horizontal"> <fragment android:id="@+id/headlines" android:layout_height="fill_parent" android:name="com.example.android.newsreader.HeadlinesFragment" android:layout_width="400dp" android:layout_marginRight="10dp"/> <fragment android:id="@+id/article" android:layout_height="fill_parent" android:name="com.example.android.newsreader.ArticleFragment" android:layout_width="fill_parent" /></LinearLayout>
對於最小寬度≥ 600 dp 的設備
系統會自動加載 layout-sw600dp/main.xml(雙面板)佈局,否則系統就會選擇 layout/main.xml(單面板)佈局
(這個選擇過程是Android系統自動選擇的)
使用佈局別名
設想這麼一個場景
當你需要同時爲Android 3.2版本前和Android 3.2版本後的手機進行屏幕尺寸適配的時候,由於尺寸限定符僅用於Android 3.2版本前,最小寬度限定符僅用於Android 3.2版本後,所以這會帶來一個問題,爲了很好地進行屏幕尺寸的適配,你需要同時維護layout-sw600dp和layout-large的兩套main.xml平板佈局,如下:
適配手機的單面板(默認)佈局:res/layout/main.xml
適配尺寸>7寸平板的雙面板佈局(Android 3.2前):res/layout-large/main.xml
適配尺寸>7寸平板的雙面板佈局(Android 3.2後)res/layout-sw600dp/main.xml
最後的兩個文件的xml內容是完全相同的,這會帶來:文件名的重複從而帶來一些列後期維護的問題
於是爲了要解決這種重複問題,我們引入了“佈局別名”
還是上面的例子,你可以定義以下佈局:
-
適配手機的單面板(默認)佈局:res/layout/main.xml
-
適配尺寸>7寸平板的雙面板佈局:res/layout/main_twopanes.xml
然後加入以下兩個文件,以便進行Android 3.2前和Android 3.2後的版本雙面板佈局適配:
-
res/values-large/layout.xml(Android 3.2之前的雙面板佈局)
<resources> <item name="main" type="layout">@layout/main_twopanes</item></resources>
-
res/values-sw600dp/layout.xml(Android 3.2及之後的雙面板佈局)
<resources><item name="main" type="layout">@layout/main_twopanes
</item></resources>
注:
最後兩個文件有着相同的內容,但是它們並沒有真正去定義佈局,它們僅僅只是將main設置成了@layout/main_twopanes的別名
由於這些文件包含 large 和 sw600dp 選擇器,因此,系統會將此文件匹配到不同版本的>7寸平板上:
a. 版本低於 3.2 的平板會匹配 large的文件
b. 版本高於 3.2 的平板會匹配 sw600dp的文件
這樣兩個layout.xml都只是引用了@layout/main_twopanes,就避免了重複定義佈局文件的情況
屏幕方向(Orientation)限定符
-
使用場景:根據屏幕方向進行佈局的調整
取以下爲例子:
-
小屏幕, 豎屏: 單面板
-
小屏幕, 橫屏: 單面板
-
7 英寸平板電腦,縱向:單面板,帶操作欄
-
7 英寸平板電腦,橫向:雙面板,寬,帶操作欄
-
10 英寸平板電腦,縱向:雙面板,窄,帶操作欄
-
10 英寸平板電腦,橫向:雙面板,寬,帶操作欄
-
電視,橫向:雙面板,寬,帶操作欄
-
方法是:
-
先定義類別:單/雙面板、是否帶操作欄、寬/窄
定義在 res/layout/ 目錄下的某個 XML 文件中
-
再進行相應的匹配:屏幕尺寸(小屏、7寸、10寸)、方向(橫、縱)
使用佈局別名進行匹配
-
在 res/layout/ 目錄下的某個 XML 文件中定義所需要的佈局類別
(單/雙面板、是否帶操作欄、寬/窄)
res/layout/onepane.xml:(單面板)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <fragment android:id="@+id/headlines" android:layout_height="fill_parent" android:name="com.example.android.newsreader.HeadlinesFragment" android:layout_width="match_parent" /> </LinearLayout>
res/layout/onepane_with_bar.xml:(單面板帶操作欄)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:layout_width="match_parent" android:id="@+id/linearLayout1" android:gravity="center" android:layout_height="50dp"> <ImageView android:id="@+id/imageView1" android:layout_height="wrap_content" android:layout_width="wrap_content" android:src="@drawable/logo" android:paddingRight="30dp" android:layout_gravity="left" android:layout_weight="0" /> <View android:layout_height="wrap_content" android:id="@+id/view1" android:layout_width="wrap_content" android:layout_weight="1" /> <Button android:id="@+id/categorybutton" android:background="@drawable/button_bg" android:layout_height="match_parent" android:layout_weight="0" android:layout_width="120dp" style="@style/CategoryButtonStyle"/> </LinearLayout> <fragment android:id="@+id/headlines" android:layout_height="fill_parent" android:name="com.example.android.newsreader.HeadlinesFragment" android:layout_width="match_parent" /> </LinearLayout>
res/layout/twopanes.xml:(雙面板,寬佈局)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="horizontal"> <fragment android:id="@+id/headlines" android:layout_height="fill_parent" android:name="com.example.android.newsreader.HeadlinesFragment" android:layout_width="400dp" android:layout_marginRight="10dp"/> <fragment android:id="@+id/article" android:layout_height="fill_parent" android:name="com.example.android.newsreader.ArticleFragment" android:layout_width="fill_parent" /></LinearLayout>
res/layout/twopanes_narrow.xml:(雙面板,窄佈局)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="horizontal"> <fragment android:id="@+id/headlines" android:layout_height="fill_parent" android:name="com.example.android.newsreader.HeadlinesFragment" android:layout_width="200dp" android:layout_marginRight="10dp"/> <fragment android:id="@+id/article" android:layout_height="fill_parent" android:name="com.example.android.newsreader.ArticleFragment" android:layout_width="fill_parent" /></LinearLayout>
2.使用佈局別名進行相應的匹配
(屏幕尺寸(小屏、7寸、10寸)、方向(橫、縱))
res/values/layouts.xml:(默認佈局)
<resources> <item name="main_layout" type="layout">@layout/onepane_with_bar</item> <bool name="has_two_panes">false</bool> </resources>
可爲resources設置bool,通過獲取其值來動態判斷目前已處在哪個適配佈局
res/values-sw600dp-land/layouts.xml
(大屏、橫向、雙面板、寬-Andorid 3.2版本後)
<resources> <item name="main_layout" type="layout">@layout/twopanes</item> <bool name="has_two_panes">true</bool></resources>
res/values-sw600dp-port/layouts.xml
(大屏、縱向、單面板帶操作欄-Andorid 3.2版本後)
<resources> <item name="main_layout" type="layout">@layout/onepane</item> <bool name="has_two_panes">false</bool></resources>
res/values-large-land/layouts.xml
(大屏、橫向、雙面板、寬-Andorid 3.2版本前)
<resources> <item name="main_layout" type="layout">@layout/twopanes</item> <bool name="has_two_panes">true</bool></resources>
res/values-large-port/layouts.xml
(大屏、縱向、單面板帶操作欄-Andorid 3.2版本前)
<resources> <item name="main_layout" type="layout">@layout/onepane</item> <bool name="has_two_panes">false</bool></resources>
這裏沒有完全把全部尺寸匹配類型的代碼貼出來,大家可以自己去嘗試把其補充完整
“佈局組件”匹配
本質:使得佈局組件自適應屏幕尺寸
-
做法
使用"wrap_content"、"match_parent"和"weight“來控制視圖組件的寬度和高度-
"wrap_content"
相應視圖的寬和高就會被設定成所需的最小尺寸以適應視圖中的內容 -
"match_parent"(在Android API 8之前叫作"fill_parent")
視圖的寬和高延伸至充滿整個父佈局 -
"weight"
1.定義:是線性佈局(Linelayout)的一個獨特比例分配屬性
2.作用:使用此屬性設置權重,然後按照比例對界面進行空間的分配,公式計算是:控件寬度=控件設置寬度+剩餘空間所佔百分比寬幅
通過使用"wrap_content"、"match_parent"和"weight"來替代硬編碼的方式定義視圖大小&位置,你的視圖要麼僅僅使用了需要的那邊一點空間,要麼就會充滿所有可用的空間,即按需佔據空間大小,能讓你的佈局元素充分適應你的屏幕尺寸
-
“圖片資源”匹配
本質:使得圖片資源在不同屏幕密度上顯示相同的像素效果
-
做法:使用自動拉伸位圖:Nine-Patch的圖片類型
假設需要匹配不同屏幕大小,你的圖片資源也必須自動適應各種屏幕尺寸
使用場景:一個按鈕的背景圖片必須能夠隨着按鈕大小的改變而改變。
使用普通的圖片將無法實現上述功能,因爲運行時會均勻地拉伸或壓縮你的圖片
-
解決方案:使用自動拉伸位圖(nine-patch圖片),後綴名是.9.png,它是一種被特殊處理過的PNG圖片,設計時可以指定圖片的拉伸區域和非拉伸區域;使用時,系統就會根據控件的大小自動地拉伸你想要拉伸的部分
1.必須要使用.9.png後綴名,因爲系統就是根據這個來區別nine-patch圖片和普通的PNG圖片的;
2.當你需要在一個控件中使用nine-patch圖片時,如
android:background="@drawable/button"
系統就會根據控件的大小自動地拉伸你想要拉伸的部分