前言
在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
屏幕適配
屏幕分辨率限定符(不推薦)
屏幕分辨率限定符適配需要在 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 文件:
假設(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 文件:
獲取設(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)載處....