Android屏幕適配方案

Android 1 月份 設(shè)備分辨率占比

(圖片來源友盟統(tǒng)計(jì))

為什么要適配?

碎片化

  • 品牌機(jī)型碎片化
  • 屏幕尺寸碎片化
  • 操作系統(tǒng)碎片化

為了保證用戶獲得一致的用戶體驗(yàn)效果,使得某一元素在Android不同尺寸、不同分辨率的手機(jī)上具備相同的顯示效果,則需要我們進(jìn)行屏幕適配。

重要概念

  • 什么是屏幕尺寸、屏幕分辨率、屏幕像素密度?
  • 什么是dp、dip、dpi、sp、px?他們之間的關(guān)系是什么?
  • 什么是mdpi、hdpi、xdpi、xxdpi、xxxdpi?如何計(jì)算和區(qū)分?

在下面的內(nèi)容中我們將介紹這些概念。

屏幕尺寸

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

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

屏幕分辨率

屏幕分辨率是指在橫縱向上的像素點(diǎn)數(shù),單位是px,1px=1個(gè)像素點(diǎn)。一般以縱向像素橫向像素,如19601080。

屏幕像素密度

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

dp、dip、dpi、sp、px

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

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

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

而sp,即scale-independent pixels,與dp類似,但是可以根據(jù)文字大小首選項(xiàng)進(jìn)行放縮,是設(shè)置字體大小的御用單位。

各單位間的轉(zhuǎn)換器

mdpi、hdpi、xdpi、xxdpi、xxxdpi

直接看下圖吧:


按照Google官方指定標(biāo)準(zhǔn)進(jìn)行區(qū)分

在進(jìn)行開發(fā)的時(shí)候,我們需要把合適大小的圖片放在合適的文件夾里面。

解決方案

1.在布局文件中使用wrap_content, match_parent, layout_weight屬性,并在指定大小的時(shí)候使用dp。

2.使用相對(duì)布局,禁用絕對(duì)布局

在開發(fā)中,我們大部分時(shí)候使用的都是線性布局、相對(duì)布局和幀布局,絕對(duì)布局由于適配性極差,所以極少使用。

3.圖片可以提供多套,及不同分辨率的圖片,但是這種會(huì)使apk體積增大,還有就是可以使用.9圖來適配一些情況。

3.限定符,主要是給不同的分辨率設(shè)置不同的布局文件

4.dimen適配

將屏幕寬度用一個(gè)固定的值的單位來統(tǒng)計(jì),即將屏幕縱橫方向上分成若干份,在布局中直接寫控件的像素。這樣的話需要將要適配的手機(jī)屏幕的分辨率各自建立一個(gè)文件夾 :


然后我們根據(jù)一個(gè)基準(zhǔn),為基準(zhǔn)的意思就是:

比如480320的分辨率為基準(zhǔn)*

  • 寬度為320,將任何分辨率的寬度分為320份,取值為x1-x320
  • 高度為480,將任何分辨率的高度分為480份,取值為y1-y480

例如對(duì)于800*480的寬度480:


可以看到x1 = 480 / 基準(zhǔn) = 480 / 320 = 1.5 ;
其他分辨率類似~~
可以使用工具生成:autolayout.jar

不過這種方案也是有局限性:

在生成的values文件夾里,如果沒有對(duì)應(yīng)的分辨率,開始是報(bào)錯(cuò)的,因?yàn)槟J(rèn)的values沒有對(duì)應(yīng)dimen,所以只能在默認(rèn)values里面也創(chuàng)建對(duì)應(yīng)文件,但是里面的數(shù)據(jù)卻不好處理,因?yàn)椴恢婪直媛剩抑缓媚J(rèn)為x1=1dp保證盡量兼容。這也是這個(gè)解決方案的幾個(gè)弊端,對(duì)于沒有生成對(duì)應(yīng)分辨率文件的手機(jī),會(huì)使用默認(rèn)values文件夾,如果默認(rèn)文件夾沒有,就會(huì)出現(xiàn)問題。

所以說,這個(gè)方案雖然是一勞永逸,但是由于實(shí)際上還是使用的px作為長度的度量單位,所以多少和google的要求有所背離,不好說以后會(huì)不會(huì)出現(xiàn)什么不可預(yù)測(cè)的問題。其次,如果要使用這個(gè)方案,你必須盡可能多的包含所有的分辨率,因?yàn)檫@個(gè)是使用這個(gè)方案的基礎(chǔ),如果有分辨率缺少,會(huì)造成顯示效果很差,甚至出錯(cuò)的風(fēng)險(xiǎn),而這又勢(shì)必會(huì)增加軟件包的大小和維護(hù)的難度,所以大家自己斟酌,擇優(yōu)使用。

具體使用可以參考:Android 屏幕適配方案

5.Android-percent-support百分比支持庫
具體使用可參考: Android 百分比布局庫(percent-support-lib) 解析與擴(kuò)展

6.Android AutoLayout

Android屏幕適配方案,直接填寫設(shè)計(jì)圖上的像素尺寸即可完成適配,最大限度解決適配問題。

不過看了Issues這種方案并不能解決所有的問題

  1. Android多分辨率適配框架

將切圖放入drawable-nodpi中。

該文件夾中的圖片不會(huì)被縮放,在不同分辨率的手機(jī)上都只顯示原圖的大小。如此以來,摒棄了系統(tǒng)對(duì)于圖片的縮放,為我們以后自己處理圖片的縮放做好了鋪墊。

  • 計(jì)算出縮放比。
  • 依據(jù)不同的分辨率計(jì)算出縮放比。

在一個(gè)高分辨率(如:1920*1080)手機(jī)上完成布局。
在布局的過程中,請(qǐng)注意一個(gè)問題:不再使用dp、sp作為大小單位,而是統(tǒng)一使用px。

為什么要這么做呢?

  • 縮放比例的確定是基于屏幕的分辨率而確定的。
    屏幕的分辨率均是采用px作為單位的,所以在布局時(shí)亦采用px從而保證縮放比例的一致和準(zhǔn)確
  • dp和sp均與設(shè)備的dpi有關(guān)。
    不同設(shè)備的dpi值不一樣,所以在不同的設(shè)備上同一個(gè)dp和sp所對(duì)應(yīng)的px值是不盡相同的。如果采用dp和sp作為尺寸的單位,那么在縮放時(shí)會(huì)產(chǎn)生較大的偏差

代碼實(shí)現(xiàn)

1.計(jì)算縮放比

int widthPixels = displayMetrics.widthPixels; 
scale = (float)widthPixels / 
BASE_SCREEN_WIDTH_FLOAT;

通過設(shè)備的寬與BASE_SCREEN_WIDTH_FLOAT的比值計(jì)算出縮放比。

2.等比例縮放UI

利用該方法對(duì)布局中的每個(gè)View進(jìn)行縮放操作。
在該方法中對(duì)每個(gè)View的寬高,padding,margin值都按比例縮放,并且在縮放后重新設(shè)置其布局參數(shù)。

3.關(guān)于TextView的特殊處理

對(duì)于TextView,不但要縮放其尺寸,還需要對(duì)其字體進(jìn)行縮放,除此以外,還要考慮到對(duì)TextView的CompoundDrawable進(jìn)行縮放。

具體的代碼如下:

public class SupportMultipleScreensUtil {

    public static final int BASE_SCREEN_WIDTH = 1080;
    public static final int BASE_SCREEN_HEIGHT = 1920;
    public static final float BASE_SCREEN_WIDTH_FLOAT = 1080F;
    public static final float BASE_SCREEN_HEIGHT_FLOAT = 1920F;
    public static float scale = 1.0F;

    public SupportMultipleScreensUtil() {

    }

    public static void init(Context context) {
        Resources resources=context.getResources();
        DisplayMetrics displayMetrics = resources.getDisplayMetrics();
        int widthPixels = displayMetrics.widthPixels;
        scale = (float)widthPixels / BASE_SCREEN_WIDTH_FLOAT;
    }


    public static void scale(View view) {
        if(null != view) {
            if(view instanceof ViewGroup) {
                scaleViewGroup((ViewGroup)view);
            } else {
                scaleView(view);
            }
        }
    }

    private static void scaleView(View view) {
        Object isScale = view.getTag(R.id.is_scale_size_tag);
        if (!(isScale instanceof Boolean) || !((Boolean) isScale).booleanValue()) {
            if (view instanceof TextView) {
                scaleTextView((TextView) view);
            } else {
                scaleViewSize(view);
            }
            view.setTag(R.id.is_scale_size_tag, Boolean.valueOf(true));
        }
    }



