Android 屏幕适配屏幕适配通用解决方案 ⑤ ( 自定义组件解决方案 | 自定义 ViewGroup 组件 onMeasure 方法中计算每个子组件坐标数据 | 自定义组件完整代码 )

Posted 韩曙亮

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 屏幕适配屏幕适配通用解决方案 ⑤ ( 自定义组件解决方案 | 自定义 ViewGroup 组件 onMeasure 方法中计算每个子组件坐标数据 | 自定义组件完整代码 )相关的知识,希望对你有一定的参考价值。

文章目录


参考文档 :


在博客 【Android 屏幕适配】屏幕适配通用解决方案 ② ( 自定义组件解决方案 | 需要解决的问题 : 设计稿坐标数据转为屏幕真实坐标数据 | 实现步骤 ) 中 , 提出 如果要实现将 宽高为 720 x 1232 的设计稿 , 对应 手机屏幕中除 状态栏之外的 布局 , 需要完成如下操作 :


首先 ,获取到实际的设备屏幕数据 , 如 手机屏幕实际宽高 , 屏幕像素密度 DPI 等数据 ;

然后 , 计算实际设备的宽高 , 扣掉状态栏的高度 , 不同手机设备状态栏高度不同 , 然后再进行后续计算 ;

再后 , 给出一个 设计稿屏幕实际有效像素值换算比例 ;

最后 , 根据给出的比例 , 在 自定义组件的 onMeasure 方法 中 , 进 行动态换算 , 计算出在当前设备中每个组件的 实际坐标数据 ;


在博客 【Android 屏幕适配】屏幕适配通用解决方案 ③ ( 自定义组件解决方案 | 获取设备状态栏高度 | 获取设备屏幕数据 ) 中 , 完成了前两项工作 , 已经获取了实际的屏幕数据 , 状态栏高度 ;


在博客 【Android 屏幕适配】屏幕适配通用解决方案 ④ ( 自定义组件解决方案 | 计算设计稿与实际布局的比例系数 ) 中 , 计算了设计稿与实际布局的比例系数 ;


本篇博客中完成第四项工作 , 根据给出的比例 , 在 自定义组件的 onMeasure 方法 中 , 进 行动态换算 , 计算出在当前设备中每个组件的 实际坐标数据 ;





一、计算素材宽高与屏幕布局宽高比例系数



首先 , 获取自定义 ViewGroup 组件的子组件的个数 ;

            // 获取该 ViewGroup 子组件个数
            int childCount = this.getChildCount();

然后 , 获取 X 轴 和 Y 轴 方向上 , 设计稿 与 屏幕实际布局 缩放系数 ; 参考 【Android 屏幕适配】屏幕适配通用解决方案 ④ ( 自定义组件解决方案 | 计算设计稿与实际布局的比例系数 ) 博客 ;

            // 获取 X 轴 设计稿 与 屏幕实际布局 缩放系数
            float xScale =  getXScaleValue();
            // 获取 Y 轴 设计稿 与 屏幕实际布局 缩放系数
            float yScale =  getYScaleValue();

    /**
     * 获取设备实际宽度与参考宽度的比值
     * @return
     */
    public float getXScaleValue()
        return screenWidth / REFERENCE_WIDTH;
    

    /**
     * 获取沙设备实际高度与参考高度的比值
     * @return
     */
    public float getYScaleValue()
        return screenHeight / REFERENCE_HEIGHT;
    

最后 , 逐个遍历子组件 , 修改子组件的 LayoutParams 布局属性中的 宽高 , 左上右下边距位置 ;

            // 逐个遍历子组件
            for (int i = 0;i < childCount;i++)
                // 获取子组件
                View child = this.getChildAt(i);
                // 获取子组件的布局属性
                LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();

                // 对子组件的 宽度 , 高度 , 左上右下的边距 进行换算
                layoutParams.width = (int) (layoutParams.width * xScale);
                layoutParams.height = (int) (layoutParams.height * yScale);

                layoutParams.leftMargin = (int) (layoutParams.leftMargin * xScale);
                layoutParams.topMargin = (int) (layoutParams.topMargin * yScale);
                layoutParams.rightMargin = (int) (layoutParams.rightMargin * xScale);
                layoutParams.bottomMargin = (int) (layoutParams.bottomMargin * yScale);
            




二、自定义组件完整代码



package kim.hsl;

import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.WindowManager;
import androidx.constraintlayout.widget.ConstraintLayout;
import java.lang.reflect.Field;

/**
 * 屏幕适配解决方案
 */
public class ScreenAdaptLayout extends ConstraintLayout 

    /**
     * 自定义布局中的子组件坐标数据是否需要进行换算
     */
    static boolean isCaculateFlag = true;
    /**
     * 屏幕适配参考宽度 / 设计稿宽度
     */
    public final float REFERENCE_WIDTH = 720;
    /**
     * 屏幕适配参考高度 / 设计稿高度
     * 这里注意标准高度是 1280, 去掉了状态栏后为 1232
     */
    public final float REFERENCE_HEIGHT = 1232;
    /**
     * 设备布局实际宽度
     */
    public float screenWidth;
    /**
     * 设备布局实际高度
     */
    public float screenHeight;
    /**
     * 上下文对象
     */
    public Context context;

    public ScreenAdaptLayout(Context context) 
        super(context);
        this.context = context;
    

    public ScreenAdaptLayout(Context context, AttributeSet attrs) 
        super(context, attrs);
        this.context = context;
    

    public ScreenAdaptLayout(Context context, AttributeSet attrs, int defStyleAttr) 
        super(context, attrs, defStyleAttr);
        this.context = context;
    

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) 
        super.onLayout(changed, l, t, r, b);
    

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) 
        // 初始化设备屏幕数据
        initMetrics();
        // isCaculateFlag 标志位, 保证该操作只执行一次
        if(isCaculateFlag)
            // 获取该 ViewGroup 子组件个数
            int childCount = this.getChildCount();
            // 获取 X 轴 设计稿 与 屏幕实际布局 缩放系数
            float xScale =  getXScaleValue();
            // 获取 Y 轴 设计稿 与 屏幕实际布局 缩放系数
            float yScale =  getYScaleValue();
            // 逐个遍历子组件
            for (int i = 0;i < childCount;i++)
                // 获取子组件
                View child = this.getChildAt(i);
                // 获取子组件的布局属性
                LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();

                // 对子组件的 宽度 , 高度 , 左上右下的边距 进行换算
                layoutParams.width = (int) (layoutParams.width * xScale);
                layoutParams.height = (int) (layoutParams.height * yScale);

                layoutParams.leftMargin = (int) (layoutParams.leftMargin * xScale);
                layoutParams.topMargin = (int) (layoutParams.topMargin * yScale);
                layoutParams.rightMargin = (int) (layoutParams.rightMargin * xScale);
                layoutParams.bottomMargin = (int) (layoutParams.bottomMargin * yScale);
            
            isCaculateFlag = false;
        
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    

    @Override
    protected void onDraw(Canvas canvas) 
        super.onDraw(canvas);
    

    private void initMetrics()
        // 获取当前设备的屏幕信息
        WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics displayMetrics = new DisplayMetrics();
        windowManager.getDefaultDisplay().getMetrics(displayMetrics);

        if(screenWidth == 0.0f || screenHeight == 0.0f)
            //获取状态框信息
            int statusBarHeight = getDimenValue(context,48);

            // 在屏幕真实宽高上减去状态栏高度
            if(displayMetrics.widthPixels > displayMetrics.heightPixels)
                // 宽度大于高度说明是横屏状态, 状态栏在左侧或者右侧
                this.screenWidth = displayMetrics.heightPixels;
                this.screenHeight = displayMetrics.widthPixels - statusBarHeight;
            else
                // 高度大于宽度说明是竖屏状态, 状态栏在上侧
                this.screenWidth = displayMetrics.widthPixels;
                this.screenHeight = displayMetrics.heightPixels - statusBarHeight;
            
        
    

    /**
     * 获取设备实际宽度与参考宽度的比值
     * @return
     */
    public float getXScaleValue()
        return screenWidth / REFERENCE_WIDTH;
    

    /**
     * 获取沙设备实际高度与参考高度的比值
     * @return
     */
    public float getYScaleValue()
        return screenHeight / REFERENCE_HEIGHT;
    

    /**
     * 通过反射 com.android.internal.R$dimen 类, 获取其中的某些字段
     * @param context   上下文对象
     * @param defValue  如果没有成功获取指定字段, 这里返回一个默认值
     * @return
     */
    public int getDimenValue(Context context, int defValue) 
        try 
            // 反射 com.android.internal.R$dimen 类
            Class<?> clazz = Class.forName("com.android.internal.R$dimen");
            // 创建 com.android.internal.R$dimen 实例对象
            Object instance = clazz.newInstance();
            // 获取指定的字段, 这里用于获取 system_bar_height, 也就是系统状态栏高度
            Field field = clazz.getField("system_bar_height");
            // 获取字段的值
            int fieldValue = (int) field.get(instance);
            // 获取的字段值是资源 ID, 需要转为实际的像素值
            return context.getResources().getDimensionPixelOffset(fieldValue);
         catch (Exception e) 
            // 如果执行出现异常, 则返回默认值
            return defValue;
        
    

以上是关于Android 屏幕适配屏幕适配通用解决方案 ⑤ ( 自定义组件解决方案 | 自定义 ViewGroup 组件 onMeasure 方法中计算每个子组件坐标数据 | 自定义组件完整代码 )的主要内容,如果未能解决你的问题,请参考以下文章

Android 屏幕适配屏幕适配通用解决方案 ③ ( 自定义组件解决方案 | 获取设备状态栏高度 | 获取设备屏幕数据 )

Android 屏幕适配屏幕适配通用解决方案 ③ ( 自定义组件解决方案 | 获取设备状态栏高度 | 获取设备屏幕数据 )

Android 屏幕适配屏幕适配通用解决方案 ① ( 定义 dimens.xml 方案 | 使用 ScreenMatch 插件生成不同屏幕分辨率的 dimens.xml 配置 )

Android 屏幕适配屏幕适配通用解决方案 ① ( 定义 dimens.xml 方案 | 使用 ScreenMatch 插件生成不同屏幕分辨率的 dimens.xml 配置 )

Android 屏幕适配屏幕适配通用解决方案 ④ ( 自定义组件解决方案 | 计算设计稿与实际布局的比例系数 )

Android 屏幕适配屏幕适配通用解决方案 ④ ( 自定义组件解决方案 | 计算设计稿与实际布局的比例系数 )