Android屏幕適配----搞定碎片化

Android屏幕適配出現(xiàn)的原因

在我們學(xué)習(xí)如何進(jìn)行屏幕適配之前余佛,我們需要先了解下為什么Android需要進(jìn)行屏幕適配。

由于Android系統(tǒng)的開放性窍荧,任何用戶辉巡、開發(fā)者、OEM廠商蕊退、運(yùn)營商都可以對Android進(jìn)行定制郊楣,修改成他們想要的樣子。

但是這種“碎片化”到底到達(dá)什么程度呢瓤荔?

在2012年净蚤,OpenSignalMaps(以下簡稱OSM)發(fā)布了第一份Android碎片化報告,統(tǒng)計(jì)數(shù)據(jù)表明输硝,

2012年今瀑,支持Android的設(shè)備共有3997種。
2013年,支持Android的設(shè)備共有11868種橘荠。
2014年屿附,支持Android的設(shè)備共有18796種。

下面這張圖片所顯示的內(nèi)容足以充分說明當(dāng)今Android系統(tǒng)碎片化問題的嚴(yán)重性哥童,因?yàn)樵搱D片中的每一個矩形都代表著一種Android設(shè)備挺份。

image

而隨著支持Android系統(tǒng)的設(shè)備(手機(jī)、平板贮懈、電視匀泊、手表)的增多,設(shè)備碎片化朵你、品牌碎片化各聘、系統(tǒng)碎片化、傳感器碎片化和屏幕碎片化的程度也在不斷地加深撬呢。而我們今天要探討的伦吠,則是對我們開發(fā)影響比較大的——屏幕的碎片化。

下面這張圖是Android屏幕尺寸的示意圖魂拦,在這張圖里面,藍(lán)色矩形的大小代表不同尺寸搁嗓,顏色深淺則代表所占百分比的大小芯勘。

2.png

而與之相對應(yīng)的,則是下面這張圖腺逛。這張圖顯示了IOS設(shè)備所需要進(jìn)行適配的屏幕尺寸和占比荷愕。

3.png

當(dāng)然,這張圖片只是4,4s,5,5c,5s和平板的尺寸棍矛,現(xiàn)在還應(yīng)該加上新推出的iphone6和plus安疗,但是和Android的屏幕碎片化程度相比而言,還是差的太遠(yuǎn)够委。

現(xiàn)在你應(yīng)該很清楚為什么要對Android的屏幕進(jìn)行適配了吧荐类?屏幕尺寸這么多,為了讓我們開發(fā)的程序能夠比較美觀的顯示在不同尺寸茁帽、分辨率玉罐、像素密度(這些概念我會在下面詳細(xì)講解)的設(shè)備上,那就要在開發(fā)的過程中進(jìn)行處理潘拨,至于如何去進(jìn)行處理吊输,這就是我們今天的主題了。

但是在開始進(jìn)入主題之前铁追,我們再來探討一件事情季蚂,那就是Android設(shè)備的屏幕尺寸,從幾寸的智能手機(jī),到10寸的平板電腦扭屁,再到幾十寸的數(shù)字電視算谈,我們應(yīng)該適配哪些設(shè)備呢?

其實(shí)這個問題不應(yīng)該這么考慮疯搅,因?yàn)閷τ诰哂邢嗤袼孛芏鹊脑O(shè)備來說濒生,像素越高,尺寸就越大幔欧,所以我們可以換個思路罪治,將問題從單純的尺寸大小轉(zhuǎn)換到像素大小和像素密度的角度來。

下圖是2014年初礁蔗,友盟統(tǒng)計(jì)的占比5%以上的6個主流分辨率觉义,可以看出,占比最高的是480800浴井,320480的設(shè)備竟然也占據(jù)了很大比例晒骇,但是和半年前的數(shù)據(jù)相比較,中低分辨率(320480磺浙、480800)的比例在減少洪囤,而中高分辨率的比例則在不斷地增加。雖然每個分辨率所占的比例在變化撕氧,但是總的趨勢沒變瘤缩,還是這六種,只是分辨率在不斷地提高伦泥。

所以說剥啤,我們只要盡量適配這幾種分辨率,就可以在大部分的手機(jī)上正常運(yùn)行了不脯。

當(dāng)然了府怯,這只是手機(jī)的適配,對于平板設(shè)備(電視也可以看做是平板)防楷,我們還需要一些其他的處理牺丙。

好了,到目前為止域帐,我們已經(jīng)弄清楚了Android開發(fā)為什么要進(jìn)行適配赘被,以及我們應(yīng)該適配哪些對象议纯,接下來驼卖,終于進(jìn)入我們的正題了!

首先抓于,我們先要學(xué)習(xí)幾個重要的概念龙优。

重要概念

什么是屏幕尺寸羊异、屏幕分辨率事秀、屏幕像素密度?
什么是dp野舶、dip易迹、dpi、sp平道、px睹欲?他們之間的關(guān)系是什么?
什么是mdpi一屋、hdpi窘疮、xdpi、xxdpi冀墨?如何計(jì)算和區(qū)分闸衫?

在下面的內(nèi)容中我們將介紹這些概念。

屏幕尺寸

屏幕尺寸指屏幕的對角線的長度诽嘉,單位是英寸蔚出,1英寸=2.54厘米

比如常見的屏幕尺寸有2.4、2.8虫腋、3.5骄酗、3.7、4.2悦冀、5.0酥筝、5.5、6.0等

屏幕分辨率

屏幕分辨率是指在橫縱向上的像素點(diǎn)數(shù)雏门,單位是px,1px=1個像素點(diǎn)掸掏。一般以縱向像素橫向像素茁影,如19601080。

屏幕像素密度

屏幕像素密度是指每英寸上的像素點(diǎn)數(shù)丧凤,單位是dpi募闲,即“dot per inch”的縮寫。屏幕像素密度與屏幕尺寸和屏幕分辨率有關(guān)愿待,在單一變化條件下浩螺,屏幕尺寸越小、分辨率越高仍侥,像素密度越大要出,反之越小。

dp农渊、dip患蹂、dpi、sp、px

px我們應(yīng)該是比較熟悉的传于,前面的分辨率就是用的像素為單位囱挑,大多數(shù)情況下,比如UI設(shè)計(jì)沼溜、Android原生API都會以px作為統(tǒng)一的計(jì)量單位平挑,像是獲取屏幕寬高等。

