(圖片來源友盟統計)
為什么要適配?
碎片化
- 品牌機型碎片化
- 屏幕尺寸碎片化
- 操作系統碎片化
為了保證用戶獲得一致的用戶體驗效果,使得某一元素在Android不同尺寸、不同分辨率的手機上具備相同的顯示效果,則需要我們進行屏幕適配。
重要概念
- 什么是屏幕尺寸、屏幕分辨率、屏幕像素密度?
- 什么是dp、dip、dpi、sp、px?他們之間的關系是什么?
- 什么是mdpi、hdpi、xdpi、xxdpi、xxxdpi?如何計算和區分?
在下面的內容中我們將介紹這些概念。
屏幕尺寸
屏幕尺寸指屏幕的對角線的長度,單位是英寸,1英寸=2.54厘米
比如常見的屏幕尺寸有2.4、2.8、3.5、3.7、4.2、5.0、5.5、6.0等
屏幕分辨率
屏幕分辨率是指在橫縱向上的像素點數,單位是px,1px=1個像素點。一般以縱向像素橫向像素,如19601080。
屏幕像素密度
屏幕像素密度是指每英寸上的像素點數,單位是dpi,即“dot per inch”的縮寫。屏幕像素密度與屏幕尺寸和屏幕分辨率有關,在單一變化條件下,屏幕尺寸越小、分辨率越高,像素密度越大,反之越小。
dp、dip、dpi、sp、px
px我們應該是比較熟悉的,前面的分辨率就是用的像素為單位,大多數情況下,比如UI設計、Android原生API都會以px作為統一的計量單位,像是獲取屏幕寬高等。
dip和dp是一個意思,都是Density Independent Pixels的縮寫,即密度無關像素,上面我們說過,dpi是屏幕像素密度,假如一英寸里面有160個像素,這個屏幕的像素密度就是160dpi,那么在這種情況下,dp和px如何換算呢?在Android中,規定以160dpi為基準,1dip=1px,如果密度是320dpi,則1dip=2px,以此類推。
假如同樣都是畫一條320px的線,在480800分辨率手機上顯示為2/3屏幕寬度,在320480的手機上則占滿了全屏,如果使用dp為單位,在這兩種分辨率下,160dp都顯示為屏幕一半的長度。這也是為什么在Android開發中,寫布局的時候要盡量使用dp而不是px的原因。
而sp,即scale-independent pixels,與dp類似,但是可以根據文字大小首選項進行放縮,是設置字體大小的御用單位。
mdpi、hdpi、xdpi、xxdpi、xxxdpi
直接看下圖吧:
在進行開發的時候,我們需要把合適大小的圖片放在合適的文件夾里面。
解決方案
1.在布局文件中使用wrap_content, match_parent, layout_weight屬性,并在指定大小的時候使用dp。
2.使用相對布局,禁用絕對布局
在開發中,我們大部分時候使用的都是線性布局、相對布局和幀布局,絕對布局由于適配性極差,所以極少使用。
3.圖片可以提供多套,及不同分辨率的圖片,但是這種會使apk體積增大,還有就是可以使用.9圖來適配一些情況。
3.限定符,主要是給不同的分辨率設置不同的布局文件
4.dimen適配
將屏幕寬度用一個固定的值的單位來統計,即將屏幕縱橫方向上分成若干份,在布局中直接寫控件的像素。這樣的話需要將要適配的手機屏幕的分辨率各自建立一個文件夾 :
然后我們根據一個基準,為基準的意思就是:
比如480320的分辨率為基準*
- 寬度為320,將任何分辨率的寬度分為320份,取值為x1-x320
- 高度為480,將任何分辨率的高度分為480份,取值為y1-y480
例如對于800*480的寬度480:
可以看到x1 = 480 / 基準 = 480 / 320 = 1.5 ;
其他分辨率類似~~
可以使用工具生成:autolayout.jar
不過這種方案也是有局限性:
在生成的values文件夾里,如果沒有對應的分辨率,開始是報錯的,因為默認的values沒有對應dimen,所以只能在默認values里面也創建對應文件,但是里面的數據卻不好處理,因為不知道分辨率,我只好默認為x1=1dp保證盡量兼容。這也是這個解決方案的幾個弊端,對于沒有生成對應分辨率文件的手機,會使用默認values文件夾,如果默認文件夾沒有,就會出現問題。
所以說,這個方案雖然是一勞永逸,但是由于實際上還是使用的px作為長度的度量單位,所以多少和google的要求有所背離,不好說以后會不會出現什么不可預測的問題。其次,如果要使用這個方案,你必須盡可能多的包含所有的分辨率,因為這個是使用這個方案的基礎,如果有分辨率缺少,會造成顯示效果很差,甚至出錯的風險,而這又勢必會增加軟件包的大小和維護的難度,所以大家自己斟酌,擇優使用。
具體使用可以參考:Android 屏幕適配方案
5.Android-percent-support百分比支持庫
具體使用可參考: Android 百分比布局庫(percent-support-lib) 解析與擴展
Android屏幕適配方案,直接填寫設計圖上的像素尺寸即可完成適配,最大限度解決適配問題。


不過看了Issues這種方案并不能解決所有的問題
將切圖放入drawable-nodpi中。
該文件夾中的圖片不會被縮放,在不同分辨率的手機上都只顯示原圖的大小。如此以來,摒棄了系統對于圖片的縮放,為我們以后自己處理圖片的縮放做好了鋪墊。
- 計算出縮放比。
- 依據不同的分辨率計算出縮放比。
在一個高分辨率(如:1920*1080)手機上完成布局。
在布局的過程中,請注意一個問題:不再使用dp、sp作為大小單位,而是統一使用px。
為什么要這么做呢?
- 縮放比例的確定是基于屏幕的分辨率而確定的。
屏幕的分辨率均是采用px作為單位的,所以在布局時亦采用px從而保證縮放比例的一致和準確 - dp和sp均與設備的dpi有關。
不同設備的dpi值不一樣,所以在不同的設備上同一個dp和sp所對應的px值是不盡相同的。如果采用dp和sp作為尺寸的單位,那么在縮放時會產生較大的偏差
代碼實現
1.計算縮放比
int widthPixels = displayMetrics.widthPixels;
scale = (float)widthPixels /
BASE_SCREEN_WIDTH_FLOAT;
通過設備的寬與BASE_SCREEN_WIDTH_FLOAT的比值計算出縮放比。
2.等比例縮放UI
利用該方法對布局中的每個View進行縮放操作。
在該方法中對每個View的寬高,padding,margin值都按比例縮放,并且在縮放后重新設置其布局參數。
3.關于TextView的特殊處理
對于TextView,不但要縮放其尺寸,還需要對其字體進行縮放,除此以外,還要考慮到對TextView的CompoundDrawable進行縮放。
具體的代碼如下:
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多分辨率適配框架