(圖片來源友盟統(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è)置字體大小的御用單位稼跳。
mdpi、hdpi汤善、xdpi红淡、xxdpi抹镊、xxxdpi
直接看下圖吧:
在進(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ò)展
Android屏幕適配方案诱渤,直接填寫設(shè)計(jì)圖上的像素尺寸即可完成適配勺美,最大限度解決適配問題碑韵。
不過看了Issues這種方案并不能解決所有的問題
將切圖放入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多分辨率適配框架