Android手机的百花齐放的态势下,种类繁多的手机屏幕,也是给开发者带来了很大的工作量,屏幕适配是众多开发者老生长谈的话题,我曾经也是被屏幕适配工作弄的头大,所以今天把自己在工作中的使用的一些方法做个小小的总结;
什么是屏幕是适配?说白了就是屏幕分辨率的适配,其核心就在于两个字“缩放”
有哪些地方需要做适配呢?
1、布局适配
比如使用 wrap_content和match_parent
使用Relativelayout 和contarintlayout
线性布局中的Weight
2、图片资源适配
使用.9格式的png图片或者SVG图片来进行缩放(优势:体积小,不失真)
使用备用位图匹配不同分辨率
3、限定符适配
分辨率限定符 drawable-hdpi,drawable-xhdpi。。。。
尺寸限定符 layout-small ,layout-large
最小宽度限定符 values-sw360dp,values-sw384dp。。。
屏幕方向限定符 layout-land,layout-port
我们的适配基本上都离不开以上三点;
其中第二点和第三点基本上都会用到,用法和原理也很简单,我就不多做介绍,下面我重点讲第一点,布局适配:
相信布局尺寸适配的方案大家多多少少都有过接触,什么百分比适配啊,利用系统能自动识别的资源名进行适配等等
下面我重点介绍我使用过的3中适配方案;
第一种 自定义百分比布局适配:
原理就是使用自定义的布局继承系统原声的布局,然后重写onMeasure方法,在measure中通过设定的百分比对尺寸进行换算
public class DisPlayUtils { private static DisPlayUtils disPlayUtils; private static final float WIDTH = 720; //单位px private static final float HEIGHT = 1080; //单位px private int widthPixels; private int heightPixels; private DisPlayUtils(Context context) { DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); if (displayMetrics.widthPixels > displayMetrics.heightPixels) { //横屏 widthPixels = displayMetrics.heightPixels; heightPixels = displayMetrics.widthPixels; } else { widthPixels = displayMetrics.widthPixels; heightPixels = displayMetrics.heightPixels - getStatusBarHeight(context); } } public static DisPlayUtils getDisPlayUtils(Context context) { if (null == disPlayUtils) { synchronized (DisPlayUtils.class) { if (null == disPlayUtils) { disPlayUtils = new DisPlayUtils(context.getApplicationContext()); } } } return disPlayUtils; } private int getStatusBarHeight(Context context) { int identifier = context.getResources().getIdentifier("status_bar_height", "dimen", "android"); if (identifier > 0) { return context.getResources().getDimensionPixelSize(identifier); } return 0; } public float getScaleX() { return widthPixels / WIDTH; } public float getScaleY() { return heightPixels / HEIGHT; } }
public class CustomLinearLayout extends LinearLayout { public static final float WIDTH = 720; public boolean flag; public CustomLinearLayout(Context context) { super(context); } public CustomLinearLayout(Context context, AttributeSet attrs) { super(context, attrs); } public CustomLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (!flag) { float scaleX = DisPlayUtils.getDisPlayUtils(getContext()).getScaleX(); float scaleY = DisPlayUtils.getDisPlayUtils(getContext()).getScaleY(); for (int i = 0; i < getChildCount(); i++) { //遍历子View View child = getChildAt(i); LayoutParams layoutParams = (LayoutParams) child.getLayoutParams(); if (layoutParams.height != LayoutParams.MATCH_PARENT && layoutParams.height != LayoutParams.WRAP_CONTENT) { layoutParams.height = (int) (layoutParams.height * scaleY); } if (LayoutParams.MATCH_PARENT != layoutParams.width && LayoutParams.WRAP_CONTENT != layoutParams.width) { layoutParams.width = (int) (layoutParams.width * scaleX); } layoutParams.leftMargin = (int) (layoutParams.leftMargin * scaleX); layoutParams.rightMargin = (int) (layoutParams.rightMargin * scaleX); layoutParams.topMargin = (int) (layoutParams.topMargin * scaleY); layoutParams.bottomMargin = (int) (layoutParams.bottomMargin * scaleY); } flag = true; } super.onMeasure(widthMeasureSpec, heightMeasureSpec); } }
第二种 自定义像素适配
原理和百分比类似,也是需要自定义布局,然后重写onMeasure方法,然后通过计算当前屏幕宽度和目标屏幕宽度的比值换算 出目标设备应有的尺寸
public class CustomRelativeLayout extends RelativeLayout { public CustomRelativeLayout(Context context) { this(context, null); } public CustomRelativeLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public CustomRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthMeasure = MeasureSpec.getSize(widthMeasureSpec); int heightMeasure = MeasureSpec.getSize(heightMeasureSpec); //遍历子View for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); ViewGroup.LayoutParams layoutParams = child.getLayoutParams(); if (checkLayoutParams(layoutParams)) { LayoutParams lp = (LayoutParams) layoutParams; float widthPercent = lp.widthPercent; float heightPercent = lp.heightPercent; float marginLeftPercent = lp.marginLeftPercent; float marginRightPercent = lp.marginRightPercent; float marginBottomPercent = lp.marginBottomPercent; float marginTopPercent = lp.marginTopPercent; if (widthPercent > 0) { layoutParams.width = (int) (widthMeasure * widthPercent); } if (heightPercent > 0) { layoutParams.height = (int) (heightMeasure * heightPercent); } if (marginLeftPercent > 0) { ((LayoutParams) layoutParams).leftMargin = (int) (widthMeasure * marginLeftPercent); } if (marginRightPercent > 0) { ((LayoutParams) layoutParams).rightMargin = (int) (widthMeasure * marginRightPercent); } if (marginBottomPercent > 0) { ((LayoutParams) layoutParams).bottomMargin = (int) (heightMeasure * marginBottomPercent); } if (marginTopPercent > 0) { ((LayoutParams) layoutParams).topMargin = (int) (heightMeasure * marginTopPercent); } } } super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { return p instanceof LayoutParams; } public LayoutParams generateLayoutParams(AttributeSet attributeSet) { return new LayoutParams(getContext(), attributeSet); } public static class LayoutParams extends RelativeLayout.LayoutParams { private float widthPercent; private float heightPercent; private float marginLeftPercent; private float marginRightPercent; private float marginTopPercent; private float marginBottomPercent; public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); TypedArray typedArray = c.obtainStyledAttributes(attrs, R.styleable.CustomRelativeLayout); widthPercent = typedArray.getFloat(R.styleable.CustomRelativeLayout_widthPercent, 0); heightPercent = typedArray.getFloat(R.styleable.CustomRelativeLayout_heightPercent, 0); marginLeftPercent = typedArray.getFloat(R.styleable.CustomRelativeLayout_marginLeftPercent, 0); marginRightPercent = typedArray.getFloat(R.styleable.CustomRelativeLayout_marginRightPercent, 0); marginTopPercent = typedArray.getFloat(R.styleable.CustomRelativeLayout_marginTopPercent, 0); marginBottomPercent = typedArray.getFloat(R.styleable.CustomRelativeLayout_marginBottomPercent, 0); typedArray.recycle(); } } }
第三种 就是大家耳熟能详的,修改系统的density值;
这中适配方式就是通过公式 density = px/dp, 通过换算出目标设备的px和当前设备dp的比值,然后把这个新的比值重新赋值给系统的density,达到修改系统分辨率目的进行适配
public class DensityUtil { private static float appDensity;//设备默认的density值 private static float scaleDensity; //设备默认的字体缩放比例 private static float mTargetAppDensity;//目标设备的density private static final float width = 360; //设计图的尺寸 dp public static void setDensityUtil(final Application application) { DisplayMetrics metrics = application.getResources().getDisplayMetrics(); if (appDensity == 0) { appDensity = metrics.density; scaleDensity = metrics.scaledDensity; application.registerComponentCallbacks(new ComponentCallbacks() { @Override public void onConfigurationChanged(Configuration newConfig) { if (newConfig != null && newConfig.fontScale > 0) { scaleDensity = application.getResources().getDisplayMetrics().scaledDensity; } } @Override public void onLowMemory() { } }); } mTargetAppDensity = metrics.widthPixels / width; } public static int getStatusBarHeight(Context context) { int resId = context.getResources().getIdentifier("status_bar_height", "dimen", "android"); if (resId > 0) { return context.getResources().getDimensionPixelSize(resId); } return 45; } public static void setAppDensity(Activity activity) { //目标设备的字体缩放比例 float mTargetScaleDensity = mTargetAppDensity * (scaleDensity / appDensity); //目标设备的像素密度; int targetDensityDpi = (int) (mTargetAppDensity * 160); DisplayMetrics displayMetrics = activity.getResources().getDisplayMetrics(); displayMetrics.scaledDensity = mTargetScaleDensity; displayMetrics.density = mTargetAppDensity; displayMetrics.densityDpi = targetDensityDpi; } }