Android 屏幕適配:最全面的解決方案

轉自:https://www.jianshu.com/p/ec5a1a30694bjavascript

 

前言

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

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


目錄

 
Android屏幕適配解決方案.png

定義

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


相關重要概念

屏幕尺寸

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

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

屏幕分辨率

  • 含義:手機在橫向、縱向上的像素點數總和
  1. 通常描述成屏幕的"寬x高」=AxB
  2. 含義:屏幕在橫向方向(寬度)上有A個像素點,在縱向方向
    (高)有B個像素點
  3. 例子:1080x1920,即寬度方向上有1080個像素點,在高度方向上有1920個像素點
  • 單位:px(pixel),1px=1像素點

UI設計師的設計圖會以px做爲統一的計量單位git

  • Android手機常見的分辨率:320x480、480x800、720x1280、1080x1920

屏幕像素密度

  • 含義:每英寸的像素點數
  • 單位:dpi(dots per ich)

假設設備內每英寸有160個像素,那麼該設備的屏幕像素密度=160dpigithub

  • 安卓手機對於每類手機屏幕大小都有一個相應的屏幕像素密度:
密度類型 表明的分辨率(px) 屏幕像素密度(dpi)
低密度(ldpi) 240x320 120
中密度(mdpi) 320x480 160
高密度(hdpi) 480x800 240
超高密度(xhdpi) 720x1280 320
超超高密度(xxhdpi) 1080x1920 480

屏幕尺寸、分辨率、像素密度三者關係

一部手機的分辨率是寬x高,屏幕大小是以寸爲單位,那麼三者的關係是:bash

 
三者關係示意圖

數學不太差的人應該能懂.....吧?ide

不懂不要緊,在這裏舉個例子
假設一部手機的分辨率是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> 
  1. 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 735,1152_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適配方法的全面整理,接下來我會介紹繼續介紹Android開發中的相關知識,有興趣能夠繼續關注Carson_Ho的安卓開發筆記

做者:Carson_Ho 連接:https://www.jianshu.com/p/ec5a1a30694b 來源:簡書 簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。
相關文章
相關標籤/搜索