dip和dp是一個意思系草,都是Density Independent Pixels的縮寫通熄,即密度無關(guān)像素,上面我們說過悄但,dpi是屏幕像素密度棠隐,假如一英寸里面有160個像素,這個屏幕的像素密度就是160dpi檐嚣,那么在這種情況下助泽,dp和px如何換算呢?在Android中嚎京,規(guī)定以160dpi為基準(zhǔn)嗡贺,1dip=1px,如果密度是320dpi鞍帝,則1dip=2px诫睬,以此類推。

假如同樣都是畫一條320px的線帕涌,在480800分辨率手機(jī)上顯示為2/3屏幕寬度摄凡,在320480的手機(jī)上則占滿了全屏,如果使用dp為單位蚓曼,在這兩種分辨率下亲澡,160dp都顯示為屏幕一半的長度。這也是為什么在Android開發(fā)中纫版,寫布局的時候要盡量使用dp而不是px的原因床绪。

而sp,即scale-independent pixels其弊,與dp類似癞己,但是可以根據(jù)文字大小首選項(xiàng)進(jìn)行放縮,是設(shè)置字體大小的御用單位梭伐。

mdpi痹雅、hdpi、xdpi籽御、xxdpi

其實(shí)之前還有個ldpi练慕,但是隨著移動設(shè)備配置的不斷升級惰匙,這個像素密度的設(shè)備已經(jīng)很罕見了,所在現(xiàn)在適配時不需考慮铃将。

mdpi项鬼、hdpi、xdpi劲阎、xxdpi用來修飾Android中的drawable文件夾及values文件夾绘盟,用來區(qū)分不同像素密度下的圖片和dimen值。

那么如何區(qū)分呢悯仙?Google官方指定按照下列標(biāo)準(zhǔn)進(jìn)行區(qū)分:

名稱 像素密度范圍
mdpi 120dpi~160dpi
hdpi 160dpi~240dpi
xhdpi 240dpi~320dpi
xxhdpi 320dpi~480dpi
xxxhdpi 480dpi~640dpi

在進(jìn)行開發(fā)的時候龄毡,我們需要把合適大小的圖片放在合適的文件夾里面。下面以圖標(biāo)設(shè)計(jì)為例進(jìn)行介紹锡垄。

在設(shè)計(jì)圖標(biāo)時沦零,對于五種主流的像素密度(MDPI、HDPI货岭、XHDPI路操、XXHDPI 和 XXXHDPI)應(yīng)按照 2:3:4:6:8 的比例進(jìn)行縮放。例如千贯,一個啟動圖標(biāo)的尺寸為48x48 dp屯仗,這表示在 MDPI 的屏幕上其實(shí)際尺寸應(yīng)為 48x48 px,在 HDPI 的屏幕上其實(shí)際大小是 MDPI 的 1.5 倍 (72x72 px)搔谴,在 XDPI 的屏幕上其實(shí)際大小是 MDPI 的 2 倍 (96x96 px)魁袜,依此類推。

雖然 Android 也支持低像素密度 (LDPI) 的屏幕敦第,但無需為此費(fèi)神峰弹,系統(tǒng)會自動將 HDPI 尺寸的圖標(biāo)縮小到 1/2 進(jìn)行匹配。

下圖為圖標(biāo)的各個屏幕密度的對應(yīng)尺寸

屏幕密度 圖標(biāo)尺寸
mdpi 48x48px
hdpi 72x72px
xhdpi 96x96px
xxhdpi 144x144px
xxxhdpi 192x192px

解決方案

支持各種屏幕尺寸

使用wrap_content芜果、match_parent垮卓、weight

要確保布局的靈活性并適應(yīng)各種尺寸的屏幕,應(yīng)使用 “wrap_content” 和 “match_parent” 控制某些視圖組件的寬度和高度师幕。

使用 “wrap_content”,系統(tǒng)就會將視圖的寬度或高度設(shè)置成所需的最小尺寸以適應(yīng)視圖中的內(nèi)容诬滩,而 “match_parent”(在低于 API 級別 8 的級別中稱為 “fill_parent”)則會展開組件以匹配其父視圖的尺寸霹粥。

如果使用 “wrap_content” 和 “match_parent” 尺寸值而不是硬編碼的尺寸,視圖就會相應(yīng)地僅使用自身所需的空間或展開以填滿可用空間疼鸟。此方法可讓布局正確適應(yīng)各種屏幕尺寸和屏幕方向后控。

下面是一段示例代碼

<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>

下圖是在橫縱屏切換的時候的顯示效果,我們可以看到這樣可以很好的適配屏幕尺寸的變化空镜。

[圖片上傳失敗...(image-726589-1543853498114)]

weight是線性布局的一個獨(dú)特的屬性浩淘,我們可以使用這個屬性來按照比例對界面進(jìn)行分配捌朴,完成一些特殊的需求。

但是张抄,我們對于這個屬性的計(jì)算應(yīng)該如何理解呢砂蔽?

首先看下面的例子,我們在布局中這樣設(shè)置我們的界面

[圖片上傳失敗...(image-540741-1543853498114)]

我們在布局里面設(shè)置為線性布局署惯,橫向排列左驾,然后放置兩個寬度為0dp的按鈕,分別設(shè)置weight為1和2极谊,在效果圖中诡右,我們可以看到兩個按鈕按照1:2的寬度比例正常排列了,這也是我們經(jīng)常使用到的場景轻猖,這是時候很好理解帆吻,Button1的寬度就是1/(1+2) = 1/3,Button2的寬度則是2/(1+2) = 2/3咙边,我們可以很清楚的明白這種情景下的占比如何計(jì)算猜煮。

但是假如我們的寬度不是0dp(wrap_content和0dp的效果相同),則是match_parent呢样眠?

下面是設(shè)置為match_parent的效果

[圖片上傳失敗...(image-2c9d10-1543853498114)]

我們可以看到友瘤,在這種情況下,占比和上面正好相反檐束,這是怎么回事呢辫秧?說到這里,我們就不得不提一下weight的計(jì)算方法了被丧。

android:layout_weight的真實(shí)含義是:如果View設(shè)置了該屬性并且有效盟戏,那么該 View的寬度等于原有寬度(android:layout_width)加上剩余空間的占比。

