目錄
前言
目錄圖
定義
相關重要概念
螢幕尺寸
螢幕分辨率
螢幕像素密度
螢幕尺寸、分辨率、像素密度三者關系
不懂沒關系,在這裡舉個例子
密度無關像素
獨立比例像素
為什麼要進行Android螢幕适配
為了保證使用者獲得一緻的使用者體驗效果:
螢幕适配問題的本質
解決方案
“布局”比對
本質1:使得布局元素自适應螢幕尺寸
本質2:根據螢幕的配置來加載相應的UI布局
限定符類型:
尺寸(size)限定符
最小寬度(Smallest-width)限定符
使用布局别名
螢幕方向(Orientation)限定符
“布局元件”比對
本質:使得布局元件自适應螢幕尺寸
“圖檔資源”比對
”使用者界面流程“比對
本質:根據螢幕的配置來加載相應的使用者界面流程
總結
解決方案
“布局控件”比對
本質:使得布局元件在不同螢幕密度上顯示相同的像素效果
那麼該如何解決控件的螢幕尺寸和螢幕密度的适配問題呢?
步驟1:以某一分辨率為基準,生成所有分辨率對應像素數清單
步驟2:把生成的各像素數清單放到對應的資源檔案
步驟3:根據UI設計師給出某一分辨率設計圖上的尺寸,找到對應像素數的機關,然後設定給控件即可
總結
“圖檔資源”比對
本質:使得圖檔資源在不同螢幕密度上顯示相同的像素效果
更好地方案解決“圖檔資源”适配問題
方法介紹
1. 先來了解下Android 加載資源過程
2. xhdpi應該是首選
額外小tips
前言
Android的螢幕适配一直以來都在折磨着我們Android開發者,本文将結合:
- Google的官方權威适配文檔
- 郭霖: Android官方提供的支援不同螢幕大小的全部方法
- 鴻洋:Android 螢幕适配方案
- 凱子:Android螢幕适配全攻略(最權威的官方适配指導)
- 自身的思考&實踐
給你帶來一種全新、全面而邏輯清晰的Android螢幕适配思路,隻要你認真閱讀,保證你能解決Android的螢幕适配問題!
目錄圖
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI2EzX4xSZz91ZsAzNfRHLGZkRGZkRfJ3bs92YsAjMfVmepNHLHdET2IVZZZTRxVTNR1GT1IXLiVTQClGVF5UMR9Fd4VGdsATNfd3bkFGazxycykFaKdkYzZUbapXNXlleSdVY2pESa9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnLzQzM4MDMzETM4ATOwEjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
定義
使得某一進制素在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後的版本雙面闆布局适配:
1.res/values-large/layout.xml(Android 3.2之前的雙面闆布局)
<resources>
<item name="main" type="layout">@layout/main_twopanes</item>
</resources>
2.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寸)、方向(橫、縱)
使用布局别名進行比對
1.在 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"
系統就會根據控件的大小自動地拉伸你想要拉伸的部分
”使用者界面流程“比對
- 使用場景:我們會根據裝置特點顯示恰當的布局,但是這樣做,會使得使用者界面流程可能會有所不同。
- 例如,如果應用處于雙面闆模式下,點選左側面闆上的項即可直接在右側面闆上顯示相關内容;而如果該應用處于單面闆模式下,點選相關的内容應該跳轉到另外一個Activity進行後續的處理。
本質:根據螢幕的配置來加載相應的使用者界面流程
-
做法
進行使用者界面流程的自适應配置:
- 确定目前布局
- 根據目前布局做出響應
- 重複使用其他活動中的片段
- 處理螢幕配置變化
- 步驟1:确定目前布局
由于每種布局的實施都會稍有不同,是以我們需要先确定目前向使用者顯示的布局。例如,我們可以先了解使用者所處的是“單面闆”模式還是“雙面闆”模式。要做到這一點,可以通過查詢指定視圖是否存在以及是否已顯示出來。
public class NewsReaderActivity extends FragmentActivity {
boolean mIsDualPane;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_layout);
View articleView = findViewById(R.id.article);
mIsDualPane = articleView != null &&
articleView.getVisibility() == View.VISIBLE;
}
}
這段代碼用于查詢“報道”面闆是否可用,與針對具體布局的寫死查詢相比,這段代碼的靈活性要大得多。
- 步驟2:根據目前布局做出響應
有些操作可能會因目前的具體布局而産生不同的結果。
例如,在新聞閱讀器示例中,如果使用者界面處于雙面闆模式下,那麼點選标題清單中的标題就會在右側面闆中打開相應報道;但如果使用者界面處于單面闆模式下,那麼上述操作就會啟動一個獨立活動:
@Override
public void onHeadlineSelected(int index) {
mArtIndex = index;
if (mIsDualPane) {
/* display article on the right pane */
mArticleFragment.displayArticle(mCurrentCat.getArticle(index));
} else {
/* start a separate activity */
Intent intent = new Intent(this, ArticleActivity.class);
intent.putExtra("catIndex", mCatIndex);
intent.putExtra("artIndex", index);
startActivity(intent);
}
}
- 步驟3:重複使用其他活動中的片段
多螢幕設計中的重複模式是指,對于某些螢幕配置,已實施界面的一部分會用作面闆;但對于其他配置,這部分就會以獨立活動的形式存在。
例如,在新聞閱讀器示例中,對于較大的螢幕,新聞報道文本會顯示在右側面闆中;但對于較小的螢幕,這些文本就會以獨立活動的形式存在。
在類似情況下,通常可以在多個活動中重複使用相同的 Fragment 子類以避免代碼重複。例如,在雙面闆布局中使用了 ArticleFragment:
<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>
然後又在小螢幕的Activity布局中重複使用了它 :
ArticleFragment frag = new ArticleFragment();
getSupportFragmentManager().beginTransaction().add(android.R.id.content, frag).commit();
- 步驟4:處理螢幕配置變化
如果我們使用獨立Activity實施界面的獨立部分,那麼請注意,我們可能需要對特定配置變化(例如螢幕方向的變化)做出響應,以便保持界面的一緻性。
例如,在運作 Android 3.0 或更高版本的标準 7 英寸平闆電腦上,如果新聞閱讀器示例應用運作在縱向模式下,就會在使用獨立活動顯示新聞報道;但如果該應用運作在橫向模式下,就會使用雙面闆布局。
也就是說,如果使用者處于縱向模式下且螢幕上顯示的是用于閱讀報道的活動,那麼就需要在檢測到螢幕方向變化(變成橫向模式)後執行相應操作,即停止上述活動并傳回主活動,以便在雙面闆布局中顯示相關内容:
public class ArticleActivity extends FragmentActivity {
int mCatIndex, mArtIndex;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mCatIndex = getIntent().getExtras().getInt("catIndex", 0);
mArtIndex = getIntent().getExtras().getInt("artIndex", 0);
// If should be in two-pane mode, finish to return to main activity
if (getResources().getBoolean(R.bool.has_two_panes)) {
finish();
return;
}
...
}
通過上面一系列步驟,我們就完全可以建立一個可以根據使用者界面配置進行自适應的應用程式App了。
總結
經過上面的介紹,對于螢幕尺寸大小适配問題應該是不成問題了。
解決方案
- 問題:如何進行螢幕密度比對?
- 答:
“布局控件”比對
本質:使得布局元件在不同螢幕密度上顯示相同的像素效果
-
做法1:使用密度無關像素
由于各種螢幕的像素密度都有所不同,是以相同數量的像素在不同裝置上的實際大小也有所差異,這樣使用像素(px)定義布局尺寸就會産生問題。
是以,請務必使用密度無關像素 dp 或**獨立比例像素 sp **機關指定尺寸。
-
相關概念介紹
密度無關像素
- 含義: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的字型會太小導緻使用者看不清
是以,為了能夠進行不同螢幕像素密度的比對,我們推薦:
- 使用dp來代替px作為控件長度的統一度量機關
- 使用sp作為文字的統一度量機關
可是,請看以下一種場景:
Nexus5的總寬度為360dp,我們現在在水準方向上放置兩個按鈕,一個是150dp左對齊,另外一個是200dp右對齊,那麼中間留有10dp間隔;但假如同樣地設定在Nexus S(螢幕寬度是320dp),會發現,兩個按鈕會重疊,因為320dp<200+150dp
從上面可以看出,由于Android螢幕裝置的多樣性,如果使用dp來作為度量機關,并不是所有的螢幕的寬度都具備相同的dp長度
再次明确,螢幕寬度和像素密度沒有任何關聯關系
是以說,dp解決了同一數值在不同分辨率中展示相同尺寸大小的問題(即螢幕像素密度比對問題),但卻沒有解決裝置尺寸大小比對的問題。(即螢幕尺寸比對問題)
當然,我們一開始讨論的就是螢幕尺寸比對問題,使用match_parent、wrap_content和weight,盡可能少用dp來指定控件的具體長寬,大部分的情況我們都是可以做到适配的。
那麼該如何解決控件的螢幕尺寸和螢幕密度的适配問題呢?
從上面可以看出:
- 因為螢幕密度(分辨率)不一樣,是以不能用固定的px
- 因為螢幕寬度不一樣,是以要小心的用dp
因為本質上是希望使得布局元件在不同螢幕密度上顯示相同的像素效果,那麼,之前是繞了個彎使用dp解決這個問題,那麼到底能不能直接用px解決呢?
即根據不同螢幕密度,控件選擇對應的像素值大小
接下來介紹一種方法:百分比适配方法,步驟如下:
- 以某一分辨率為基準,生成所有分辨率對應像素數清單
- 将生成像素數清單存放在res目錄下對應的values檔案下
- 根據UI設計師給出設計圖上的尺寸,找到對應像素數的機關,然後設定給控件即可
步驟1:以某一分辨率為基準,生成所有分辨率對應像素數清單
現在我們以320x480的分辨率為基準:
- 将螢幕的寬度分為320份,取值為x1~x320
- 将螢幕的高度分為480份,取值為y1~y480
然後生成該分辨率對應像素數的清單,如下圖:
- lay_x.xml(寬)
<?xml version="1.0" encoding="utf-8"?>
<resources><dimen name="x1">1.0px</dimen>
<dimen name="x2">2.0px</dimen>
<dimen name="x3">3.0px</dimen>
<dimen name="x4">4.0px</dimen>
<dimen name="x5">5.0px</dimen>
<dimen name="x6">6.0px</dimen>
<dimen name="x7">7.0px</dimen>
<dimen name="x8">8.0px</dimen>
<dimen name="x9">9.0px</dimen>
<dimen name="x10">10.0px</dimen>
...
<dimen name="x300">300.0px</dimen>
<dimen name="x301">301.0px</dimen>
<dimen name="x302">302.0px</dimen>
<dimen name="x303">303.0px</dimen>
<dimen name="x304">304.0px</dimen>
<dimen name="x305">305.0px</dimen>
<dimen name="x306">306.0px</dimen>
<dimen name="x307">307.0px</dimen>
<dimen name="x308">308.0px</dimen>
<dimen name="x309">309.0px</dimen>
<dimen name="x310">310.0px</dimen>
<dimen name="x311">311.0px</dimen>
<dimen name="x312">312.0px</dimen>
<dimen name="x313">313.0px</dimen>
<dimen name="x314">314.0px</dimen>
<dimen name="x315">315.0px</dimen>
<dimen name="x316">316.0px</dimen>
<dimen name="x317">317.0px</dimen>
<dimen name="x318">318.0px</dimen>
<dimen name="x319">319.0px</dimen>
<dimen name="x320">320px</dimen>
</resources>
- lay_y.xml(高)
<?xml version="1.0" encoding="utf-8"?>
<resources><dimen name="y1">1.0px</dimen>
<dimen name="y2">2.0px</dimen>
<dimen name="y3">3.0px</dimen>
<dimen name="y4">4.0px</dimen>
...
<dimen name="y480">480px</dimen>
</resources>
找到基準後,是時候把其他分辨率補全了,現今以寫1080x1920的分辨率為例:
因為基準是320x480,是以1080/320=3.375px,1920/480=4px,是以相應檔案應該是
- lay_x.xml
<?xml version="1.0" encoding="utf-8"?>
<resources><dimen name="x1">3.375px</dimen>
<dimen name="x2">6.65px</dimen>
<dimen name="x3">10.125px</dimen>
...
<dimen name="x320">1080px</dimen>
</resources>
- lay_y.xml
<?xml version="1.0" encoding="utf-8"?>
<resources><dimen name="y1">4px</dimen>
<dimen name="y2">8px</dimen>
<dimen name="y3">12px</dimen>
<dimen name="y4">16px</dimen>
...
<dimen name="y480">1920px</dimen>
</resources>
用上面的方法把你需要适配的分辨率的像素清單補全吧~
作為程式猿的我們當然不會做手寫的這些蠢事!!!多謝 @鴻洋大神 提供了自動生成工具(内置了常用的分辨率),大家可以直接點選這裡下載下傳
注:工具預設基準為400*320,當然對于特殊需求,通過指令行指定即可:
java -jar 檔案名.jar 基準寬 基準高 額外支援尺寸1的寬,額外支援尺寸1的高_額外支援尺寸2的寬,額外支援尺寸2的高:
例如:需要設定的基準是800x1280,額外支援尺寸:735x1152 ;3200x4500;
java -jar 檔案名.jar 800 1280 735,1152_3200,4500
步驟2:把生成的各像素數清單放到對應的資源檔案
将生成像素數清單(lay_x.xml和lay_y.xml)存放在res目錄下對應的values檔案(注意寬、高要對應),如下圖:
注:
- 分辨率為480x320的資源檔案應放在res/values-480x320檔案夾中;同理分辨率為1920x1080的資源檔案應放在res/values-1920x1080檔案夾中。(其中values-480x320是分辨率限定符)
-
必須在預設values裡面也建立對應預設lay_x.xml和lay_y.xml檔案,如下圖
lay_x.xml
<?xml version="1.0" encoding="utf-8">
<resources>
<dimen name="x1">1.0dp</dimen>
<dimen name="x2">2.0dp</dimen>
...
</resources>
- 因為對于沒有生成對應分辨率檔案的手機,會使用預設values檔案夾,如果預設values檔案夾沒有(即沒有對應的分辨率、沒有對應dimen)就會報錯,進而無法進行螢幕适配。
(注意對應機關改為dp,而不同于上面的px。因為不知道機型的分辨率,是以預設分辨率檔案隻好預設為x1=1dp以保證盡量相容(又回到dp老方法了),這也是這個解決方案的一個弊端)
步驟3:根據UI設計師給出某一分辨率設計圖上的尺寸,找到對應像素數的機關,然後設定給控件即可
如下圖:
<FrameLayout >
<Button
android:layout_gravity="center"
android:gravity="center"
android:text="@string/hello_world"
android:layout_width="@dimen/x160"
android:layout_height="@dimen/y160"/>
</FrameLayout>
總結
使用上述的适配方式,應該能進行90%的适配了,但其缺點還是很明顯:
- 由于實際上還是使用px作為長度的度量機關,是以和google的要求使用dp作為度量機關會有所背離
- 必須盡可能多的包含所有分辨率,因為這個是使用這個方案的基礎,如果有某個分辨率缺少,将無法完成該螢幕的适配
- 過多的分辨率像素描述xml檔案會增加軟體包的大小和維護的難度
“圖檔資源”比對
本質:使得圖檔資源在不同螢幕密度上顯示相同的像素效果
-
做法:提供備用位圖(符合螢幕尺寸的圖檔資源)
由于 Android 可在各種螢幕密度的裝置上運作,是以我們提供的位圖資源應該始終可以滿足各類密度的要求:
密度類型 | 代表的分辨率(px) | 系統密度(dpi) |
---|---|---|
低密度(ldpi) | 240x320 | 120 |
中密度(mdpi) | 320x480 | 160 |
高密度(hdpi) | 480x800 | 240 |
超高密度(xhdpi) | 720x1280 | 320 |
超超高密度(xxhdpi) | 1080x1920 | 480 |
- 步驟1:根據以下尺寸範圍針對各密度生成相應的圖檔。
比如說,如果我們為 xhdpi 裝置生成了 200x200 px尺寸的圖檔,就應該按照相應比例地為 hdpi、mdpi 和 ldpi 裝置分别生成 150x150、100x100 和 75x75 尺寸的圖檔
即一套分辨率=一套位圖資源(這個當然是Ui設計師做了)
- 步驟2:将生成的圖檔檔案放在 res/ 下的相應子目錄中(mdpi、hdpi、xhdpi、xxhdpi),系統就會根據運作您應用的裝置的螢幕密度自動選擇合适的圖檔
- 步驟3:通過引用 @drawable/id,系統都能根據相應螢幕的 螢幕密度(dpi)自動選取合适的位圖。
注:
- 如果是.9圖或者是不需要多個分辨率的圖檔,放在drawable檔案夾即可
- 對應分辨率的圖檔要正确的放在合适的檔案夾,否則會造成圖檔拉伸等問題。
更好地方案解決“圖檔資源”适配問題
上述方案是常見的一種方案,這固然是一種解決辦法,但缺點在于:
- 每套分辨率出一套圖,為美工或者設計增加了許多工作量
- 對Android工程檔案的apk包變的很大
那麼,有沒有一種方法:
- 保證螢幕密度适配
- 可以最小占用設計資源
- 使得apk包不變大(隻使用一套分辨率的圖檔資源)
下面我們就來介紹這個方法:
- 隻需選擇唯一一套分辨率規格的圖檔資源
方法介紹
1. 先來了解下Android 加載資源過程
Android SDK會根據螢幕密度自動選擇對應的資源檔案進行渲染加載(自動渲染)
比如說,SDK檢測到你手機的分辨率是320x480(dpi=160),會優先到drawable-mdpi檔案夾下找對應的圖檔資源;但假設你隻在xhpdi檔案夾下有對應的圖檔資源檔案(mdpi檔案夾是空的),那麼SDK會去xhpdi檔案夾找到相應的圖檔資源檔案,然後将原有大像素的圖檔自動縮放成小像素的圖檔,于是大像素的圖檔照樣可以在小像素分辨率的手機上正常顯示。
具體請看http://blog.csdn.net/xiebudong/article/details/37040263
是以理論上來說隻需要提供一種分辨率規格的圖檔資源就可以了。
那麼應該提供哪種分辨率規格呢?
如果隻提供ldpi規格的圖檔,對于大分辨率(xdpi、xxdpi)的手機如果把圖檔放大就會不清晰
是以需要提供一套你需要支援的最大dpi分辨率規格的圖檔資源,這樣即使使用者的手機分辨率很小,這樣圖檔縮小依然很清晰。那麼這一套最大dpi分辨率規格應該是哪種呢?是現在市面手機分辨率最大可達到1080X1920的分辨率(dpi=xxdpi=480)嗎?
2. xhdpi應該是首選
原因如下:
-
xhdpi分辨率以内的手機需求量最旺盛
目前市面上最普遍的高端機的分辨率還多集中在720X1080範圍内(xhdpi),是以目前來看xhpdi規格的圖檔資源成為了首選
-
節省設計資源&工作量
在現在的App開發中(iOS和Android版本),有些設計師為了保持App不同版本的體驗互動一緻,可能會以iPhone手機為基礎進行設計,包括後期的切圖之類的。
設計師們一般都會用最新的iPhone6和iPhone5s(5s和5的尺寸以及分辨率都一樣)來做原型設計,所有參數請看下圖
機型 | 分辨率(px) | 螢幕尺寸(inch) | 系統密度(dpi) |
---|---|---|---|
iPhone 5s | 640X1164 | 4 | 332 |
iPhone 6 | 1334x750 | 4.7 | 326 |
iPhone 6 Plus | 1080x1920 | 5 | 400 |
iPhone主流的螢幕dpi約等于320, 剛好屬于xhdpi,是以選擇xhdpi作為唯一一套dpi圖檔資源,可以讓設計師不用專門為Android端切圖,直接把iPhone的那一套切好的圖檔資源放入drawable-xhdpi檔案夾裡就好,這樣大大減少的設計師的工作量!
額外小tips
-
ImageView的ScaleType屬性
設定不同的ScaleType會得到不同的顯示效果,一般情況下,設定為centerCrop能獲得較好的适配效果。
- 動态設定
使用場景:有些情況下,我們需要動态的設定控件大小或者是位置,比如說popwindow的顯示位置和偏移量等
這時我們可以動态擷取目前的螢幕屬性,然後設定合适的數值
public class ScreenSizeUtil {
public static int getScreenWidth(Activity activity) {
return activity.getWindowManager().getDefaultDisplay().getWidth();
}
public static int getScreenHeight(Activity activity) {
return activity.getWindowManager().getDefaultDisplay().getHeight();
}
}