Android屏幕適配

前言

在Android開發(fā)中拗踢,由于Android碎片化嚴(yán)重疮胖,屏幕分辨率千奇百怪,而想要在各種分辨率的設(shè)備上顯示基本一致的效果傲宜,適配成本越來越高。雖然Android官方提供了dp單位來適配夫啊,但其在各種奇怪分辨率下表現(xiàn)卻不盡如人意函卒。

想要完美解決屏幕適配問題。首先撇眯,必須要了解的幾個概念报嵌。

屏幕尺寸、屏幕分辨率叛本、屏幕像素密度

屏幕尺寸:屏幕對角線長度沪蓬,單位是英寸,我們常說的多少多少寸来候,比如4.7存手機(jī)跷叉、5.7存手機(jī),指的就是這個营搅。
屏幕分辨率:如 1920×1080云挟,是指在手機(jī)屏幕的像素點(diǎn)的個數(shù),單位是px转质,1px = 1 像素點(diǎn)园欣,一般是縱向像素 × 橫向像素,意味著高有 1920 個像素點(diǎn)休蟹,寬有 1080 個像素點(diǎn)沸枯。
屏幕像素密度:是指每英寸上的像素點(diǎn)數(shù),單位是 dpi(dotper inch)赂弓。像素密度和屏幕尺寸和屏幕分辨率有關(guān)绑榴,它是由對角線的像素點(diǎn)數(shù)除以屏幕的大小得到的,關(guān)系如下:


屏幕像素密度算法

dp盈魁、dip翔怎、dpi、sp杨耙、px

dp:是Android 特有的赤套,意為密度無關(guān)像素,Google 發(fā)布的 BASELINE(基準(zhǔn)線)為 160珊膜,以此為基準(zhǔn)容握。
dip:Density Independent Pixels,同dp一個意思车柠,目前廢棄了唯沮,一般都寫dp脖旱。
dpi:即為屏幕像素密度的單位
sp:Scale-IndependentPixels的縮寫堪遂,可以根據(jù)文字大小首選項(xiàng)自動進(jìn)行縮放介蛉。Google推薦我們使用12sp以上的大小,通橙芡剩可以使用12sp币旧,14sp,18sp猿妈,22sp吹菱,為避免精度損失,建議最好不要使用奇數(shù)和小數(shù)彭则。
px:就是我們常說的像素

mdpi鳍刷、hdpi、xhdpi俯抖、xxhdpi输瓜、xxxhdpi

像素密度范圍對應(yīng)表
Google各種尺寸對應(yīng)密度表

屏幕適配

屏幕分辨率限定符(不推薦)

屏幕分辨率限定符適配需要在 res 文件夾下創(chuàng)建各種屏幕分辨率對應(yīng)的 values-xxx 文件夾,如下圖:


屏幕分辨率限定符

然后根據(jù)一個基準(zhǔn)分辨率芬萍,例如基準(zhǔn)分辨率為 1280x720尤揣,將寬度分成 720 份,取值為 1px~720px柬祠,將高度分成 1280 份北戏,取值為 1px~1280px,生成各種分辨率對應(yīng)的 dimens.xml 文件漫蛔。如下分別為分辨率 1280x720 與 1920x1080 所對應(yīng)的橫向dimens.xml 文件:

image

假設(shè)設(shè)計(jì)圖上的一個控件的寬度為 720px嗜愈,那么布局中就寫 android:layout_width="@dimen/x720" ,當(dāng)運(yùn)行程序的時候莽龟,系統(tǒng)會根據(jù)設(shè)備的分辨率去尋找對應(yīng)的 dimens.xml 文件蠕嫁。例如運(yùn)行在分辨率為 1280x720 的設(shè)備上,系統(tǒng)會自動找到對應(yīng)的 values-1280x720 文件夾下的 lay_x.xml 文件轧房,由上圖可知 x720 對應(yīng)的值為
720.px拌阴,可鋪滿該屏幕寬度。運(yùn)行在分辨率為 1920x1080 的設(shè)備上奶镶,系統(tǒng)會自動找到對應(yīng)的 values-1920x1080 文件夾下的 lay_x.xml 文件迟赃,由上圖可知 x720 對應(yīng)的值為 1080.0px,可鋪滿該屏幕寬度厂镇。這樣就達(dá)到了屏幕適配的要求纤壁!

smallestWidth 限定符

smallestWidth 限定符適配原理與屏幕分辨率限定符適配原理一樣,系統(tǒng)都是根據(jù)限定符去尋找對應(yīng)的 dimens.xml 文件捺信。例如程序運(yùn)行在最小寬度為 360dp 的設(shè)備上酌媒,系統(tǒng)會自動找到對應(yīng)的 values-sw360dp 文件夾下的 dimens.xml 文件欠痴。區(qū)別就在于屏幕分辨率限定符適配是拿 px 值等比例縮放,而 smallestWidth 限定符適配是拿 dp 值來等比縮放而已秒咨。需要注意的是“最小寬度”是不區(qū)分方向的喇辽,即無論是寬度還是高度,哪一邊小就認(rèn)為哪一邊是“最小寬度”雨席。如下分別為最小寬度為 360dp 與最小寬度為 640dp 所對應(yīng)的 dimens.xml 文件:


image

獲取設(shè)備最小寬度代碼為:

DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
int heightPixels = ScreenUtils.getScreenHeight(this);
int widthPixels = ScreenUtils.getScreenWidth(this);
float density = dm.density;
float heightDP = heightPixels / density;
float widthDP = widthPixels / density;
float smallestWidthDP;
if(widthDP < heightDP) {
    smallestWidthDP = widthDP;
}else {
    smallestWidthDP = heightDP;
}
為什么選擇 smallestWidth 限定符適配菩咨?

既然原理都一樣,都需要多套 dimens.xml 文件陡厘,那為什么要選擇 smallestWidth 限定符適配呢抽米?

屏幕分辨率限定符適配是根據(jù)屏幕分辨率的,Android 設(shè)備分辨率一大堆糙置,而且還要考慮虛擬鍵盤云茸,這樣就需要大量的 dimens.xml 文件。因?yàn)闊o論手機(jī)屏幕的像素多少谤饭,密度多少标捺,90% 的手機(jī)的最小寬度都為 360dp,所以采用 smallestWidth 限定符適配只需要少量 dimens.xml 文件即可网持。
屏幕分辨率限定符適配采用的是 px 單位宜岛,而 smallestWidth 限定符適配采用的單位是 dp 和 sp,dp 和 sp 是google 推薦使用的計(jì)量單位功舀。又由于很多應(yīng)用要求字體大小隨系統(tǒng)改變萍倡,所以字體單位使用 sp 也更靈活。
屏幕分辨率限定符適配需要設(shè)備分辨率與 values-xx 文件夾完全匹配才能達(dá)到適配辟汰,而 smallestWidth 限定符適配尋找 dimens.xml 文件的原理是從大往小找列敲,例如設(shè)備的最小寬度為 360dp,就會先去找 values-360dp帖汞,發(fā)現(xiàn)沒有則會向下找 values-320dp戴而,如果還是沒有才找默認(rèn)的 values 下的 demens.xml 文件,所以即使沒有完全匹配也能達(dá)到不錯的適配效果翩蘸。

自定義布局組件適配

##ScreenAdapterLayout 
public class ScreenAdapterLayout extends RelativeLayout {
    private boolean flag;

    public ScreenAdapterLayout(Context context) {
        super(context);
    }

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

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

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (!flag) { //防止兩次測量
            flag = true;
            //獲取橫豎方向等比
            float scaleX = Utils.getInstance(getContext()).getHorizontalScale();
            float scaleY = Utils.getInstance(getContext()).getVerticalScale();
            //子View個數(shù)
            int childCount = getChildCount();
            for (int i = 0; i < childCount; i++) {
                View child = getChildAt(i);
                LayoutParams params = (LayoutParams) child.getLayoutParams();
                params.width = (int) (params.width * scaleX);
                params.height = (int) (params.height * scaleY);
                params.leftMargin = (int) (params.leftMargin * scaleX);
                params.rightMargin = (int) (params.rightMargin * scaleX);
                params.topMargin = (int) (params.topMargin * scaleY);
                params.bottomMargin = (int) (params.bottomMargin * scaleY);
                child.setPadding((int) (child.getPaddingLeft() * scaleX), (int) (child.getPaddingTop() * scaleY),
                        (int) (child.getPaddingRight() * scaleX), (int) (child.getPaddingBottom() * scaleY));
            }
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}

##Utils 
public class Utils {
    private static Utils utils;
    private Context mContext;
    //這里是設(shè)計(jì)稿參考寬高
    private static final float STANDARD_WIDTH = 1080;
    private static final float STANDARD_HEIGHT = 1920;
    //這里是屏幕顯示寬高
    private int mDisplayWidth;
    private int mDisplayHeight;

