高級UI---LSN-9-1-android屏幕適配全方位解析

前言
前面八次課我們已經(jīng)將android的繪制基礎(chǔ)已經(jīng)講完,那么現(xiàn)在我們下面的兩個(gè)內(nèi)容點(diǎn)是事件分發(fā)問題,和屏幕適配相關(guān)今天這節(jié)課我們主要來進(jìn)android但中的各種屏幕適配問題

1.屏幕適配概念

而隨著支持Android系統(tǒng)的設(shè)備(手機(jī)、平板、電視、手表)的增多,設(shè)備碎片化、品牌碎片化、系統(tǒng)碎片化、傳感器碎片化和屏幕碎片化的程度也在不斷地加深。而我們今天要探討的,則是對我們開發(fā)影響比較大的——屏幕的碎片化。

下面這張圖是Android屏幕尺寸的示意圖,在這張圖里面,藍(lán)色矩形的大小代表不同尺寸,顏色深淺則代表所占百分比的大小。

image.png

下面是IOS的

image.png

通過對比可以很明顯知道adnroid的屏幕到底有多少種了吧。而蘋果只有5種包括現(xiàn)在最新的劉海屏

那么想要對屏幕適配的相關(guān)處理方案有一定的自己的心得,那么首先我們需要了解關(guān)于android屏幕的一定基礎(chǔ)

1.屏幕適配基礎(chǔ)

那么下面是我給大家寫的一個(gè)屏幕適配基礎(chǔ)的思維導(dǎo)圖,基本為一個(gè)基礎(chǔ)篇的大綱,這里我不會(huì)在課上非常詳細(xì)的給大家去過,就全部體現(xiàn)在簡書當(dāng)中

屏幕適配.png

那么屏幕適配相關(guān)概念上我們需要掌握最基礎(chǔ)的3點(diǎn)
那么相對基礎(chǔ)的內(nèi)容是給段位比較低的同學(xué),高段位可選擇跳過

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

屏幕尺寸指的是:


屏幕尺寸.png

分辨率:


image.png

屏幕像素密度(DPI<Dots Per Inch>)
指每一英寸長度中,可顯示輸出的像素個(gè)數(shù),
DPI的數(shù)字受屏幕尺寸和分辨率所影響,DPI可以通過計(jì)算所得

計(jì)算公式:sqrt(1080^2+1920^2)/屏幕尺寸

上述內(nèi)容在于掃盲..畢竟還是有不清楚的同學(xué),而DPI跟下面內(nèi)容結(jié)合比較密切所以啰嗦了兩句

2.什么是dp,dip,sp,px?它們之間的關(guān)系?

px:構(gòu)成圖像的最小單位
dip(重點(diǎn)):Desity Independent pixels的縮寫,即密度無關(guān)像素
android內(nèi)部在識(shí)別圖像像素時(shí)以160dpi為基準(zhǔn),1dip=1px或1dp=1px
例:在下列兩臺(tái)設(shè)備上使用DP進(jìn)行操作
480 * 320 160dpi 那么這臺(tái)機(jī)器上的1DP會(huì)被翻譯成1px
800 * 480 240dpi 而這臺(tái)機(jī)器上的1DP會(huì)被翻譯成1.5px
也就是說當(dāng)前我們設(shè)備的DP是由android給予的基礎(chǔ)標(biāo)準(zhǔn)按比例進(jìn)行翻譯的,這也是為什么我們用DP能解決一部分適配的原因

3.mdpi,hdpi,xdpi,xxdpi,xxxdpi?如何計(jì)算和區(qū)分?

  名稱                 像素密度范圍         圖片大小
  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官方開發(fā)文檔中,說明了 ** mdpi:hdpi:xhdpi:xxhdpi:xxxhdpi=2:3:4:6:8 ** 的尺寸比例進(jìn)行縮放。例如,一個(gè)圖標(biāo)的大小為48×48dp,表示在mdpi上,實(shí)際大小為48×48px,在hdpi像素密度上,實(shí)際尺寸為mdpi上的1.5倍,即72×72px,以此類推,可以繼續(xù)往后增加,不過一般情況下已經(jīng)夠用了,這種用來去適配手機(jī)和平板之間的圖形問題

2.屏幕適配基礎(chǔ)篇(常識(shí),見思維導(dǎo)圖,這里只詳細(xì)講一下限定符)

2.1使用 "wrap_content" 和 "match_parent"
2.2相對布局控制屏幕
2.3. .9圖的應(yīng)用
上面三個(gè)都是最基本的android使用,我們只需要在平常應(yīng)用是注意到就行了,這里不詳細(xì)去講

2.4.限定符

我們在做屏幕的適配時(shí)在屏幕 尺寸相差不大的情況下,dp可以使不同分辨率的設(shè)備上展示效果相似。但是在屏幕尺寸相差比較大的情況下(平板),dp就失去了這種效果。所以需要以下的限定符來約束,采用多套布局,數(shù)值等方式來適配。

那么其實(shí)所謂的限定符就是android在進(jìn)行資源加載的時(shí)候會(huì)按照屏幕的相關(guān)信息對文件夾對應(yīng)的名字進(jìn)行識(shí)別,而這些特殊名字就是我們的限定符

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

2.4.1使用尺寸限定符:

當(dāng)我們要在大屏幕上顯示不同的布局,就要使用large限定符。例如,在寬的屏幕左邊顯示列表右邊顯示列表項(xiàng)的詳細(xì)信息,在一般寬度的屏幕只顯示列表,不顯示列表項(xiàng)的詳細(xì)信息,我們就可以使用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"/>
<!-- 列表項(xiàng)的詳細(xì)信息 -->
<fragment android:id="@+id/article"
          android:layout_height="fill_parent"
          android:name="com.example.android.newsreader.ArticleFragment"
          android:layout_width="fill_parent" />
</LinearLayout>

如果這個(gè)程序運(yùn)行在屏幕尺寸大于7inch的設(shè)備上,系統(tǒng)就會(huì)加載res/layout-large/main.xml 而不是res/layout/main.xml,在小于7inch的設(shè)備上就會(huì)加載res/layout/main.xml。

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

2.4.2使用最小寬度限定符

最小寬度限定符的使用和large基本一致,只是使用了具體的寬度限定。
res/layout/main.xml,單面板(默認(rèn))布局:

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

這種方式是不區(qū)分屏幕方向的。這種最小寬度限定符適用于android3.2之后,所以如果要適配android全部的版本,就要使用large限定符和sw600dp文件同時(shí)存在于項(xiàng)目res目錄下。

這就要求我們維護(hù)兩個(gè)相同功能的文件。為了避免繁瑣操作,我們就要使用布局別名。

2.4.3使用布局別名
res/layout/main.xml: 單面板布局
res/layout-large/main.xml: 多面板布局
res/layout-sw600dp/main.xml: 多面板布局
由于后兩個(gè)文具文件一樣,我們可以用以下兩個(gè)文件代替上面三個(gè)布局文件:

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

然后在res下建立
res/values/layout.xml、
res/values-large/layout.xml、
res/values-sw600dp/layout.xml三個(gè)文件。

默認(rèn)布局
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);

這樣,程序在運(yùn)行時(shí),就會(huì)檢測手機(jī)的屏幕大小,如果是平板設(shè)備就會(huì)加載res/layout/main_twopanes.xml,如果是手機(jī)設(shè)備,就會(huì)加載res/layout/main.xml 。我們就解決了只使用一個(gè)布局文件來適配android3.2前后的所有平板設(shè)備。

2.4.4使用屏幕方向限定符
如果我們要求給橫屏、豎屏顯示的布局不一樣。就可以使用屏幕方向限定符來實(shí)現(xiàn)。
例如,要在平板上實(shí)現(xiàn)橫豎屏顯示不用的布局,可以用以下方式實(shí)現(xiàn)。
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>

那么上述是最基本的屏幕適配的解決方案
這里找到一個(gè)神人給官方適配方案做的翻譯推給大家參考
https://blog.csdn.net/wzy_1988/article/details/52932875

3.屏幕適配解決方案:

基礎(chǔ)篇結(jié)束之后,我們市場上最常用的解決方案我給大家總結(jié)了兩種
1.通過自定義布局組件來完成


有聽過我公開課的同學(xué)應(yīng)該知道我當(dāng)時(shí)寫了一套,其核心原理是根據(jù)一個(gè)參照分辨率進(jìn)行布局,然后再各個(gè)機(jī)器上提取當(dāng)前機(jī)器分辨率換算出系數(shù)之后,然后再通過重新測量的方式來達(dá)到適配的效果,這一套方案基本能適用于95以上的機(jī)型,那么今天到時(shí)候再加上劉海屏的適配就OK了。
下面是代碼,
公開課地址:鏈接:https://pan.baidu.com/s/1xaRi6574Tq98wDvr19AOvA 密碼:zcn5

  /**
   * 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系數(shù):"+scaleX);
        Log.i("testbarry","y系數(shù):"+scaleY);
        for (int i = 0;i < count;i++){
            View child = this.getChildAt(i);
            //代表的是當(dāng)前空間的所有屬性列表
            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;

//當(dāng)前設(shè)備實(shí)際寬高
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);

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

    if(displayMetricsWidth == 0.0f || displayMetricsHeight == 0.0f){
        //獲取狀態(tài)框信息
        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;
        }

    }
}

//對外提供系數(shù)
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.給各個(gè)分辨率單獨(dú)適配,res,dimens里設(shè)置各個(gè)對應(yīng)的px,再統(tǒng)一調(diào)用,由系統(tǒng)篩選。

這種方式比較久遠(yuǎn)了,但是確實(shí)還是有很多項(xiàng)目在使用到這種方式
其原理就是據(jù)設(shè)備屏幕的分辨率各自寫一套dimens.xml文件,然后根據(jù)一個(gè)基準(zhǔn)分辨率(例如720x1080),將寬度分成720份,取值為1px——720px,將高度分成1080份,取值為1px——1080px。生成各自dimens.xml文件對應(yīng)的值。
但是今天我根據(jù)這個(gè)方法,在這個(gè)方案的基礎(chǔ)之上給大家做了一次改變,運(yùn)用之前所見的DP的概念,結(jié)合之前講的限定符,用DP來升級了這種方案,dp適配原理與px適配一樣,區(qū)別就在于px適配是根據(jù)屏幕分辨率,即拿px值等比例縮放,而dp適配是拿dp值來等比縮放而已。
既然原理都一樣,都需要多套dimens.xml文件,為什么說dp適配就比px適配好呢?
因?yàn)閜x適配是根據(jù)屏幕分辨率的,Android設(shè)備分辨率一大堆,而且還要考慮虛擬鍵盤。而dp適配無論手機(jī)屏幕的像素多少,密度比值多少,80%的手機(jī)的最小寬度dp值(widthPixels / density)都為360dp,這樣就大大減少了dimens.xml文件
PS:(現(xiàn)在基本上手機(jī)的dpi都在350+以上 那么按最低算 350/160=2.1 那么360 * 2.1 = 720+ 基本上手機(jī)的分辨率都會(huì)在360dp之內(nèi) 上面例子19201080的情況 500/160=3.125 那么 3603.125=1125其實(shí)也在360之內(nèi))
那么傳統(tǒng)做法:

image.png

改良后的做法:


image.png

獲取最小寬度獲取如下:

    DisplayMetrics dm = new DisplayMetrics();

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

    int widthPixels = dm.widthPixels;

    float density = dm.density;

    float widthDP = widthPixels / density;

所以通過這種兩種形式的結(jié)合能夠達(dá)到我們整體適配任意機(jī)型的目的

著作:Kerwin Barry
郵箱:kerwin0210@sina.com
原創(chuàng)博客,轉(zhuǎn)載請注明出處.....

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,646評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,595評論 3 418
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,560評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,035評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,814評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,224評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,301評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,444評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,988評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,804評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,998評論 1 370
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,544評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,237評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,665評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,927評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,706評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,993評論 2 374

推薦閱讀更多精彩內(nèi)容