1. 程式人生 > >Android-螢幕適配全攻略(轉)

Android-螢幕適配全攻略(轉)

2012年到2014年支援Android裝置的種類從3997增長到18796。同時各大廠商定製的螢幕尺寸也非常多。這將非常不利於我們進行螢幕適配。這要求我們必須掌握螢幕適配技能,以便使我們的app可以適用於不同螢幕尺寸的裝置上。

從上圖可以看出,主流的解析度是前六種:1280×720、1920×1080、800×480、854×480、960×540、1184×720,不過我們有解決方案。看完這篇文章,想必你就可以解決常見的螢幕適配問題。

一. 核心概念與單位詳解

介紹幾個在Android螢幕適配上非常重要的名詞:

1. 什麼是螢幕尺寸、螢幕解析度、螢幕畫素密度?

螢幕尺寸是指螢幕對角線的長度。單位是英寸,1英寸=2.54釐米
螢幕解析度

是指在橫縱向上的畫素點數,單位是px,1px=1畫素點,一般是縱向畫素橫向畫素,如1280×720
螢幕畫素密度是指每英寸上的畫素點數,單位是dpi,即“dot per inch”的縮寫,畫素密度和螢幕尺寸和螢幕解析度有關


螢幕畫素密度計算公式


例如:計算Nexus5的螢幕畫素密度:
螢幕尺寸:4.95inch、解析度:1920×1080,螢幕畫素密度:445



和官方給出的一樣,說明我們計算正確。

2. 什麼是dp、dip、dpi、sp、px?之間的關係是什麼?

dip:Density Independent Pixels(密度無關畫素)的縮寫。以160dpi為基準,1dp=1px
dp:

dip
dpi:螢幕畫素密度的單位,“dot per inch”的縮寫

px:畫素,物理上的絕對單位

sp:Scale-Independent Pixels的縮寫,可以根據文字大小首選項自動進行縮放。Google推薦我們使用12sp以上的大小,通常可以使用12sp,14sp,18sp,22sp,最好不要使用奇數和小數。


說明:如果A裝置的引數為480×320,160dpi,B設定的引數為800×480,240dpi。我們要畫出一條和螢幕寬度一樣長的直線,如果使用px作為單位,必須在A裝置上設定為320px,在B裝置上設定480px。但是如果我們使用dp作為單位,由於以160dpi為基準,1dp=1px,所以A裝置上設定為320dp就等於螢幕寬度(320px),在B裝置上設定為320dp就等於320×(240/160)=480px,即B裝置的螢幕寬度。這樣,使用dp

作為單位就可以實現簡單的螢幕適配。這知識一種巧合,也有B裝置的畫素密度不是這樣剛剛好的,就需要我們運用別的螢幕適配技術。

3. 什麼是mdpi、hdpi、xdpi、xxdpi、xxxdpi?如何計算和區分?

用於區分不同的畫素密度。

名稱 畫素密度範圍 圖片大小
mdpi 120dp~160dp 48×48px
hdpi 160dp~240dp 72×72px
xhdpi 240dp~320dp 96×96px
xxhdpi 320dp~480dp 144×144px
xxxhdpi 480dp~640dp 192×192px

在Google官方開發文件中,說明了 mdpi:hdpi:xhdpi:xxhdpi:xxxhdpi=2:3:4:6:8 的尺寸比例進行縮放。例如,一個圖示的大小為48×48dp,表示在mdpi上,實際大小為48×48px,在hdpi畫素密度上,實際尺寸為mdpi上的1.5倍,即72×72px,以此類推。

二. 解決方案-支援各種螢幕尺寸

我們可以通過以下幾種方式來支援各種螢幕尺寸:

1. 使用wrap_content、math_parent、weight

wrap_content:根據控制元件的內容設定控制元件的尺寸
math_parent:根據父控制元件的尺寸大小設定控制元件的尺寸
weight:權重,線上性佈局中可以使用weight屬性設定控制元件所佔的比例

