Android開發:最全面、最易懂的Android屏幕適配解決方案

前言

Android的屏幕適配一直以來都在折磨着我們Android開發者,本文將結合:

給你帶來一種全新、全面而邏輯清晰的Android屏幕適配思路,只要你認真閱讀,保證你能解決Android的屏幕適配問題!


目錄


Android屏幕適配解決方案.png

定義

使得某一元素在Android不同尺寸、不同分辨率的手機上具備相同的顯示效果


相關重要概念

屏幕尺寸

  • 含義:手機對角線的物理尺寸
  • 單位:英寸(inch),1英寸=2.54cm

    Android手機常見的尺寸有5寸、5.5寸、6寸等等

屏幕分辨率

  • 含義:手機在橫向、縱向上的像素點數總和
    1. 一般描述成屏幕的"寬x高”=AxB
    2. 含義:屏幕在橫向方向(寬度)上有A個像素點,在縱向方向
      (高)有B個像素點
    3. 例子: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,可以保證在不同屏幕像素密度的設備上顯示相同的效果

    1. Android開發時用dp而不是px單位設置圖片大小,是Android特有的單位
    2. 場景:假如同樣都是畫一條長度是屏幕一半的線,如果使用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
    1. Android開發時用此單位設置文字大小,可根據字體大小首選項進行縮放
    2. 推薦使用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佈局、用戶界面流程

  • 使得“圖片資源”匹配不同的屏幕密度

解決方案

  • 問題:如何進行屏幕尺寸匹配?
  • 答:

屏幕尺寸適配解決方案.png

“佈局”匹配

本質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. 確定當前佈局
  2. 根據當前佈局做出響應
  3. 重複使用其他活動中的片段
  4. 處理屏幕配置變化
  • 步驟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();
  • 步驟3:處理屏幕配置變化
    如果我們使用獨立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了。


總結

經過上面的介紹,對於屏幕尺寸大小適配問題應該是不成問題了。


解決方案

  • 問題:如何進行屏幕密度匹配?
  • 答:

屏幕密度匹配解決方案.png

“佈局控件”匹配

本質:使得佈局組件在不同屏幕密度上顯示相同的像素效果

  • 做法1:使用密度無關像素
    由於各種屏幕的像素密度都有所不同,因此相同數量的像素在不同設備上的實際大小也有所差異,這樣使用像素(px)定義佈局尺寸就會產生問題。
    因此,請務必使用密度無關像素 dp 或獨立比例像素 sp 單位指定尺寸。
  • 相關概念介紹
    密度無關像素
  • 含義:density-independent pixel,叫dp或dip,與終端上的實際物理像素點無關。
  • 單位:dp,可以保證在不同屏幕像素密度的設備上顯示相同的效果

    1. Android開發時用dp而不是px單位設置圖片大小,是Android特有的單位
    2. 場景:假如同樣都是畫一條長度是屏幕一半的線,如果使用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
    1. Android開發時用此單位設置文字大小,可根據用戶的偏好文字大小/字體大小首選項進行縮放
    2. 推薦使用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解決呢?

即根據不同屏幕密度,控件選擇對應的像素值大小

接下來介紹一種方法:百分比適配方法,步驟如下:

  1. 以某一分辨率爲基準,生成所有分辨率對應像素數列表
  2. 將生成像素數列表存放在res目錄下對應的values文件下
  3. 根據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 7351152_3200,4500

步驟2:把生成的各像素數列表放到對應的資源文件

將生成像素數列表(lay_x.xml和lay_y.xml)存放在res目錄下對應的values文件(注意寬、高要對應),如下圖:


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

總結

本文根據現今主流Android的適配方法,以邏輯清晰的方式進行了主流Android適配方法的全面整理,

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章