前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Android 屏幕适配】屏幕适配通用解决方案 ⑤ ( 自定义组件解决方案 | 自定义 ViewGroup 组件 onMeasure 方法中计算每个子组件坐标数据 | 自定义组件完整代码 )

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

作者头像
韩曙亮
发布2023-03-30 16:14:42
2870
发布2023-03-30 16:14:42
举报
文章被收录于专栏:韩曙亮的移动开发专栏

文章目录

参考文档 :

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

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

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

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

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

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

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

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

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


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

代码语言:javascript
复制
            // 获取该 ViewGroup 子组件个数
            int childCount = this.getChildCount();

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

代码语言:javascript
复制
            // 获取 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 布局属性中的 宽高 , 左上右下边距位置 ;

代码语言:javascript
复制
            // 逐个遍历子组件
            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);
            }

二、自定义组件完整代码


代码语言:javascript
复制
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;
        }
    }
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-08-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • 一、计算素材宽高与屏幕布局宽高比例系数
  • 二、自定义组件完整代码
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档