從這個角度我們來解釋一下上面的現(xiàn)象甥桂。在上面的代碼中柿究,我們設(shè)置每個Button的寬度都是match_parent,假設(shè)屏幕寬度為L黄选,那么每個Button的寬度也應(yīng)該都為L蝇摸,剩余寬度就等于L-(L+L)= -L。

Button1的weight=1办陷,剩余寬度占比為1/(1+2)= 1/3貌夕,所以最終寬度為L+1/3*(-L)=2/3L民镜,Button2的計(jì)算類似制圈,最終寬度為L+2/3(-L)=1/3L们童。

這是在水平方向上的慧库,那么在垂直方向上也是這樣嗎跷跪?

下面是測試代碼和效果

如果是垂直方向完沪,那么我們應(yīng)該改變的是layout_height的屬性覆积,下面是0dp的顯示效果

如果是垂直方向宽档,那么我們應(yīng)該改變的是layout_height的屬性吗冤,下面是0dp的顯示效果

下面是match_parent的顯示效果覆致,結(jié)論和水平是完全一樣的

雖然說我們演示了match_parent的顯示效果煌妈,并說明了原因,但是在真正用的時候之宿,我們都是設(shè)置某一個屬性為0dp比被,然后按照權(quán)重計(jì)算所占百分比。

使用相對布局,禁用絕對布局

在開發(fā)中涯贞,我們大部分時候使用的都是線性布局州疾、相對布局和幀布局严蓖,絕對布局由于適配性極差,所以極少使用毒姨。

由于各種布局的特點(diǎn)不一樣,所以不能說哪個布局好用俘枫,到底應(yīng)該使用什么布局只能根據(jù)實(shí)際需求來確定。我們可以使用 LinearLayout 的嵌套實(shí)例并結(jié)合 “wrap_content” 和 “match_parent”邓嘹,以便構(gòu)建相當(dāng)復(fù)雜的布局汹押。不過,我們無法通過 LinearLayout 精確控制子視圖的特殊關(guān)系妙痹;系統(tǒng)會將 LinearLayout 中的視圖直接并排列出。

如果我們需要將子視圖排列出各種效果而不是一條直線耿芹,通常更合適的解決方法是使用 RelativeLayout琉闪,這樣就可以根據(jù)各組件之間的特殊關(guān)系指定布局了。例如蛀蜜,我們可以將某個子視圖對齊到屏幕左側(cè)涵防,同時將另一個視圖對齊到屏幕右側(cè)。

下面的代碼以官方Demo為例說明椰憋。

<?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>

在上面的代碼中我們使用了相對布局硕旗,并且使用alignXXX等屬性指定了子控件的位置漆枚,下面是這種布局方式在應(yīng)對屏幕變化時的表現(xiàn)

在小尺寸屏幕的顯示

在平板的大尺寸上的顯示效果

雖然控件的大小由于屏幕尺寸的增加而發(fā)生了改變软族,但是我們可以看到,由于使用了相對布局,所以控件之前的位置關(guān)系并沒有發(fā)生什么變化揖庄,這說明我們的適配成功了。

使用限定符
使用尺寸限定符

上面所提到的靈活布局或者是相對布局,可以為我們帶來的優(yōu)勢就只有這么多了晾捏。雖然這些布局可以拉伸組件內(nèi)外的空間以適應(yīng)各種屏幕添坊,但它們不一定能為每種屏幕都提供最佳的用戶體驗(yàn)。因此,我們的應(yīng)用不僅僅只實(shí)施靈活布局嗡髓,還應(yīng)該應(yīng)針對各種屏幕配置提供一些備用布局嚣鄙。

如何做到這一點(diǎn)呢哑子?我們可以通過使用配置限定符帐要,在運(yùn)行時根據(jù)當(dāng)前的設(shè)備配置自動選擇合適的資源了,例如根據(jù)各種屏幕尺寸選擇不同的布局冒冬。

很多應(yīng)用會在較大的屏幕上實(shí)施“雙面板”模式剂邮,即在一個面板上顯示項(xiàng)目列表绰姻,而在另一面板上顯示對應(yīng)內(nèi)容狂芋。平板電腦和電視的屏幕已經(jīng)大到可以同時容納這兩個面板了,但手機(jī)屏幕就需要分別顯示屡萤。因此,我們可以使用以下文件以便實(shí)施這些布局:

res/layout/main.xml,單面板(默認(rèn))布局:

<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领虹,雙面板布局:

<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 限定符。系統(tǒng)會在屬于較大屏幕(例如 7 英寸或更大的平板電腦)的設(shè)備上選擇此布局均唉。系統(tǒng)會在較小的屏幕上選擇其他布局(無限定符)。

使用最小寬度限定符

在版本低于 3.2 的 Android 設(shè)備上层扶,開發(fā)人員遇到的問題之一是“較大”屏幕的尺寸范圍,該問題會影響戴爾 Streak、早期的 Galaxy Tab 以及大部分 7 英寸平板電腦匾旭。即使這些設(shè)備的屏幕屬于“較大”的尺寸,但很多應(yīng)用可能會針對此類別中的各種設(shè)備(例如 5 英寸和 7 英寸的設(shè)備)顯示不同的布局色瘩。這就是 Android 3.2 版在引入其他限定符的同時引入“最小寬度”限定符的原因。

最小寬度限定符可讓您通過指定某個最小寬度(以 dp 為單位)來定位屏幕。例如聊倔,標(biāo)準(zhǔn) 7 英寸平板電腦的最小寬度為 600 dp,因此如果您要在此類屏幕上的用戶界面中使用雙面板(但在較小的屏幕上只顯示列表)须揣,您可以使用上文中所述的單面板和雙面板這兩種布局,但您應(yīng)使用 sw600dp 指明雙面板布局僅適用于最小寬度為 600 dp 的屏幕卵酪,而不是使用 large 尺寸限定符。

res/layout/main.xml,單面板(默認(rèn))布局:

<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,雙面板布局:

<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 的設(shè)備私爷,系統(tǒng)會選擇 layout-sw600dp/main.xml(雙面板)布局,否則系統(tǒng)就會選擇 layout/main.xml(單面板)布局工秩。