    public Utils(Context context) {
        mContext = context;
        //獲取屏幕寬高
        if (mDisplayWidth == 0 || mDisplayHeight == 0) {
            WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
            if (windowManager != null) {
                //寬高獲取
                DisplayMetrics displayMetrics = new DisplayMetrics();
                //如果不是NavigationBar沉浸式(不包含NavigationBar)
                windowManager.getDefaultDisplay().getMetrics(displayMetrics);
//                windowManager.getDefaultDisplay().getRealMetrics(displayMetrics);//真實(shí)屏幕寬高
                //判斷當(dāng)前的橫豎屏
                if (displayMetrics.widthPixels > displayMetrics.heightPixels) {
                    //橫屏
                    mDisplayWidth = displayMetrics.heightPixels;
                    mDisplayHeight = displayMetrics.widthPixels - getStatusBarHeight(context);
                } else {
                    //豎屏
                    mDisplayWidth = displayMetrics.widthPixels;
                    mDisplayHeight = displayMetrics.heightPixels - getStatusBarHeight(context);
                }
            }
        }
    }

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

    //獲取狀態(tài)欄高度
    public int getStatusBarHeight(Context context) {
        int resId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
        if (resId > 0) {
            return context.getResources().getDimensionPixelSize(resId);//獲取具體的像素值
        }
        return 0;
    }

    //獲取水平方向的縮放比例
    public float getHorizontalScale() {
        return mDisplayWidth / STANDARD_WIDTH;
    }

    //獲取垂直方向的縮放比例
    public float getVerticalScale() {
        return mDisplayHeight / STANDARD_HEIGHT-getStatusBarHeight(mContext);
    }
}

##activity_pixel.xml
<?xml version="1.0" encoding="utf-8"?>
<com.dn_alan.myapplication.pixel.ScreenAdapterLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/textView"
        android:layout_width="540px"
        android:layout_height="960px"
        android:background="@android:color/holo_red_dark"
        android:paddingLeft="20px"
        android:gravity="center"
        android:text="動腦學(xué)院"
        android:onClick="pixel"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</com.dn_alan.myapplication.pixel.ScreenAdapterLayout>

作者:Alan
原創(chuàng)博客所意,請注明轉(zhuǎn)載處....

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市催首,隨后出現(xiàn)的幾起案子扶踊,更是在濱河造成了極大的恐慌,老刑警劉巖郎任,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件秧耗,死亡現(xiàn)場離奇詭異,居然都是意外死亡舶治,警方通過查閱死者的電腦和手機(jī)分井,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門车猬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人尺锚,你說我怎么就攤上這事珠闰。” “怎么了缩麸?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵铸磅,是天一觀的道長。 經(jīng)常有香客問我杭朱,道長,這世上最難降的妖魔是什么吹散? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任弧械,我火速辦了婚禮,結(jié)果婚禮上空民,老公的妹妹穿的比我還像新娘刃唐。我一直安慰自己,他們只是感情好界轩,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布画饥。 她就那樣靜靜地躺著,像睡著了一般浊猾。 火紅的嫁衣襯著肌膚如雪抖甘。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天葫慎,我揣著相機(jī)與錄音衔彻,去河邊找鬼。 笑死偷办,一個胖子當(dāng)著我的面吹牛艰额,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播椒涯,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼柄沮,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了废岂?” 一聲冷哼從身側(cè)響起祖搓,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎泪喊,沒想到半個月后棕硫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡袒啼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年哈扮,在試婚紗的時候發(fā)現(xiàn)自己被綠了纬纪。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡滑肉,死狀恐怖包各,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情靶庙,我是刑警寧澤问畅,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站六荒,受9級特大地震影響护姆,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜掏击,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一卵皂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧砚亭,春花似錦灯变、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至寻仗,卻和暖如春刃泌,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背愧沟。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工蔬咬, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人沐寺。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓林艘,卻偏偏與公主長得像,于是被迫代替她去往敵國和親混坞。 傳聞我的和親對象是個殘疾皇子狐援,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評論 2 355