屏幕適配全方位解析

1.屏幕適配概念

而隨着支持Android系統的設備(手機、平板、電視、手錶)的增多,設備碎片化、品牌碎片化、系統碎片化、傳感器碎片化和屏幕碎片化的程度也在不斷地加深。而咱們今天要探討的,則是對咱們開發影響比較大的——屏幕的碎片化。android

下面這張圖是Android屏幕尺寸的示意圖,在這張圖裏面,藍色矩形的大小表明不一樣尺寸,顏色深淺則表明所佔百分比的大小。面試

下面是IOS的canvas

經過對比能夠很明顯知道adnroid的屏幕到底有多少種了吧。而蘋果只有5種包括如今最新的劉海屏ide

那麼想要對屏幕適配的相關處理方案有必定的本身的心得,那麼首先咱們須要瞭解關於android屏幕的必定基礎佈局

1.屏幕適配基礎

那麼下面是我給你們寫的一個屏幕適配基礎的思惟導圖,基本爲一個基礎篇的大綱,這裏我不會在課上很是詳細的給你們去過,就所有體如今簡書當中this

那麼屏幕適配相關概念上咱們須要掌握最基礎的3點
那麼相對基礎的內容是給段位比較低的同窗,高段位可選擇跳過spa

1.什麼是屏幕尺寸,屏幕分辨率,屏幕像素密度

屏幕尺寸指的是:.net

分辨率:翻譯

屏幕像素密度(DPI<Dots Per Inch>)
指每一英寸長度中,可顯示輸出的像素個數,
DPI的數字受屏幕尺寸和分辨率所影響,DPI能夠經過計算所得3d

上述內容在於掃盲..畢竟仍是有不清楚的同窗,而DPI跟下面內容結合比較密切因此囉嗦了兩句

2.什麼是dp,dip,sp,px?它們之間的關係?

px:構成圖像的最小單位
dip(重點):Desity Independent pixels的縮寫,即密度無關像素
android內部在識別圖像像素時以160dpi爲基準,1dip=1px或1dp=1px
例:在下列兩臺設備上使用DP進行操做
480 * 320 160dpi 那麼這臺機器上的1DP會被翻譯成1px
800 * 480 240dpi 而這臺機器上的1DP會被翻譯成1.5px
也就是說當前咱們設備的DP是由android給予的基礎標準按比例進行翻譯的,這也是爲何咱們用DP能解決一部分適配的緣由

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,以此類推,能夠繼續日後增長,不過通常狀況下已經夠用了,這種用來去適配手機和平板之間的圖形問題

2.屏幕適配基礎篇(常識,見思惟導圖,這裏只詳細講一下限定符)

2.1使用 "wrap_content" 和 "match_parent"
2.2相對佈局控制屏幕
2.3. .9圖的應用
上面三個都是最基本的android使用,咱們只須要在日常應用是注意到就好了,這裏不詳細去講

2.4.限定符

咱們在作屏幕的適配時在屏幕 尺寸相差不大的狀況下,dp可使不一樣分辨率的設備上展現效果類似。可是在屏幕尺寸相差比較大的狀況下(平板),dp就失去了這種效果。因此須要如下的限定符來約束,採用多套佈局,數值等方式來適配。

那麼其實所謂的限定符就是android在進行資源加載的時候會按照屏幕的相關信息對文件夾對應的名字進行識別,而這些特殊名字就是咱們的限定符

限定符分類:
    屏幕尺寸    
        small   小屏幕
        normal  基準屏幕
        large   大屏幕
        xlarge  超大屏幕
    屏幕密度
        ldpi    <=120dpi
        mdpi    <= 160dpi
        hdpi    <= 240dpi
        xhdpi   <= 320dpi
        xxhdpi  <= 480dpi
        xxhdpi  <= 640dpi(只用來存放icon)
        nodpi   與屏幕密度無關的資源.系統不會針對屏幕密度對其中資源進行壓縮或者拉伸
        tvdpi   介於mdpi與hdpi之間,特定針對213dpi,專門爲電視準備的,手機應用開發不須要關心這個密度值.
    屏幕方向    
        land    橫向
        port    縱向
    屏幕寬高比   
        long    比標準屏幕寬高比明顯的高或者寬的這樣屏幕
        notlong 和標準屏幕配置同樣的屏幕寬高比

2.4.1使用尺寸限定符:

當咱們要在大屏幕上顯示不一樣的佈局,就要使用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推出了最小寬度限定符。

2.4.2使用最小寬度限定符

最小寬度限定符的使用和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目錄下。

這就要求咱們維護兩個相同功能的文件。爲了不繁瑣操做,咱們就要使用佈局別名。

2.4.3使用佈局別名
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先後的全部平板設備。

2.4.4使用屏幕方向限定符
若是咱們要求給橫屏、豎屏顯示的佈局不同。就可使用屏幕方向限定符來實現。
例如,要在平板上實現橫豎屏顯示不用的佈局,能夠用如下方式實現。
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>

那麼上述是最基本的屏幕適配的解決方案
這裏找到一個神人給官方適配方案作的翻譯推給你們參考
https://blog.csdn.net/wzy_1988/article/details/52932875

3.屏幕適配解決方案:

基礎篇結束以後,咱們市場上最經常使用的解決方案我給你們總結了兩種
1.經過自定義佈局組件來完成

有聽過我公開課的同窗應該知道我當時寫了一套,其核心原理是根據一個參照分辨率進行佈局,而後再各個機器上提取當前機器分辨率換算出係數以後,而後再經過從新測量的方式來達到適配的效果,這一套方案基本能適用於95以上的機型,那麼今天到時候再加上劉海屏的適配就OK了。
下面是代碼,

/**
   * Created by barry on 2018/6/7.
   */
public class ScreenAdaptationRelaLayout extends RelativeLayout {
public ScreenAdaptationRelaLayout(Context context) {
    super(context);
}

public ScreenAdaptationRelaLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
}

public ScreenAdaptationRelaLayout(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
}

static boolean isFlag = true;

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    if(isFlag){
        int count = this.getChildCount();
        float scaleX =  UIUtils.getInstance(this.getContext()).getHorizontalScaleValue();
        float scaleY =  UIUtils.getInstance(this.getContext()).getVerticalScaleValue();

        Log.i("testbarry","x係數:"+scaleX);
        Log.i("testbarry","y係數:"+scaleY);
        for (int i = 0;i < count;i++){
            View child = this.getChildAt(i);
            //表明的是當前空間的全部屬性列表
            LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();
            layoutParams.width = (int) (layoutParams.width * scaleX);
            layoutParams.height = (int) (layoutParams.height * scaleY);
            layoutParams.rightMargin = (int) (layoutParams.rightMargin * scaleX);
            layoutParams.leftMargin = (int) (layoutParams.leftMargin * scaleX);
            layoutParams.topMargin = (int) (layoutParams.topMargin * scaleY);
            layoutParams.bottomMargin = (int) (layoutParams.bottomMargin * scaleY);
        }
        isFlag = false;
    }

    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
}
}
public class UIUtils {

private Context context;

private static UIUtils utils ;

public static UIUtils getInstance(Context context){
    if(utils == null){
        utils = new UIUtils(context);
    }
    return utils;
}

//參照寬高
public final float STANDARD_WIDTH = 720;
public final float STANDARD_HEIGHT = 1232;

//當前設備實際寬高
public float displayMetricsWidth ;
public float displayMetricsHeight ;

private  final String DIMEN_CLASS = "com.android.internal.R$dimen";

private UIUtils(Context context){
    this.context = context;
    //
    WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

    //加載當前界面信息
    DisplayMetrics displayMetrics = new DisplayMetrics();
    windowManager.getDefaultDisplay().getMetrics(displayMetrics);

    if(displayMetricsWidth == 0.0f || displayMetricsHeight == 0.0f){
        //獲取狀態框信息
        int systemBarHeight = getValue(context,"system_bar_height",48);

        if(displayMetrics.widthPixels > displayMetrics.heightPixels){
            this.displayMetricsWidth = displayMetrics.heightPixels;
            this.displayMetricsHeight = displayMetrics.widthPixels - systemBarHeight;
        }else{
            this.displayMetricsWidth = displayMetrics.widthPixels;
            this.displayMetricsHeight = displayMetrics.heightPixels - systemBarHeight;
        }

    }
}

//對外提供係數
public float getHorizontalScaleValue(){
    return displayMetricsWidth / STANDARD_WIDTH;
}

public float getVerticalScaleValue(){

    Log.i("testbarry","displayMetricsHeight:"+displayMetricsHeight);
    return displayMetricsHeight / STANDARD_HEIGHT;
}

public int getValue(Context context,String systemid,int defValue) {

    try {
        Class<?> clazz = Class.forName(DIMEN_CLASS);
        Object r = clazz.newInstance();
        Field field = clazz.getField(systemid);
        int x = (int) field.get(r);
        return context.getResources().getDimensionPixelOffset(x);

    } catch (Exception e) {
       return defValue;
    }
}

}

2.給各個分辨率單獨適配,res,dimens裏設置各個對應的px,再統一調用,由系統篩選。

這種方式比較久遠了,可是確實仍是有不少項目在使用到這種方式
其原理就是據設備屏幕的分辨率各自寫一套dimens.xml文件,而後根據一個基準分辨率(例如720x1080),將寬度分紅720份,取值爲1px——720px,將高度分紅1080份,取值爲1px——1080px。生成各自dimens.xml文件對應的值。
可是今天我根據這個方法,在這個方案的基礎之上給你們作了一次改變,運用以前所見的DP的概念,結合以前講的限定符,用DP來升級了這種方案,dp適配原理與px適配同樣,區別就在於px適配是根據屏幕分辨率,即拿px值等比例縮放,而dp適配是拿dp值來等比縮放而已。
既然原理都同樣,都須要多套dimens.xml文件,爲何說dp適配就比px適配好呢?
由於px適配是根據屏幕分辨率的,Android設備分辨率一大堆,並且還要考慮虛擬鍵盤。而dp適配不管手機屏幕的像素多少,密度比值多少,80%的手機的最小寬度dp值(widthPixels / density)都爲360dp,這樣就大大減小了dimens.xml文件
PS:(如今基本上手機的dpi都在350+以上 那麼按最低算 350/160=2.1 那麼360 2.1 = 720+ 基本上手機的分辨率都會在360dp以內 上面例子19201080的狀況 500/160=3.125 那麼 360*3.125=1125其實也在360以內)
那麼傳統作法:

改良後的作法:

獲取最小寬度獲取以下:

DisplayMetrics dm = new DisplayMetrics();

    getWindowManager().getDefaultDisplay().getMetrics(dm);

    int widthPixels = dm.widthPixels;

    float density = dm.density;

    float widthDP = widthPixels / density;

因此經過這種兩種形式的結合可以達到咱們總體適配任意機型的目的

做者:Barry
原文連接:https://www.jianshu.com/p/0586c7e7e212

閱讀更多

適配不一樣尺寸屏幕,自動拉伸位圖9.圖片的使用|處理

適配不一樣尺寸屏幕幾個關鍵點分享

屏幕適配之尺寸的相關概論《一》

2018 Android面試心得,已拿到offer

2018 Android面試心得,已拿到offer

相關文章
相關標籤/搜索