但 Android 版本低于 3.2 的設(shè)備不支持此技術(shù),原因是這些設(shè)備無法將 sw600dp 識別為尺寸限定符眉菱,因此我們?nèi)孕枋褂?large 限定符克伊。這樣一來,就會有一個名稱為 res/layout-large/main.xml 的文件(與 res/layout-sw600dp/main.xml 一樣)洗搂。但是沒有太大關(guān)系,我們將馬上學(xué)習(xí)如何避免此類布局文件出現(xiàn)的重復(fù)惫叛。

使用布局別名

最小寬度限定符僅適用于 Android 3.2 及更高版本。因此,如果我們?nèi)孕枋褂门c較低版本兼容的概括尺寸范圍(小警医、正常、大和特大)。例如鲁豪,如果要將用戶界面設(shè)計(jì)成在手機(jī)上顯示單面板,但在 7 英寸平板電腦、電視和其他較大的設(shè)備上顯示多面板郭宝,那么我們就需要提供以下文件:

    res/layout/main.xml: 單面板布局
    res/layout-large: 多面板布局
    res/layout-sw600dp: 多面板布局

后兩個文件是相同的榄檬,因?yàn)槠渲幸粋€用于和 Android 3.2 設(shè)備匹配,而另一個則是為使用較低版本 Android 的平板電腦和電視準(zhǔn)備的。

要避免平板電腦和電視的文件出現(xiàn)重復(fù)(以及由此帶來的維護(hù)問題)沪袭,您可以使用別名文件。例如,您可以定義以下布局:

    res/layout/main.xml毅该,單面板布局
    res/layout/main_twopanes.xml罢绽,雙面板布局

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>

后兩個文件的內(nèi)容相同,但它們并未實(shí)際定義布局明垢。它們只是將 main 設(shè)置成了 main_twopanes 的別名。由于這些文件包含 large 和 sw600dp 選擇器溯革,因此無論 Android 版本如何,系統(tǒng)都會將這些文件應(yīng)用到平板電腦和電視上(版本低于 3.2 的平板電腦和電視會匹配 large萎攒,版本高于 3.2 的平板電腦和電視則會匹配 sw600dp)。

使用屏幕方向限定符

某些布局會同時支持橫向模式和縱向模式蔑歌,但我們可以通過調(diào)整優(yōu)化其中大部分布局的效果羹应。在新聞閱讀器示例應(yīng)用中,每種屏幕尺寸和屏幕方向下的布局行為方式如下所示:

    小屏幕次屠,縱向:單面板园匹,帶徽標(biāo)
    小屏幕劫灶,橫向:單面板裸违,帶徽標(biāo)
    7 英寸平板電腦,縱向:單面板本昏,帶操作欄
    7 英寸平板電腦供汛,橫向:雙面板,寬涌穆,帶操作欄
    10 英寸平板電腦怔昨,縱向:雙面板,窄宿稀,帶操作欄
    10 英寸平板電腦趁舀,橫向:雙面板,寬祝沸,帶操作欄
    電視矮烹,橫向:雙面板,寬罩锐,帶操作欄

因此奉狈,這些布局中的每一種都定義在了 res/layout/ 目錄下的某個 XML 文件中。為了繼續(xù)將每個布局分配給各種屏幕配置涩惑,該應(yīng)用會使用布局別名將兩者相匹配:

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>

既然我們已定義了所有可能的布局问芬,那就只需使用配置限定符將正確的布局映射到各種配置即可。

現(xiàn)在只需使用布局別名技術(shù)即可做到這一點(diǎn):

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>
使用自動拉伸位圖

支持各種屏幕尺寸通常意味著您的圖片資源還必須能適應(yīng)各種尺寸强戴。例如骑歹,無論要應(yīng)用到什么形狀的按鈕上墨微,按鈕背景都必須能適應(yīng)道媚。

如果在可以更改尺寸的組件上使用了簡單的圖片,您很快就會發(fā)現(xiàn)顯示效果多少有些不太理想翘县,因?yàn)橄到y(tǒng)會在運(yùn)行時平均地拉伸或收縮您的圖片最域。解決方法為使用自動拉伸位圖,這是一種格式特殊的 PNG 文件锈麸,其中會指明可以拉伸以及不可以拉伸的區(qū)域镀脂。

.9的制作,實(shí)際上就是在原圖片上添加1px的邊界忘伞,然后按照我們的需求薄翅,把對應(yīng)的位置設(shè)置成黑色線,系統(tǒng)就會根據(jù)我們的實(shí)際需求進(jìn)行拉伸氓奈。

下圖是對.9圖的四邊的含義的解釋翘魄,左上邊代表拉伸區(qū)域,右下邊代表padding box舀奶,就是間隔區(qū)域暑竟,在下面,我們給出一個例子伪节,方便大家理解光羞。


4.png

先看下面兩張圖绩鸣,我們理解一下這四條線的含義。

image

上圖和下圖的區(qū)別,就在于右下邊的黑線不一樣蓖康,具體的效果的區(qū)別倒信,看右邊的效果圖。上圖效果圖中深藍(lán)色的區(qū)域乘综,代表內(nèi)容區(qū)域,我們可以看到是在正中央的九妈,這是因?yàn)槲覀冊谟蚁逻叺氖莾蓚€點(diǎn),這兩個點(diǎn)距離上下左右四個方向的距離就是padding的距離嚷兔,所以深藍(lán)色內(nèi)容區(qū)域在圖片正中央,我們再看下圖壶运,由于右下邊的黑線是圖片長度,所以就沒有padding,從效果圖上的表現(xiàn)就是深藍(lán)色區(qū)域和圖片一樣大狈谊,因此壁榕,我們可以利用右下邊來控制內(nèi)容與背景圖邊緣的padding。


image

如果你還不明白,那么我們看下面的效果圖催享,我們分別以圖一和圖二作為背景圖,下面是效果圖攀涵。

我們可以看到,使用wrap_content屬性設(shè)置長寬怒详,圖一比圖二的效果大一圈,這是為什么呢静尼?還記得我上面說的padding嗎?

image

這就是padding的效果提現(xiàn),怎么證明呢掌敬?我們再看下面一張圖,給圖一添加padding=0华临,這樣背景圖設(shè)置的padding效果就沒了,是不是兩個一樣大了扶供?

image

ok闽晦,我想你應(yīng)該明白右下邊的黑線的含義了笋敞,下面我們再看一下左上邊的效果。

下面我們只設(shè)置了左上邊線鞭莽,效果圖如下

