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)容中我們將介紹這些概念缕碎。

屏幕尺寸

屏幕尺寸指屏幕的對角線的長度咏雌,單位是英寸,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個像素點(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都會以px作為統(tǒng)一的計(jì)量單位,像是獲取屏幕寬高等逊朽。

dip和dp是一個意思曲伊,都是Density Independent Pixels的縮寫,即密度無關(guān)像素岛蚤,上面我們說過涤妒,dpi是屏幕像素密度赚哗,假如一英寸里面有160個像素铁坎,這個屏幕的像素密度就是160dpi硬萍,那么在這種情況下朴乖,dp和px如何換算呢助赞?在Android中,規(guī)定以160dpi為基準(zhǔn)畜普,1dip=1px吃挑,如果密度是320dpi街立,則1dip=2px,以此類推逛犹。

假如同樣都是畫一條320px的線虽画,在480800分辨率手機(jī)上顯示為2/3屏幕寬度荣病,在320480的手機(jī)上則占滿了全屏众雷,如果使用dp為單位,在這兩種分辨率下砾省,160dp都顯示為屏幕一半的長度编兄。這也是為什么在Android開發(fā)中,寫布局的時候要盡量使用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ā)的時候垮耳,我們需要把合適大小的圖片放在合適的文件夾里面遂黍。

解決方案

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

2.使用相對布局铃彰,禁用絕對布局

在開發(fā)中牙捉,我們大部分時候使用的都是線性布局敬飒、相對布局和幀布局,絕對布局由于適配性極差带到,所以極少使用揽惹。

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

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

4.dimen適配

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


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

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

  • 寬度為320挠说,將任何分辨率的寬度分為320份损俭,取值為x1-x320
  • 高度為480,將任何分辨率的高度分為480份雁仲,取值為y1-y480

例如對于800*480的寬度480:


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

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

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

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

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

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

6.Android AutoLayout

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

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

  1. Android多分辨率適配框架

將切圖放入drawable-nodpi中坛掠。

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

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

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

為什么要這么做呢扑庞?

  • 縮放比例的確定是基于屏幕的分辨率而確定的譬重。
    屏幕的分辨率均是采用px作為單位的,所以在布局時亦采用px從而保證縮放比例的一致和準(zhǔn)確
  • dp和sp均與設(shè)備的dpi有關(guān)罐氨。
    不同設(shè)備的dpi值不一樣臀规,所以在不同的設(shè)備上同一個dp和sp所對應(yīng)的px值是不盡相同的。如果采用dp和sp作為尺寸的單位栅隐,那么在縮放時會產(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

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

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

對于TextView,不但要縮放其尺寸鸯屿,還需要對其字體進(jìn)行縮放,除此以外,還要考慮到對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)容合作請聯(lián)系作者
  • 序言:七十年代末换可,一起剝皮案震驚了整個濱河市慨飘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖稽坤,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異杖玲,居然都是意外死亡今膊,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門恕刘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人含蓉,你說我怎么就攤上這事斟赚∞志” “怎么了发侵?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我掌腰,道長齿梁,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任省核,我火速辦了婚禮气忠,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘淘钟。我一直安慰自己袱瓮,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布精拟。 她就那樣靜靜地躺著,像睡著了一般师枣。 火紅的嫁衣襯著肌膚如雪践美。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機(jī)與錄音杂曲,去河邊找鬼。 笑死货抄,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的怪与。 我是一名探鬼主播分别,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼括授,長吁一口氣:“原來是場噩夢啊……” “哼薛夜!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起晚伙,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤民傻,失蹤者是張志新(化名)和其女友劉穎漓踢,沒想到半個月后漏隐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體挺据,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡婉称,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年俗壹,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片肛捍。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出悬襟,到底是詐尸還是另有隱情脊岳,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布莫瞬,位于F島的核電站,受9級特大地震影響檩小,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜瓦戚,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一印衔、第九天 我趴在偏房一處隱蔽的房頂上張望彤敛。 院中可真熱鬧,春花似錦袄秩、人聲如沸贮喧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽翘贮。三九已至扯再,卻和暖如春斋竞,著一層夾襖步出監(jiān)牢的瞬間复濒,已是汗流浹背袖扛。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工唇礁, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓常遂,卻偏偏與公主長得像圈匆,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

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