android 最全屏幕适配方案-自定义实现谷歌百分比布局
为什么要屏幕适配?
因为Android设备的碎片化严重,导致app的界面元素在不同分辨率的设备屏幕尺寸上显示不一致;
为了让布局、布局组件、资源、用户界面流程,匹配不同设备屏幕尺寸;
常见屏幕适配方式:
布局适配
避免写死控件尺寸,使用warp_content\match_content;
灵活使用以下属性设置:
LinearLayout android:layout_weight="0.5"
RelativeLayout android:layout_centerInParent="true"....
ContraintLayout android:layout_constraintLeft_toLeftOf="parent"...
Percent-support-lib xxx:layout_widgetPercent="30%"
图片资源适配
.9图或者SVG图实现缩放
备用位图匹配不同分辨率
用户流程适配
根据业务逻辑执行不同的跳转逻辑
根据别名展示不同的界面
例如:手机和平板适配中就会采用此方式实现
限定符适配
分辨率限定符 drawable-hdpi、drawable-xhdpi,等
尺寸限定符 layout-small(小屏),layout-large(大屏),
最小宽度限定符 values-sw360dp,values-sw384dp,(数字表示设备像素密度)
屏幕方向限定符 layout-land(水平),layout-port(竖直)
市场刘海屏和水滴屏请参考各自官网适配详情
自定义View适配
原理:以一个特定尺寸设备屏幕分辨率为参考,在View的加载过程中,根据当前设备的实际屏幕分辨率和参考设备屏幕分辨率缩放比换算出View控件的目标像素,再作用到控件上;
实现步骤:
一、创建一个工具类:用来获取屏幕水平和垂直方向缩放比
public class Utils {
private static Utils instance;
private float mDisplayWidth;
private float mDisplayHeigth;
private float STANDARD_WIDTH = 720;
private float STANDARD_HEIGHT = 1280;
private Utils(Context context) {
if (mDisplayWidth == 0 || mDisplayHeigth == 0) {
// 获取屏幕实际宽高
WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
if (manager != null) {
DisplayMetrics metrics = new DisplayMetrics();
manager.getDefaultDisplay().getMetrics(metrics);
if (metrics.widthPixels > metrics.heightPixels) {//横屏
mDisplayWidth = metrics.heightPixels;
mDisplayHeigth = metrics.widthPixels;
} else {
mDisplayWidth = metrics.widthPixels;
mDisplayHeigth = metrics.heightPixels - getStatusBarHeight(context);
}
}
}
}
public static Utils getInstance(Context context) {
if (instance == null) {
instance = new Utils(context.getApplicationContext());
}
return instance;
}
// 获取屏幕状态栏高度
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 mDisplayHeigth / STANDARD_HEIGHT;
}
}
二、在自定义View中测量时实际使用
public class MyView extends RelativeLayout {
private boolean flag;// 用于标记,防止二次测量
private float scaleX;
private float scaleY;
public MyView(Context context) {
this(context, null);
}
public MyView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//获取缩放比
scaleX = Utils.getInstance(getContext()).getHorizontalScale();
scaleY = Utils.getInstance(getContext()).getVerticalScale();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (!flag) {
int childCount = getChildCount();
if (childCount > 0) {
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
// 获取每个View的Params属性
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);
}
}
flag = true;
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
百分比布局适配
Android提供了Android-percent-support这个库,支持百分比布局,在一定程度上可以解决屏幕适配的问题
两种布局:
PercentRelativeLayout和PercentFrameLayout
PercentRelativeLayout继承RelativeLayout
PercentFrameLayout继承FrameLayout
官方使用
build.gradle添加:
implementation 'com.android.support:percent:28.0.0'
PercentFrameLayout
<?xml version="1.0" encoding="utf-8"?>
<android.support.percent.PercentFrameLayout
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">
<ImageView
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_heightPercent="20%"
app:layout_widthPercent="50%"
android:layout_gravity="center"
android:background="@mipmap/picture"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:layout_gravity="center"
android:text="孩子"
android:gravity="center"/>
</android.support.percent.PercentFrameLayout>
根据UI的设计原型在不同的设备屏幕上显示效果都是一样的;
下面就是重点了,自己实现谷歌官方百分比布局
一、values文件夹下创建自定义属性attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="PercentLayout">
<attr name="widthPercent" format="float"></attr>
<attr name="heightPercent" format="float"></attr>
<attr name="marginLeftPercent" format="float"></attr>
<attr name="marginRightPercent" format="float"></attr>
<attr name="marginTopPercent" format="float"></attr>
<attr name="marginBottomPercent" format="float"></attr>
</declare-styleable>
</resources>
二、创建自定义布局,并解析自定义属性
public class PercentLayout extends RelativeLayout {
public PercentLayout(Context context) {
this(context, null);
}
public PercentLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PercentLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public static class PercentParams extends RelativeLayout.LayoutParams {
private float widthPercent;
private float heightPercent;
private float marginLeftPercent;
private float marginRightPercent;
private float marginTopPercent;
private float marginBottomPercent;
public PercentParams(Context c, AttributeSet attrs) {
super(c, attrs);
// 解析自定义属性
TypedArray array = c.obtainStyledAttributes(attrs, R.styleable.PercentLayout);
widthPercent = (float) array.getFloat(R.styleable.PercentLayout_widthPercent, 0);
heightPercent = (float) array.getFloat(R.styleable.PercentLayout_heightPercent, 0);
marginLeftPercent = (float) array.getFloat(R.styleable.PercentLayout_marginLeftPercent, 0);
marginRightPercent = (float) array.getFloat(R.styleable.PercentLayout_marginRightPercent, 0);
marginTopPercent = (float) array.getFloat(R.styleable.PercentLayout_marginTopPercent, 0);
marginBottomPercent = (float) array.getFloat(R.styleable.PercentLayout_marginBottomPercent, 0);
array.recycle();
}
}
}
三、循环测量子view并设置子view的params值;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 获取父容器宽高
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heighSize = MeasureSpec.getSize(heightMeasureSpec);
// 循环遍历子view并设置params
int childCount = getChildCount();
if (childCount > 0) {
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();
// 如果是自定义的百分比属性
if (checkLayoutParams(layoutParams)) {
PercentParams percentParams = (PercentParams) layoutParams;
float widthPercent = percentParams.widthPercent;
float heightPercent = percentParams.heightPercent;
float marginLeftPercent = percentParams.marginLeftPercent;
float marginRightPercent = percentParams.marginRightPercent;
float marginTopPercent = percentParams.marginTopPercent;
float marginBottomPercent = percentParams.marginBottomPercent;
if (widthPercent > 0) layoutParams.width = (int) (widthSize * widthPercent);
if (heightPercent > 0) layoutParams.height = (int) (heighSize * heightPercent);
if (marginLeftPercent > 0)
layoutParams.leftMargin = (int) (widthSize * marginLeftPercent);
if (marginRightPercent > 0)
layoutParams.rightMargin = (int) (widthSize * marginRightPercent);
if (marginTopPercent > 0)
layoutParams.topMargin = (int) (heighSize * marginTopPercent);
if (marginBottomPercent > 0)
layoutParams.bottomMargin = (int) (heighSize * marginBottomPercent);
}
}
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
private boolean checkLayoutParams(LayoutParams params) {
return params instanceof PercentParams;
}
// 这里一定要重写此方法,并返回自定义的PercentParams
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new PercentParams(getContext(), attrs);
}
四、使用自定义百分比布局
<?xml version="1.0" encoding="utf-8"?>
<com.xxx.uidemo.PercentLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:text="百分比控件"
app:heightPercent="0.5"
app:widthPercent="0.5"
tools:ignore="MissingPrefix" />
</com.xxx.uidemo.PercentLayout>