image

上面的線沒有包住圖標(biāo),下面的線正好包住了圖標(biāo),從右邊的效果圖應(yīng)該可以看出差別惧辈,黑線所在的區(qū)域就是拉伸區(qū)域念逞,上圖黑線所在的全是純色翎承,所以圖標(biāo)不變形,下面的拉伸區(qū)域包裹了圖標(biāo)甸各,所以在拉伸的時候就會對圖標(biāo)進(jìn)行拉伸,但是這樣就會導(dǎo)致圖標(biāo)變形誊酌。注意到下面紅線區(qū)域了嘛碧浊?這是系統(tǒng)提示我們的,因?yàn)檫@樣拉伸驹止,不符合要求,所以會提示一下抖仅。

image

支持各種屏幕密度

使用非密度制約像素

由于各種屏幕的像素密度都有所不同褥实,因此相同數(shù)量的像素在不同設(shè)備上的實(shí)際大小也有所差異,這樣使用像素定義布局尺寸就會產(chǎn)生問題乔外。因此莉测,請務(wù)必使用 dp 或 sp 單位指定尺寸。dp 是一種非密度制約像素,其尺寸與 160 dpi 像素的實(shí)際尺寸相同唆阿。sp 也是一種基本單位,但它可根據(jù)用戶的偏好文字大小進(jìn)行調(diào)整(即尺度獨(dú)立性像素)浅辙,因此我們應(yīng)將該測量單位用于定義文字大小。

例如泽腮,請使用 dp(而非 px)指定兩個視圖間的間距:

<Button android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/clickme"
    android:layout_marginTop="20dp" />

請務(wù)必使用 sp 指定文字大小:

<TextView android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:textSize="20sp" />

除了介紹這些最基礎(chǔ)的知識之外碧磅,我們下面再來討論一下另外一個問題。

經(jīng)過上面的介紹严望,我們都清楚峻黍,為了能夠規(guī)避不同像素密度的陷阱姆涩,Google推薦使用dp來代替px作為控件長度的度量單位台腥,但是我們來看下面的一個場景察署。

假如我們以Nexus5作為書寫代碼時查看效果的測試機(jī)型,Nexus5的總寬度為360dp扳埂,我們現(xiàn)在需要在水平方向上放置兩個按鈕蔚携,一個是150dp左對齊誊辉,另外一個是200dp右對齊,中間留有10dp間隔,那么在Nexus5上面的顯示效果就是下面這樣

但是如果在Nexus S或者是Nexus One運(yùn)行呢坑傅?下面是運(yùn)行結(jié)果

可以看到蒜茴,兩個按鈕發(fā)生了重疊。

我們都已經(jīng)用了dp了诺核,為什么會出現(xiàn)這種情況呢陈瘦?

你聽我慢慢道來。

雖然說dp可以去除不同像素密度的問題,使得1dp在不同像素密度上面的顯示效果相同,但是還是由于Android屏幕設(shè)備的多樣性训枢,如果使用dp來作為度量單位,并不是所有的屏幕的寬度都是相同的dp長度,比如說虾宇,Nexus S和Nexus One屬于hdpi,屏幕寬度是320dp,而Nexus 5屬于xxhdpi靶端,屏幕寬度是360dp,Galaxy Nexus屬于xhdpi凛膏,屏幕寬度是384dp杨名,Nexus 6 屬于xxxhdpi,屏幕寬度是410dp猖毫。所以說台谍,光Google自己一家的產(chǎn)品就已經(jīng)有這么多的標(biāo)準(zhǔn),而且屏幕寬度和像素密度沒有任何關(guān)聯(lián)關(guān)系,即使我們使用dp又兵,在320dp寬度的設(shè)備和410dp的設(shè)備上,還是會有90dp的差別风纠。當(dāng)然誊抛,我們盡量使用match_parent和wrap_content,盡可能少的用dp來指定控件的具體長寬乙各,再結(jié)合上權(quán)重咬荷,大部分的情況我們都是可以做到適配的腔召。

但是除了這個方法舔琅,我們還有沒有其他的更徹底的解決方案呢虚循?

我們換另外一個思路來思考這個問題。

下面的方案來自Android Day Day Up 一群的【blue-深圳】,謝謝他的分享精神

因?yàn)榉直媛什灰粯樱圆荒苡胮x;因?yàn)槠聊粚挾炔灰粯余诼模砸⌒牡挠胐p溉旋,那么我們可不可以用另外一種方法來統(tǒng)一單位,不管分辨率是多大,屏幕寬度用一個固定的值的單位來統(tǒng)計(jì)呢隶校?

答案是:當(dāng)然可以满俗。

我們假設(shè)手機(jī)屏幕的寬度都是320某單位凉夯,那么我們將一個屏幕寬度的總像素?cái)?shù)平均分成320份扎阶,每一份對應(yīng)具體的像素就可以了。

具體如何來實(shí)現(xiàn)呢户辱?我們看下面的代碼

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintWriter;
 
public class MakeXml {
 
    private final static String rootPath = "C:\\Users\\Administrator\\Desktop\\layoutroot\\values-{0}x{1}\\";
 
    private final static float dw = 320f;
    private final static float dh = 480f;
 
    private final static String WTemplate = "<dimen name=\"x{0}\">{1}px</dimen>\n";
    private final static String HTemplate = "<dimen name=\"y{0}\">{1}px</dimen>\n";
 
    public static void main(String[] args) {
        makeString(320, 480);
        makeString(480,800);
        makeString(480, 854);
        makeString(540, 960);
        makeString(600, 1024);
        makeString(720, 1184);
        makeString(720, 1196);
        makeString(720, 1280);
        makeString(768, 1024);
        makeString(800, 1280);
        makeString(1080, 1812);
        makeString(1080, 1920);
        makeString(1440, 2560);
    }
 