    private static void scaleViewGroup(ViewGroup viewGroup) {
        for (int i = 0; i < viewGroup.getChildCount(); ++i) {
            View view = viewGroup.getChildAt(i);
            if (view instanceof ViewGroup) {
                scaleViewGroup((ViewGroup) view);
            }
            scaleView(view);
        }
    }


    /**
     * 博客地址:
     * http://blog.csdn.net/lfdfhl
     */
    public static void scaleViewSize(View view) {
        if (null != view) {
            int paddingLeft = getScaleValue(view.getPaddingLeft());
            int paddingTop = getScaleValue(view.getPaddingTop());
            int paddingRight = getScaleValue(view.getPaddingRight());
            int paddingBottom = getScaleValue(view.getPaddingBottom());
            view.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);

            LayoutParams layoutParams = view.getLayoutParams();

            if (null != layoutParams) {

                if (layoutParams.width > 0) {
                    layoutParams.width = getScaleValue(layoutParams.width);
                }

                if (layoutParams.height > 0) {
                    layoutParams.height = getScaleValue(layoutParams.height);
                }

                if (layoutParams instanceof MarginLayoutParams) {
                    MarginLayoutParams marginLayoutParams = (MarginLayoutParams) layoutParams;
                    int topMargin = getScaleValue(marginLayoutParams.topMargin);
                    int leftMargin = getScaleValue(marginLayoutParams.leftMargin);
                    int bottomMargin = getScaleValue(marginLayoutParams.bottomMargin);
                    int rightMargin = getScaleValue(marginLayoutParams.rightMargin);
                    marginLayoutParams.topMargin = topMargin;
                    marginLayoutParams.leftMargin = leftMargin;
                    marginLayoutParams.bottomMargin = bottomMargin;
                    marginLayoutParams.rightMargin = rightMargin;
                }
            }
            view.setLayoutParams(layoutParams);
        }
    }

    private static void setTextViewCompoundDrawables(TextView textView, Drawable leftDrawable, Drawable topDrawable, Drawable rightDrawable, Drawable bottomDrawable) {
        if(null != leftDrawable) {
            scaleDrawableBounds(leftDrawable);
        }

        if(null != rightDrawable) {
            scaleDrawableBounds(rightDrawable);
        }

        if(null != topDrawable) {
            scaleDrawableBounds(topDrawable);
        }

        if(null != bottomDrawable) {
            scaleDrawableBounds(bottomDrawable);
        }
        textView.setCompoundDrawables(
        leftDrawable,topDrawable, rightDrawable,bottomDrawable);
    }

    public static Drawable scaleDrawableBounds(Drawable drawable) {
    int right=getScaleValue(drawable.getIntrinsicWidth());
    int bottom=getScaleValue(drawable.getIntrinsicHeight());
    drawable.setBounds(0, 0, right, bottom);
    return drawable;
    }

    public static void scaleTextView(TextView textView) {
        if (null != textView) {

            scaleViewSize(textView);

            Object isScale = textView.getTag(R.id.is_scale_font_tag);
            if (!(isScale instanceof Boolean) || !((Boolean) isScale).booleanValue()) {
                float size = textView.getTextSize();
                size *= scale;
            textView.setTextSize(
            TypedValue.COMPLEX_UNIT_PX, size);
            }

            Drawable[] drawables = textView.getCompoundDrawables();
            Drawable leftDrawable = drawables[0];
            Drawable topDrawable = drawables[1];
            Drawable rightDrawable = drawables[2];
            Drawable bottomDrawable = drawables[3];              
            setTextViewCompoundDrawables(
            textView, leftDrawable, topDrawable, 
            rightDrawable, bottomDrawable);
            int compoundDrawablePadding = getScaleValue(textView.getCompoundDrawablePadding());
        
            textView.setCompoundDrawablePadding(
            compoundDrawablePadding);
        }
    }

    public static int getScaleValue(int value) {
        return value <= 4?value:(int) Math.ceil((double)(scale * (float)value));
    }

 }

具體可參考: Android多分辨率適配框架

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容