很久沒寫部落格了,最近在研究pad端開發的時候,螢幕适配着實把我整得很惱火,很難過,特此整理了此篇文章來整螢幕适配。廢話不說,直接開整。
參考:
- Google的官方權威适配文檔
- 郭霖: Android官方提供的支援不同螢幕大小的全部方法
- Stormzhang: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開發時用此機關設定文字大小,可根據字型大小首選項進行縮放,因為sp才會随着系統字型大小而改變!
- 推薦使用12sp、14sp、18sp、22sp作為字型設定的大小,不推薦使用奇數和小數,容易造成精度的丢失問題;小于12sp的字型會太小導緻使用者看不清
在設計圖示時,對于五種主流的像素密度(MDPI、HDPI、XHDPI、XXHDPI 和 XXXHDPI)應按照 2:3:4:6:8 的比例進行縮放。例如,一個啟動圖示的尺寸為48x48 dp,這表示在 MDPI 的螢幕上其實際尺寸應為 48x48 px,在 HDPI 的螢幕上其實際大小是 MDPI 的 1.5 倍 (72x72 px),在 XDPI 的螢幕上其實際大小是 MDPI 的 2 倍 (96x96 px),依此類推。
雖然 Android 也支援低像素密度 (LDPI) 的螢幕,但無需為此費神,系統會自動将 HDPI 尺寸的圖示縮小到 1/2 進行比對。
下圖為圖示的各個螢幕密度的對應尺寸
螢幕密度 | 圖示尺寸 |
mdpi | 48x48px |
hdpi | 72x72px |
xhdpi | 96x96px |
xxhdpi | 144x144px |
xxxhdpi | 192x192px |
“布局”比對
本質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.作用:使用此屬性設定權重,然後按照比例對界面進行空間的配置設定,公式計算是:控件寬度=控件設定寬度+剩餘空間所占百分比寬幅
-
“圖檔資源”比對
本質:使得圖檔資源在不同螢幕密度上顯示相同的像素效果
-
做法:使用自動拉伸位圖:Nine-Patch的圖檔類型
假設需要比對不同螢幕大小,你的圖檔資源也必須自動适應各種螢幕尺寸
使用場景:一個按鈕的背景圖檔必須能夠随着按鈕大小的改變而改變。
使用普通的圖檔将無法實作上述功能,因為運作時會均勻地拉伸或壓縮你的圖檔
- 解決方案:使用自動拉伸位圖(nine-patch圖檔),字尾名是.9.png,它是一種被特殊處理過的PNG圖檔,設計時可以指定圖檔的拉伸區域和非拉伸區域;使用時,系統就會根據控件的大小自動地拉伸你想要拉伸的部分
1.必須要使用.9.png字尾名,因為系統就是根據這個來差別nine-patch圖檔和普通的PNG圖檔的;
2.當你需要在一個控件中使用nine-patch圖檔時,如
系統就會根據控件的大小自動地拉伸你想要拉伸的部分
android:background="@drawable/button"
寫在後面:一般我們現在做手機适配做H X XX三套圖就行了。讓UI的原型最好是XH 的原型圖,一般情況他給的尺寸/2我們就能用了,坑逼的UI給的iphone6的尺寸的話最好拿過來自己改一下圖的寬度再測量。pad端适配比手機還坑,最好和老闆商量先适配幾個主流的,布局多用權重。點9圖得放在
drawable檔案夾下。