本文將告訴你如何讓你的應用程序支持各類不一樣屏幕大小,主要經過如下幾種辦法:html
讓你的佈局能充分的自適應屏幕java
根據屏幕的配置來加載合適的UI佈局android
確保正確的佈局應用在正確的設備屏幕上程序員
提供能夠根據屏幕大小自動伸縮的圖片ide
爲了確保你的佈局可以自適應各類不一樣屏幕大小,你應該在佈局的視圖中使用"wrap_content"和"match_parent"來肯定它的寬和高。若是你使用了"wrap_content",相應視圖的寬和高就會被設定成恰好可以包含視圖中內容的最小值。而若是你使用了"match_parent"(在Android API 8以前叫做"fill_parent"),就會讓視圖的寬和高延伸至充滿整個父佈局。工具
經過使用"wrap_content"和"match_parent"來替代硬編碼的方式定義視圖大小,你的視圖要麼僅僅使用了須要的那邊一點空間,要麼就會充滿全部可用的空間。例如:佈局
<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>
注意上面的例子中是如何使用 "wrap_content" 和 "match_parent" 來給控件定義寬高的,這讓整個佈局能夠正確地適應不一樣屏幕的大小,甚至是橫屏。ui
下圖是這個佈局分別在豎屏和橫屏時顯示的結果,注意控件的寬和高是根據屏幕自適應的。編碼
經過多層嵌套LinearLayout和組合使用"wrap_content"和"match_parent"已經能夠構建出足夠複雜的佈局。可是LinearLayout沒法容許你準確地控制子視圖以前的位置關係,全部LinearLayout中的子視圖只能簡單的一個挨着一個地排列。若是你須要讓子視圖可以有更多的排列方式,而不是簡單地排成一行或一列,使用RelativeLayout將會是更好的解決方案。RelativeLayout容許佈局的子控件之間使用相對定位的方式控制控件的位置,好比你可讓一個子視圖居屏幕左側對齊,讓另外一個子視圖居屏幕右側對齊。spa
例如:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/label" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Type here:"/> <EditText android:id="@+id/entry" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/label"/> <Button android:id="@+id/ok" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/entry" android:layout_alignParentRight="true" android:layout_marginLeft="10dp" android:text="OK" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toLeftOf="@id/ok" android:layout_alignTop="@id/ok" android:text="Cancel" /> </RelativeLayout>
下圖展現了這個佈局在QVGA屏幕上顯示的結果。
下圖展現了這個佈局在一個更大的屏幕上顯示的結果。
能夠注意到,即便屏幕的大小改變,視圖以前的相對位置都沒有改變。
雖然使用以上幾種方式能夠解決屏幕適配性的問題,可是那些經過伸縮控件來適應各類不一樣屏幕大小的佈局,未必就是提供了最好的用戶體驗。你的應用程序應該不只僅實現了可自適應的佈局,還應該提供一些方案根據屏幕的配置來加載不一樣的佈局,能夠經過配置限定符(configuration qualifiers)來實現。配置限定符容許程序在運行時根據當前設備的配置自動加載合適的資源(好比爲不一樣尺寸屏幕設計不一樣的佈局)。
如今有不少的應用程序爲了支持大屏設備,都會實現「two pane」模式(程序會在左側的面板上展現一個包含子項的List,在右側面板上展現內容)。平板和電視設備的屏幕都很大,足夠同時顯示兩個面板,而手機 屏幕一次只能顯示一個面板,兩個面板須要分開顯示。因此,爲了實現這種佈局,你可能須要如下文件:
res/layout/main.xml,single-pane(默認)佈局:
<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-large/main.xml ,two-pane佈局:
<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>
請注意第二個佈局的目錄名中包含了large限定符,那些被定義爲大屏的設備(好比7寸以上的平板)會自動加載此佈局,而小屏設備會加載另外一個默認的佈局。
使用Size限定符有一個問題會讓不少程序員感到頭疼,large究竟是指多大呢?不少應用程序都但願可以更自由地爲不一樣屏幕設備加載不一樣的佈局,無論它 們是否是被系統認定爲"large"。這就是Android爲何在3.2之後引入了"Smallest-width"限定符。
Smallest-width限定符容許你設定一個具體的最小值(以dp爲單位)來指定屏幕。例如,7寸的平板最小寬度是600dp,因此若是你想讓你的UI在這種屏幕上顯示two pane,在更小的屏幕上顯示single pane,你可使用sw600dp來表示你想在600dp以上寬度的屏幕上使用two pane模式。
res/layout/main.xml,single-pane(默認)佈局:
<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-sw600dp/main.xml ,two-pane佈局:
<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>
這意味着,那些最小屏幕寬度大於600dp的設備會選擇 layout-sw600dp/main.xml (two-pane)佈局,而更小屏幕的設備將會選擇 layout/main.xml (single-pane)佈局。
然而,使用早於Android 3.2系統的設備將沒法識別sw600dp這個限定符,因此你仍是同時須要使用large限定符。這樣你就須要在res/layout-large和res/layout-sw600dp目錄下都添加一個相同的main.xml。下節你將會看到如何避免重複定義這種佈局的技巧。
Smallest-width限定符僅在Android 3.2及以後的系統中有效。於是,你也須要同時使用Size限定符(small, normal, large和xlarge)來兼容更早的系統。例如,你想手機上顯示single-pane界面,而在7寸平板和更大屏的設備上顯示multi-pane 界面,你須要提供如下文件:
res/layout/main.xml: single-pane佈局
res/layout-large: multi-pane佈局
res/layout-sw600dp: multi-pane佈局
最後的兩個文件是徹底相同的,爲了要解決這種重複,你須要使用別名技巧。例如,你能夠定義如下佈局:
res/layout/main.xml, single-pane佈局
res/layout/main_twopanes.xml, two-pane佈局
加入如下兩個文件:
res/values-large/layout.xml:
<resources> <item name="main" type="layout">@layout/main_twopanes</item> </resources>
res/values-sw600dp/layout.xml:
<resources> <item name="main" type="layout">@layout/main_twopanes</item> </resources>
最後兩個文件有着相同的內容,可是它們並無真正去定義佈局,它們僅僅只是給main定義了一個別名main_twopanes。這樣兩個layout.xml都只是引用了@layout/main_twopanes,就避免了重複定義佈局文件的狀況。
有些佈局會在橫屏和豎屏的狀況下都顯示的很好,可是多數狀況下這些佈局均可以再調整的。在News Reader示例程序中,佈局在不一樣屏幕尺寸和不一樣屏幕方向中是這樣顯示的:
小屏幕, 豎屏: 單面板, 顯示logo
小屏幕, 橫屏: 單面板, 顯示logo
7寸平板, 豎屏: 單面板, 顯示action bar
7寸平板, 橫屏: 雙面板, 寬, 顯示action bar
10寸平板, 豎屏: 雙面板, 窄, 顯示action bar
10寸平板, 橫屏: 雙面板, 寬, 顯示action bar
電視, 橫屏: 雙面板, 寬, 顯示action bar
全部這些佈局都是定義在 res/layout/ 這個目錄下,爲了要讓設備根據屏幕配置來加載正確的佈局,程序須要使用佈局別名來實現。
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>
如今全部須要的佈局都已經定義好了,剩下的只要使用限定符來讓各個設備根據屏幕配置加載正確的佈局了。你如今就可使用佈局別名技術:
res/values/layouts.xml:
<resources> <item name="main_layout" type="layout">@layout/onepane_with_bar</item> <bool name="has_two_panes">false</bool> </resources>
res/values-sw600dp-land/layouts.xml:
<resources> <item name="main_layout" type="layout">@layout/twopanes</item> <bool name="has_two_panes">true</bool> </resources>
res/values-sw600dp-port/layouts.xml:
<resources> <item name="main_layout" type="layout">@layout/onepane</item> <bool name="has_two_panes">false</bool> </resources>
res/values-large-land/layouts.xml:
<resources> <item name="main_layout" type="layout">@layout/twopanes</item> <bool name="has_two_panes">true</bool> </resources>
res/values-large-port/layouts.xml:
<resources> <item name="main_layout" type="layout">@layout/twopanes_narrow</item> <bool name="has_two_panes">true</bool> </resources>
支持不一樣屏幕大小一般狀況下也意味着,你的圖片資源也須要有自適應的能力。例如,一個按鈕的背景圖片必須可以隨着按鈕大小的改變而改變。
若是你想使用普通的圖片來實現上述功能,你很快就會發現結果是使人失望的,由於運行時會均勻地拉伸或壓縮你的圖片。解決方案是使用nine-patch圖片,它是一種被特殊處理過的PNG圖片,你能夠指定哪些區域能夠拉伸而哪些區域不能夠。
於是,當你設計須要在不一樣大小的控件中使用的圖片時,最好的方法就是用nine-patch圖片。爲了將圖片轉換成nine-patch圖片,你能夠從一張普通的圖片開始:
而後經過SDK中帶有的draw9patch工具打開這張圖片(工具位置在SDK的tools目錄下),你能夠在圖片的左邊框和上邊框繪製來標記哪些區域能夠被拉伸。你也能夠在圖片的右邊框和下邊框繪製來標記內容須要放置在哪一個區域。結果以下圖所示:
注意圖片邊框上的黑色像素,在上邊框和左邊框的部分表示當圖片須要拉伸時就拉伸黑點標記的位置。在下邊框和右邊框的部分表示內容將會被放置的區域。
同時須要注意,這張圖片的後綴名是 .9.png。你必需要使用這個後綴名,由於系統就是根據這個來區別nine-patch圖片和普通的PNG圖片的。
當你須要在一個控件中使用nine-patch圖片時(如android:background="@drawable/button"),系統就會根據控件的大小自動地拉伸你想要拉伸的部分,效果以下圖所示: