思路:通过实际的屏幕大小比上设计图的屏幕大小,得到一个缩放值,然后自定义一个ViewGroup,在该ViewGroup的OnMeasure中重新计算每个子View的大小。
比如现在有个控件,宽高各是屏幕的一半,假设我们的屏幕是720p(720*1280)的屏幕,那这个控件的宽和高就应该是 360 * 640,如下图所示:

那么,当我们的屏幕大小变成1080 * 1920的时候,我还想要这个控件宽和高各占屏幕的一半的时候,这时候控件的宽和高就变成了540 * 960,才能实现占一半的效果:

现在需求明确了:无论屏幕大小怎么变,我要这个控件在屏幕中所显示的比例始终是一样的;
然后开始找规律:
//view的宽度等于 屏幕宽度/基准宽度*基准大小
540 = 1080/720*360
960 = 1920/1280*640
所以我们只要找到这个比例,再对设计图上的尺寸做对应的缩放就能实现屏幕布局的适配了。
撸码
1.目录结构

2.需要一个util类来帮助获取屏幕的相关信息
import android.content.Context;
import android.util.DisplayMetrics;
import android.view.WindowManager;
public class ScreenAdapterUtils {
/**
* 基准宽高
*/
private float standardWidth = 720;
private float standardHeight = 1280;
/**
* 当前屏幕宽高
*/
private float width;
private float height;
public static ScreenAdapterUtils instance;
private ScreenAdapterUtils(Context context) {
//如果没有获取过屏幕信息
if (width == 0 || height == 0) {
//通过Context获取WindowManager
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
//实例化一个DisplayMetrics
DisplayMetrics displayMetrics = new DisplayMetrics();
//通过wm获取屏幕信息,并保存在displayMetrics中
windowManager.getDefaultDisplay().getMetrics(displayMetrics);
if (displayMetrics.widthPixels > displayMetrics.heightPixels) {
// 横屏模式
width = displayMetrics.heightPixels;
height = displayMetrics.widthPixels - getStateBar(context);
} else {
//竖屏模式
width = displayMetrics.widthPixels;
height = displayMetrics.heightPixels - getStateBar(context);
}
}
}
/**
* 获取状态栏高度
* @param context
* @return
*/
private float getStateBar(Context context) {
float result = 0;
int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
result = context.getResources().getDimensionPixelSize(resourceId);
}
return result;
}
public static ScreenAdapterUtils getInstance(Context context) {
if (instance == null) {
instance = new ScreenAdapterUtils(context.getApplicationContext());
}
return instance;
}
//获取宽度缩放值
public float getScaleX() {
return width / standardWidth;
}
//获取高度缩放值
public float getScaleY() {
return height / standardHeight;
}
}
3.自定义相关ViewGroup
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import com.ruiheng.antqueen.screenadapter.ScreenAdapterUtils;
import androidx.annotation.Nullable;
public class LinearLayout extends android.widget.LinearLayout {
/**
* measure方法会执行多次,只对view做一次改变
* measure方法会执行多次的原因:
* https://www.jianshu.com/p/733c7e9fb284
*/
private boolean flag;
public LinearLayout(Context context) {
super(context);
}
public LinearLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public LinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (!flag) {
// 获取缩放比例
float scaleX = ScreenAdapterUtils.getInstance(getContext()).getScaleX();
float scaleY = ScreenAdapterUtils.getInstance(getContext()).getScaleY();
int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();
// 重新对宽高赋值
layoutParams.width = (int) (layoutParams.width * scaleX);
layoutParams.height = (int) (layoutParams.height * scaleY);
// 重新对X方向margin赋值
layoutParams.rightMargin = (int) (layoutParams.rightMargin * scaleX);
layoutParams.leftMargin = (int) (layoutParams.leftMargin * scaleX);
// 重新对Y方向margin赋值
layoutParams.topMargin = (int) (layoutParams.topMargin * scaleY);
layoutParams.bottomMargin = (int) (layoutParams.bottomMargin * scaleY);
// 重设属性
child.setLayoutParams(layoutParams);
}
flag = true;
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
4.如何使用
在xml布局文件中使用自己定义的ViewGroup,上面是定义了一个LinearLayout,可以根据需要重写FrameLayout,RelativeLayout。。等等需的任何layout,重写的时候只需要把上面的LinearLayout的onMeasure cv过去就行了。
在使用的时候,宽高设置都按照基准大小来设置,需要注意的是单位必须是px,因为在对view进行缩放的时候都是使用的px

效果:
棒子手机自带切换屏幕分辨率,不管怎么切换,出来的效果都是宽高各占一半效果


5.over