Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android屏幕適配全攻略(最權威的官方適配指導)

Android屏幕適配全攻略(最權威的官方適配指導)

編輯:關於Android編程

Android的屏幕適配一直以來都在折磨著我們這些開發者,本篇文章以Google的官方文檔為基礎,全面而深入的講解了Android屏幕適配的原因、重要概念、解決方案及最佳實踐,我相信如果你能認真的學習本文,對於Android的屏幕適配,你將有所收獲!

Android屏幕適配出現的原因
  • 2012年,支持Android的設備共有3997種。
  • 2013年,支持Android的設備共有11868種。
  • 2014年,支持Android的設備共有18796種。

下面這張圖片所顯示的內容足以充分說明當今Android系統碎片化問題的嚴重性,因為該圖片中的每一個矩形都代表著一種Android設備。

\vczWtcSjrNTyyse21M7Sw8e/qrei07DP7LHIvc+087XEJm1kYXNoOyZtZGFzaDvGwcS7tcTL6casu6+hozwvcD4NCjxwPs/Cw+bV4tXFzbzKx0FuZHJvaWTGwcS7s9+057XEyr7S4s28o6zU2tXi1cXNvMDvw+ajrMC2yau+2NDOtcS089ChtPqx7bK7zayz37Tno6zR1cmrye7Hs9TytPqx7cv51byw2bfWsci1xLTz0KGhozwvcD4NCjxwPjxpbWcgYWx0PQ=="" src="/uploadfile/Collfiles/20160525/20160525090553108.png" title="\" />

而與之相對應的,則是下面這張圖。這張圖顯示了iOS設備所需要進行適配的屏幕尺寸和占比。

\

當然,這張圖片只是4,4s,5,5c,5s和平板的尺寸,現在還應該加上新推出的iphone6和plus,但是和Android的屏幕碎片化程度相比而言,還是差的太遠。

詳細的統計數據請到這裡查看

現在你應該很清楚為什麼要對Android的屏幕進行適配了吧?屏幕尺寸這麼多,為了讓我們開發的程序能夠比較美觀的顯示在不同尺寸、分辨率、像素密度(這些概念我會在下面詳細講解)的設備上,那就要在開發的過程中進行處理,至於如何去進行處理,這就是我們今天的主題了。

但是在開始進入主題之前,我們再來探討一件事情,那就是Android設備的屏幕尺寸,從幾寸的智能手機,到10寸的平板電腦,再到幾十寸的數字電視,我們應該適配哪些設備呢?

其實這個問題不應該這麼考慮,因為對於具有相同像素密度的設備來說,像素越高,尺寸就越大,所以我們可以換個思路,將問題從單純的尺寸大小轉換到像素大小和像素密度的角度來。

下圖是2014年初,友盟統計的占比5%以上的6個主流分辨率,可以看出,占比最高的是480*800,320*480的設備竟然也占據了很大比例,但是和半年前的數據相比較,中低分辨率(320*480、480*800)的比例在減少,而中高分辨率的比例則在不斷地增加。雖然每個分辨率所占的比例在變化,但是總的趨勢沒變,還是這六種,只是分辨率在不斷地提高。

\

所以說,我們只要盡量適配這幾種分辨率,就可以在大部分的手機上正常運行了。

當然了,這只是手機的適配,對於平板設備(電視也可以看做是平板),我們還需要一些其他的處理。

好了,到目前為止,我們已經弄清楚了Android開發為什麼要進行適配,以及我們應該適配哪些對象,接下來,終於進入我們的正題了!

首先,我們先要學習幾個重要的概念。

重要概念

什麼是屏幕尺寸、屏幕分辨率、屏幕像素密度?
什麼是dp、dip、dpi、sp、px?他們之間的關系是什麼?
什麼是mdpi、hdpi、xdpi、xxdpi?如何計算和區分?

在下面的內容中我們將介紹這些概念。

屏幕尺寸

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

比如常見的屏幕尺寸有2.4、2.8、3.5、3.7、4.2、5.0、5.5、6.0等

屏幕分辨率

屏幕分辨率是指在橫縱向上的像素點數,單位是px,1px=1個像素點。一般以縱向像素*橫向像素,如1960*1080。

屏幕像素密度

屏幕像素密度是指每英寸上的像素點數,單位是dpi,即“dot per inch”的縮寫。屏幕像素密度與屏幕尺寸和屏幕分辨率有關,在單一變化條件下,屏幕尺寸越小、分辨率越高,像素密度越大,反之越小。

dp、dip、dpi、sp、px