    public static void makeString(int w, int h) {
 
        StringBuffer sb = new StringBuffer();
        sb.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
        sb.append("<resources>");
        float cellw = w / dw;
        for (int i = 1; i < 320; i++) {
            sb.append(WTemplate.replace("{0}", i + "").replace("{1}",
                    change(cellw * i) + ""));
        }
        sb.append(WTemplate.replace("{0}", "320").replace("{1}", w + ""));
        sb.append("</resources>");
 
        StringBuffer sb2 = new StringBuffer();
        sb2.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
        sb2.append("<resources>");
        float cellh = h / dh;
        for (int i = 1; i < 480; i++) {
            sb2.append(HTemplate.replace("{0}", i + "").replace("{1}",
                    change(cellh * i) + ""));
        }
        sb2.append(HTemplate.replace("{0}", "480").replace("{1}", h + ""));
        sb2.append("</resources>");
 
        String path = rootPath.replace("{0}", h + "").replace("{1}", w + "");
        File rootFile = new File(path);
        if (!rootFile.exists()) {
            rootFile.mkdirs();
        }
        File layxFile = new File(path + "lay_x.xml");
        File layyFile = new File(path + "lay_y.xml");
        try {
            PrintWriter pw = new PrintWriter(new FileOutputStream(layxFile));
            pw.print(sb.toString());
            pw.close();
            pw = new PrintWriter(new FileOutputStream(layyFile));
            pw.print(sb2.toString());
            pw.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
 
    }
 
    public static float change(float a) {
        int temp = (int) (a * 100);
        return temp / 100f;
    }
}

代碼應(yīng)該很好懂撒犀,我們將一個屏幕寬度分為320份,高度480份叠聋,然后按照實(shí)際像素對每一個單位進(jìn)行復(fù)制汗侵,放在對應(yīng)values-widthxheight文件夾下面的lax.xml和lay.xml里面幸缕,這樣就可以統(tǒng)一所有你想要的分辨率的單位了,下面是生成的一個320*480分辨率的文件晰韵,因?yàn)閷捀叻指钪罂偡謹(jǐn)?shù)和像素?cái)?shù)相同发乔,所以x1就是1px,以此類推

<?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>

那么1080*1960分辨率下是什么樣子呢宫屠?我們可以看下列疗,由于1080和320是3.37倍的關(guān)系,所以x1=3.37px

<?xml version="1.0" encoding="utf-8"?>
<resources><dimen name="x1">3.37px</dimen>
<dimen name="x2">6.75px</dimen>
<dimen name="x3">10.12px</dimen>
<dimen name="x4">13.5px</dimen>
<dimen name="x5">16.87px</dimen>
<dimen name="x6">20.25px</dimen>
<dimen name="x7">23.62px</dimen>
<dimen name="x8">27.0px</dimen>
<dimen name="x9">30.37px</dimen>
<dimen name="x10">33.75px</dimen>
...省略好多行
<dimen name="x300">1012.5px</dimen>
<dimen name="x301">1015.87px</dimen>
<dimen name="x302">1019.25px</dimen>
<dimen name="x303">1022.62px</dimen>
<dimen name="x304">1026.0px</dimen>
<dimen name="x305">1029.37px</dimen>
<dimen name="x306">1032.75px</dimen>
<dimen name="x307">1036.12px</dimen>
<dimen name="x308">1039.5px</dimen>
<dimen name="x309">1042.87px</dimen>
<dimen name="x310">1046.25px</dimen>
<dimen name="x311">1049.62px</dimen>
<dimen name="x312">1053.0px</dimen>
<dimen name="x313">1056.37px</dimen>
<dimen name="x314">1059.75px</dimen>
<dimen name="x315">1063.12px</dimen>
<dimen name="x316">1066.5px</dimen>
<dimen name="x317">1069.87px</dimen>
<dimen name="x318">1073.25px</dimen>
<dimen name="x319">1076.62px</dimen>
<dimen name="x320">1080px</dimen>
</resources>

無論在什么分辨率下浪蹂,x320都是代表屏幕寬度抵栈,y480都是代表屏幕高度。

那么坤次,我們應(yīng)該如何使用呢古劲?

首先,我們要把生成的所有values文件夾放到res目錄下缰猴,當(dāng)設(shè)計(jì)師把UI高清設(shè)計(jì)圖給你之后产艾,你就可以根據(jù)設(shè)計(jì)圖上的尺寸,以某一個分辨率的機(jī)型為基礎(chǔ)滑绒,找到對應(yīng)像素?cái)?shù)的單位闷堡,然后設(shè)置給控件即可。

下圖還是兩個Button疑故,不同的是杠览,我們把單位換成了我們在values文件夾下dimen的值,這樣在你指定的分辨率下纵势,不管寬度是320dp踱阿、360dp,還是410dp钦铁,就都可以完全適配了软舌。

但是,還是有個問題牛曹,為什么下面的三個沒有適配呢佛点?

這是因?yàn)橛捎谠谏傻膙alues文件夾里,沒有對應(yīng)的分辨率躏仇,其實(shí)一開始是報錯的恋脚,因?yàn)槟J(rèn)的values沒有對應(yīng)dimen腺办,所以我只能在默認(rèn)values里面也創(chuàng)建對應(yīng)文件焰手,但是里面的數(shù)據(jù)卻不好處理糟描,因?yàn)椴恢婪直媛剩抑缓媚J(rèn)為x1=1dp保證盡量兼容书妻。這也是這個解決方案的幾個弊端船响,對于沒有生成對應(yīng)分辨率文件的手機(jī),會使用默認(rèn)values文件夾躲履,如果默認(rèn)文件夾沒有见间,就會出現(xiàn)問題。

所以說工猜,這個方案雖然是一勞永逸米诉,但是由于實(shí)際上還是使用的px作為長度的度量單位,所以多少和google的要求有所背離篷帅,不好說以后會不會出現(xiàn)什么不可預(yù)測的問題史侣。其次,如果要使用這個方案魏身,你必須盡可能多的包含所有的分辨率惊橱,因?yàn)檫@個是使用這個方案的基礎(chǔ),如果有分辨率缺少箭昵,會造成顯示效果很差税朴,甚至出錯的風(fēng)險,而這又勢必會增加軟件包的大小和維護(hù)的難度家制,所以大家自己斟酌正林,擇優(yōu)使用。

提供備用位圖

由于 Android 可在具有各種屏幕密度的設(shè)備上運(yùn)行颤殴,因此我們提供的位圖資源應(yīng)始終可以滿足各類普遍密度范圍的要求:低密度觅廓、中等密度、高密度以及超高密度诅病。這將有助于我們的圖片在所有屏幕密度上都能得到出色的質(zhì)量和效果哪亿。

要生成這些圖片,我們應(yīng)先提取矢量格式的原始資源贤笆,然后根據(jù)以下尺寸范圍針對各密度生成相應(yīng)的圖片蝇棉。

    xhdpi:2.0
    hdpi:1.5
    mdpi:1.0(最低要求)
    ldpi:0.75

也就是說,如果我們?yōu)?xhdpi 設(shè)備生成了 200x200 px尺寸的圖片芥永,就應(yīng)該使用同一資源為 hdpi篡殷、mdpi 和 ldpi 設(shè)備分別生成 150x150、100x100 和 75x75 尺寸的圖片埋涧。

然后板辽,將生成的圖片文件放在 res/ 下的相應(yīng)子目錄中(mdpi奇瘦、hdpi、xhdpi劲弦、xxhdpi)耳标,系統(tǒng)就會根據(jù)運(yùn)行您應(yīng)用的設(shè)備的屏幕密度自動選擇合適的圖片。

這樣一來邑跪,只要我們引用 @drawable/id次坡,系統(tǒng)都能根據(jù)相應(yīng)屏幕的 dpi 選取合適的位圖。

還記得我們上面提到的圖標(biāo)設(shè)計(jì)尺寸嗎画畅?和這個其實(shí)是一個意思砸琅。

但是還有個問題需要注意下,如果是.9圖或者是不需要多個分辨率的圖片轴踱,就放在drawable文件夾即可症脂,對應(yīng)分辨率的圖片要正確的放在合適的文件夾,否則會造成圖片拉伸等問題淫僻。

實(shí)施自適應(yīng)用戶界面流程

前面我們介紹過诱篷,如何根據(jù)設(shè)備特點(diǎn)顯示恰當(dāng)?shù)牟季郑沁@樣做嘁傀,會使得用戶界面流程可能會有所不同兴蒸。例如,如果應(yīng)用處于雙面板模式下细办,點(diǎn)擊左側(cè)面板上的項(xiàng)即可直接在右側(cè)面板上顯示相關(guān)內(nèi)容橙凳;而如果該應(yīng)用處于單面板模式下,點(diǎn)擊相關(guān)的內(nèi)容應(yīng)該跳轉(zhuǎn)到另外一個Activity進(jìn)行后續(xù)的處理笑撞。所以我們應(yīng)該按照下面的流程岛啸,一步步的完成自適應(yīng)界面的實(shí)現(xiàn)。

確定當(dāng)前布局

由于每種布局的實(shí)施都會稍有不同茴肥,因此我們需要先確定當(dāng)前向用戶顯示的布局坚踩。例如,我們可以先了解用戶所處的是“單面板”模式還是“雙面板”模式瓤狐。要做到這一點(diǎn)瞬铸,可以通過查詢指定視圖是否存在以及是否已顯示出來。

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;
    }
}

請注意础锐,這段代碼用于查詢“報道”面板是否可用嗓节,與針對具體布局的硬編碼查詢相比,這段代碼的靈活性要大得多皆警。

再舉一個適應(yīng)各種組件的存在情況的方法示例:在對這些組件執(zhí)行操作前先查看它們是否可用拦宣。例如,新聞閱讀器示例應(yīng)用中有一個用于打開菜單的按鈕,但只有在版本低于 3.0 的 Android 上運(yùn)行該應(yīng)用時鸵隧,這個按鈕才會存在绸罗,因?yàn)?API 級別 11 或更高級別中的 ActionBar 已取代了該按鈕的功能。因此豆瘫,您可以使用以下代碼為此按鈕添加事件偵聽器:

Button catButton = (Button) findViewById(R.id.categorybutton);
OnClickListener listener = /* create your listener here */;
if (catButton != null) {
    catButton.setOnClickListener(listener);
}
根據(jù)當(dāng)前布局做出響應(yīng)

有些操作可能會因當(dāng)前的具體布局而產(chǎn)生不同的結(jié)果珊蟀。例如,在新聞閱讀器示例中靡羡,如果用戶界面處于雙面板模式下系洛,那么點(diǎn)擊標(biāo)題列表中的標(biāo)題就會在右側(cè)面板中打開相應(yīng)報道俊性;但如果用戶界面處于單面板模式下略步,那么上述操作就會啟動一個獨(dú)立活動:

@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);
    }
}

同樣,如果該應(yīng)用處于雙面板模式下定页,就應(yīng)設(shè)置帶導(dǎo)航標(biāo)簽的操作欄趟薄;但如果該應(yīng)用處于單面板模式下,就應(yīng)使用下拉菜單設(shè)置導(dǎo)航欄典徊。因此我們的代碼還應(yīng)確定哪種情況比較合適:

final String CATEGORIES[] = { "熱門報道", "政治", "經(jīng)濟(jì)", "Technology" };
 
public void onCreate(Bundle savedInstanceState) {
    ....
    if (mIsDualPane) {
        /* use tabs for navigation */
        actionBar.setNavigationMode(android.app.ActionBar.NAVIGATION_MODE_TABS);
        int i;
        for (i = 0; i < CATEGORIES.length; i++) {
            actionBar.addTab(actionBar.newTab().setText(
                CATEGORIES[i]).setTabListener(handler));
        }
        actionBar.setSelectedNavigationItem(selTab);
    }
    else {
        /* use list navigation (spinner) */
        actionBar.setNavigationMode(android.app.ActionBar.NAVIGATION_MODE_LIST);
        SpinnerAdapter adap = new ArrayAdapter(this,
                R.layout.headline_item, CATEGORIES);
        actionBar.setListNavigationCallbacks(adap, handler);
    }
}
重復(fù)使用其他活動中的片段

多屏幕設(shè)計(jì)中的重復(fù)模式是指杭煎,對于某些屏幕配置,已實(shí)施界面的一部分會用作面板卒落;但對于其他配置羡铲,這部分就會以獨(dú)立活動的形式存在。例如儡毕,在新聞閱讀器示例中也切,對于較大的屏幕,新聞報道文本會顯示在右側(cè)面板中腰湾;但對于較小的屏幕雷恃,這些文本就會以獨(dú)立活動的形式存在。

在類似情況下费坊,通车够保可以在多個活動中重復(fù)使用相同的 Fragment 子類以避免代碼重復(fù)。例如附井,在雙面板布局中使用了 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布局中重復(fù)使用了它 :

ArticleFragment frag = new ArticleFragment();
getSupportFragmentManager().beginTransaction().add(android.R.id.content, frag).commit();

當(dāng)然讨越,這與在 XML 布局中聲明片段的效果是一樣的,但在這種情況下卻沒必要使用 XML 布局永毅,因?yàn)閳蟮榔问谴嘶顒又械奈ㄒ唤M件把跨。

請務(wù)必在設(shè)計(jì)片段時注意,不要針對具體活動創(chuàng)建強(qiáng)耦合卷雕。要做到這一點(diǎn)节猿,通常可以定義一個接口,該接口概括了相關(guān)片段與其主活動交互所需的全部方式滨嘱,然后讓主活動實(shí)施該界面:

例如峰鄙,新聞閱讀器應(yīng)用的 HeadlinesFragment 會精確執(zhí)行以下代碼:

public class HeadlinesFragment extends ListFragment {
    ...
    OnHeadlineSelectedListener mHeadlineSelectedListener = null;
 
    /* Must be implemented by host activity */
    public interface OnHeadlineSelectedListener {
        public void onHeadlineSelected(int index);
    }
    ...
 
    public void setOnHeadlineSelectedListener(OnHeadlineSelectedListener listener) {
        mHeadlineSelectedListener = listener;
    }
}

然后,如果用戶選擇某個標(biāo)題太雨,相關(guān)片段就會通知由主活動指定的偵聽器(而不是通知某個硬編碼的具體活動):

public class HeadlinesFragment extends ListFragment {
    ...
    @Override
    public void onItemClick(AdapterView<?> parent,
                            View view, int position, long id) {
        if (null != mHeadlineSelectedListener) {
            mHeadlineSelectedListener.onHeadlineSelected(position);
        }
    }
    ...
}

除此之外吟榴,我們還可以使用第三方框架,比如說使用“訂閱-發(fā)布”模式的EventBus來更多的優(yōu)化組件之間的通信囊扳,減少耦合吩翻。

處理屏幕配置變化

如果我們使用獨(dú)立Activity實(shí)施界面的獨(dú)立部分,那么請注意锥咸,我們可能需要對特定配置變化(例如屏幕方向的變化)做出響應(yīng)狭瞎,以便保持界面的一致性。

例如搏予,在運(yùn)行 Android 3.0 或更高版本的標(biāo)準(zhǔn) 7 英寸平板電腦上熊锭,如果新聞閱讀器示例應(yīng)用運(yùn)行在縱向模式下,就會在使用獨(dú)立活動顯示新聞報道雪侥;但如果該應(yīng)用運(yùn)行在橫向模式下碗殷,就會使用雙面板布局。

也就是說速缨,如果用戶處于縱向模式下且屏幕上顯示的是用于閱讀報道的活動锌妻,那么就需要在檢測到屏幕方向變化(變成橫向模式)后執(zhí)行相應(yīng)操作,即停止上述活動并返回主活動旬牲,以便在雙面板布局中顯示相關(guān)內(nèi)容:

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;
        }
        ...
}

通過上面幾個步驟仿粹,我們就完全可以建立一個可以根據(jù)用戶界面配置進(jìn)行自適應(yīng)的App了。

最佳實(shí)踐

關(guān)于高清設(shè)計(jì)圖尺寸

Google官方給出的高清設(shè)計(jì)圖尺寸有兩種方案引谜,一種是以mdpi設(shè)計(jì)牍陌,然后對應(yīng)放大得到更高分辨率的圖片,另外一種則是以高分辨率作為設(shè)計(jì)大小员咽,然后按照倍數(shù)對應(yīng)縮小到小分辨率的圖片毒涧。

根據(jù)經(jīng)驗(yàn),我更推薦第二種方法贝室,因?yàn)樾》直媛试谏筛叻直媛蕡D片的時候契讲,會出現(xiàn)像素丟失,我不知道是不是有方法可以阻止這種情況發(fā)生滑频。

而分辨率可以以1280720或者是19601080作為主要分辨率進(jìn)行設(shè)計(jì)捡偏。

ImageView的ScaleType屬性

設(shè)置不同的ScaleType會得到不同的顯示效果,一般情況下峡迷,設(shè)置為centerCrop能獲得較好的適配效果银伟。

動態(tài)設(shè)置

有一些情況下你虹,我們需要動態(tài)的設(shè)置控件大小或者是位置,比如說popwindow的顯示位置和偏移量等彤避,這個時候我們可以動態(tài)的獲取當(dāng)前的屏幕屬性傅物,然后設(shè)置合適的數(shù)值

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();
    }
 
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市琉预,隨后出現(xiàn)的幾起案子董饰,更是在濱河造成了極大的恐慌,老刑警劉巖圆米,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件卒暂,死亡現(xiàn)場離奇詭異,居然都是意外死亡娄帖,警方通過查閱死者的電腦和手機(jī)也祠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來块茁,“玉大人齿坷,你說我怎么就攤上這事∈福” “怎么了?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵崎场,是天一觀的道長佩耳。 經(jīng)常有香客問我,道長谭跨,這世上最難降的妖魔是什么干厚? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮螃宙,結(jié)果婚禮上蛮瞄,老公的妹妹穿的比我還像新娘。我一直安慰自己谆扎,他們只是感情好挂捅,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著堂湖,像睡著了一般闲先。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上无蜂,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天伺糠,我揣著相機(jī)與錄音,去河邊找鬼斥季。 笑死训桶,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播舵揭,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼慰照,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了琉朽?” 一聲冷哼從身側(cè)響起毒租,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎箱叁,沒想到半個月后墅垮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡耕漱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年算色,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片螟够。...
    茶點(diǎn)故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡灾梦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出妓笙,到底是詐尸還是另有隱情若河,我是刑警寧澤,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布寞宫,位于F島的核電站萧福,受9級特大地震影響辈赋,放射性物質(zhì)發(fā)生泄漏鲫忍。R本人自食惡果不足惜篷就,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一钾麸、第九天 我趴在偏房一處隱蔽的房頂上張望更振。 院中可真熱鬧,春花似錦饭尝、人聲如沸肯腕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽实撒。三九已至,卻和暖如春涉瘾,著一層夾襖步出監(jiān)牢的瞬間知态,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工立叛, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留负敏,地道東北人。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓秘蛇,卻偏偏與公主長得像其做,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子赁还,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評論 2 345

推薦閱讀更多精彩內(nèi)容