例如,我們要實現下圖所顯示的效果:當螢幕尺寸改變時,new reader控制元件兩邊的控制元件大小不變,new reader控制元件會佔完剩餘的空間。


實現:通過給new reader控制元件設定屬性:android:layout_width="wrap_content"android:layout_weight="1",兩邊兩個控制元件為固定大小,android:layout_weight="0"

具體佈局檔案如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    
android:layout_width="match_parent"    
android:layout_height="match_parent"    
android:orientation="horizontal">    
<TextView        
      android:layout_width="80dp"        
      android:layout_height="80dp"        
      android:layout_weight="0"        
      android:background="#028330"/>    
<TextView        
      android:layout_width="wrap_content"        
      android:layout_height="80dp"        
      android:text="new
reader"        
      android:textSize="22sp"        
      android:layout_weight="1"/>    
<TextView        
      android:layout_width="160dp"        
      android:layout_height="80dp"       
      android:text="Politics"        
      android:textSize="18sp"        
      android:layout_weight="0"        
      android:background="#028330"/>
</LinearLayout>

小插曲:關於android:layout_weight屬性

公式:所佔寬度=原來寬度+剩餘空間所佔百分比的寬度

一般情況,我們都是設定要進行比例分配的方向的寬度為0dp,然後再用權重進行分配。如下:

<Button    
      android:layout_width="0dp"    
      android:layout_height="wrap_content"    
      android:layout_weight="1"    
      android:text="Button1" />
<Button    
     android:layout_width="0dp"    
     android:layout_height="wrap_content"    
     android:layout_weight="2"    
     android:text="Button2" />

效果為:


寬度為0dp時,所佔比例


設螢幕寬度為L,
根據公式,
button1寬度=0+L×1/(1+2)=1/3L
button2寬度=0+L×2/(1+2)=2/3L
但如果設定為match_parent

<Button    
      android:layout_width="match_parent"    
      android:layout_height="wrap_content"    
      android:layout_weight="1"    
      android:text="Button1" />
<Button    
     android:layout_width="match_parent"    
     android:layout_height="wrap_content"    
     android:layout_weight="2"    
     android:text="Button2" />

效果為:


寬度為match_parent時,所佔比例

button1寬度=L+(L-2L)×1/3=2/3L
button2寬度=L+(L-2L)×2/3=1/3L

當然,還有其他的方式,都可以運用此公式進行計算。
在實際開發中,我們一般使用0dp的方式,而不使用其他方式。

2. 使用相對佈局,禁用絕對佈局

簡單的佈局一般都使用線性佈局,而略微複雜點的佈局,我們使用相對佈局,大多數時候,我們都是使用這兩種佈局的巢狀。

我們使用相對佈局的原因是,相對佈局能在各種尺寸的螢幕上保持控制元件間的相對位置。

3. 使用限定符

  • 使用尺寸限定符
    當我們要在大螢幕上顯示不同的佈局,就要使用large限定符。例如,在寬的螢幕左邊顯示列表右邊顯示列表項的詳細資訊,在一般寬度的螢幕只顯示列表,不顯示列表項的詳細資訊,我們就可以使用large限定符。

    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>

    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>

    如果這個程式執行在螢幕尺寸大於7inch的裝置上,系統就會載入res/layout-large/main.xml 而不是res/layout/main.xml,在小於7inch的裝置上就會載入res/layout/main.xml

    需要注意的是,這種通過large限定符分辨螢幕尺寸的方法,適用於android3.2之前。在android3.2之後,為了更精確地分辨螢幕尺寸大小,Google推出了最小寬度限定符。

  • 使用最小寬度限定符
    最小寬度限定符的使用和large基本一致,只是使用了具體的寬度限定。
    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>

    res/layout-sw600dp/main.xml,雙面板佈局: Small Width 最小寬度

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

    這種方式是不區分螢幕方向的。這種最小寬度限定符適用於android3.2之後,所以如果要適配android全部的版本,就要使用large限定符和sw600dp檔案同時存在於專案res目錄下。

    這就要求我們維護兩個相同功能的檔案。為了避免繁瑣操作,我們就要使用佈局別名。

  • 使用佈局別名
    res/layout/main.xml: 單面板佈局
    res/layout-large/main.xml: 多面板佈局
    res/layout-sw600dp/main.xml: 多面板佈局

    由於後兩個文具檔案一樣,我們可以用以下兩個檔案代替上面三個佈局檔案:

    res/layout/main.xml 單面板佈局
    res/layout/main_twopanes.xml 雙面板佈局

    然後在res下建立
    res/values/layout.xml
    res/values-large/layout.xml
    res/values-sw600dp/layout.xml三個檔案。

    預設佈局
    res/values/layout.xml:

    <resources>
      <item name="main" type="layout">@layout/main</item>
    </resources>
  • Android3.2之前的平板佈局
    res/values-large/layout.xml:

    <resources>
      <item name="main" type="layout">@layout/main_twopanes</item>
    </resources>

    Android3.2之後的平板佈局
    res/values-sw600dp/layout.xml:

    <resources>
      <item name="main" type="layout">@layout/main_twopanes</item>
    </resources>

    這樣就有了main為別名的佈局。
    在activity中setContentView(R.layout.main);

    這樣,程式在執行時,就會檢測手機的螢幕大小,如果是平板裝置就會載入res/layout/main_twopanes.xml,如果是手機裝置,就會載入res/layout/main.xml 。我們就解決了只使用一個佈局檔案來適配android3.2前後的所有平板裝置。

  • 使用螢幕方向限定符

    如果我們要求給橫屏、豎屏顯示的佈局不一樣。就可以使用螢幕方向限定符來實現。
    例如,要在平板上實現橫豎屏顯示不用的佈局,可以用以下方式實現。
    res/values-sw600dp-land/layouts.xml:橫屏

    <resources>
      <item name="main" type="layout">@layout/main_twopanes</item>
    </resources>

    res/values-sw600dp-port/layouts.xml:豎屏

    <resources>
      <item name="main" type="layout">@layout/main</item>
    </resources>

4. 使用自動拉伸點陣圖

自動拉伸點陣圖,即android下特有的.9.png圖片格式。

當我們需要使圖片在拉伸後還能保持一定的顯示效果,比如,不能使圖片中的重要畫素拉伸,不能使內容區域受到拉伸的影響,我們就可以使用.9.png圖來實現。

要使用.9.png,必須先得建立.9.png圖片,androidSDK給我們提供了的工具就包含.9.png檔案的建立和修改工具。雙擊SDK安裝目錄 oolsdraw9patch.bat,就會開啟下圖所示的視窗。


.9.png圖片製作器
  • 直接把圖片拖進去,選擇Filesave.9.png,即可儲存為.9.png圖片。
    不過,這和原圖沒有什麼區別,我們要製作成自動拉伸的圖片,還需要進行簡單的處理。

  • 在左邊和上邊點下一個畫素或多個畫素點,是選擇了拉伸的畫素,即把選擇的畫素點進行拉伸。選擇的畫素點越多,拉伸的程度也越大。

  • 右邊和下邊是選擇內容區域,在右邊和下邊畫上直線,交叉的區域就是內容區域。

  • 什麼事內容區域呢?
    比如,我們給Button設定了一個.9.png作為背景,還要設定其android:text屬性,設定的text所佔的位置就是內容區域。

  • 我們必須保證內容區域包含text文字,才會正常顯示出text文字的內容。

下面是一個例子:


button背景.9.png


上圖是我們製作的.9.png,設定好了左上拉伸畫素點和內容區域。

Button屬性設定:

<Button    
    android:layout_width="300dp"    
    android:layout_height="300dp"    
    android:background="@drawable/button"    
    android:text="Button" />

Button的顯式效果

如果我們選擇的內容區域偏差太大,可能就不會顯示出text值BUTTON