px我們應該是比較熟悉的,前面的分辨率就是用的像素為單位,大多數情況下,比如UI設計、Android原生API都會以px作為統一的計量單位,像是獲取屏幕寬高等。

dip和dp是一個意思,都是Density Independent Pixels的縮寫,即密度無關像素,上面我們說過,dpi是屏幕像素密度,假如一英寸裡面有160個像素,這個屏幕的像素密度就是160dpi,那麼在這種情況下,dp和px如何換算呢?在Android中,規定以160dpi為基准,1dip=1px,如果密度是320dpi,則1dip=2px,以此類推。

假如同樣都是畫一條320px的線,在480*800分辨率手機上顯示為2/3屏幕寬度,在320*480的手機上則占滿了全屏,如果使用dp為單位,在這兩種分辨率下,160dp都顯示為屏幕一半的長度。這也是為什麼在Android開發中,寫布局的時候要盡量使用dp而不是px的原因。

而sp,即scale-independent pixels,與dp類似,但是可以根據文字大小首選項進行放縮,是設置字體大小的御用單位。

mdpi、hdpi、xdpi、xxdpi

其實之前還有個ldpi,但是隨著移動設備配置的不斷升級,這個像素密度的設備已經很罕見了,所在現在適配時不需考慮。

mdpi、hdpi、xdpi、xxdpi用來修飾Android中的drawable文件夾及values文件夾,用來區分不同像素密度下的圖片和dimen值。

那麼如何區分呢?Google官方指定按照下列標准進行區分:

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

在進行開發的時候,我們需要把合適大小的圖片放在合適的文件夾裡面。下面以圖標設計為例進行介紹。

\

在設計圖標時,對於五種主流的像素密度(MDPI、HDPI、XHDPI、XXHDPI 和 XXXHDPI)應按照 2:3:4:6:8 的比例進行縮放。例如,一個啟動圖標的尺寸為48x48 dp,這表示在 MDPI 的屏幕上其實際尺寸應為 48x48 px,在 HDPI 的屏幕上其實際大小是 MDPI 的 1.5 倍 (72x72 px),在 XDPI 的屏幕上其實際大小是 MDPI 的 2 倍 (96x96 px),依此類推。

雖然 Android 也支持低像素密度 (LDPI) 的屏幕,但無需為此費神,系統會自動將 HDPI 尺寸的圖標縮小到 1/2 進行匹配。

下圖為圖標的各個屏幕密度的對應尺寸

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

解決方案

支持各種屏幕尺寸

使用wrap_content、match_parent、weight

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

使用 “wrap_content”,系統就會將視圖的寬度或高度設置成所需的最小尺寸以適應視圖中的內容,而 “match_parent”(在低於 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"/>
    
<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" />
\n");
        sb.append("");
        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("");

        StringBuffer sb2 = new StringBuffer();
        sb2.append("\n");
        sb2.append("");
        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("");

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

代碼應該很好懂,我們將一個屏幕寬度分為320份,高度480份,然後按照實際像素對每一個單位進行復制,放在對應values-widthxheight文件夾下面的lax.xml和lay.xml裡面,這樣就可以統一所有你想要的分辨率的單位了,下面是生成的一個320*480分辨率的文件,因為寬高分割之後總分數和像素數相同,所以x1就是1px,以此類推


<resources><dimen name="x1">1.0px
<resources><dimen name="x1">3.37px parent,
                            View view, int position, long id) {
        if (null != mHeadlineSelectedListener) {
            mHeadlineSelectedListener.onHeadlineSelected(position);
        }
    }
    ...
}

除此之外,我們還可以使用第三方框架,比如說使用“訂閱-發布”模式的EventBus來更多的優化組件之間的通信,減少耦合。

處理屏幕配置變化

如果我們使用獨立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了。

最佳實踐

關於高清設計圖尺寸

Google官方給出的高清設計圖尺寸有兩種方案,一種是以mdpi設計,然後對應放大得到更高分辨率的圖片,另外一種則是以高分辨率作為設計大小,然後按照倍數對應縮小到小分辨率的圖片。

根據經驗,我更推薦第二種方法,因為小分辨率在生成高分辨率圖片的時候,會出現像素丟失,我不知道是不是有方法可以阻止這種情況發生。

而分辨率可以以1280*720或者是1960*1080作為主要分辨率進行設計。

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系統的開放性,任何用戶、開發者、OEM廠商、運營商都可以對Android進行定制,修改成他們想要的樣子。

但是這種“碎片化”到底到達什麼程度呢?

在2012年,OpenSignalMaps(以下簡稱OSM)發布了第一份Android碎片化報告,統計數據表明,

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved