秒懂OnMeasure

自定义控件系列:
秒懂OnMeasure
秒懂OnLayout
让自定义ViewGroup里的子控件支持Margin
让自定义ViewGroup支持Padding
自定义ViewGroup的一个综合实践 FlowLayout
onDraw
最简单的自定义View:SwitchView

我感觉之所以写不好自定义view,是因为我们了解的自定义View的基础知识知道的太少,但是在了解自定义view的基础知识的过程中,又很容易被源码带跑偏,找不到重点,结果是看了很多源码,云里雾里等于没看。

很多时候,源码是很重要,但是不懂适可而止的看源码,你就陷入了汪洋大海。
例如:初中几何里老师讲了“两点之间、直线最短”这个公理后,我们就可以做很多几何题目了,做的过程中还很爽,但是老师没讲“两点之间、直线最短”这个公理的源码是什么,为什么“两点之间、直线最短”,你要想证明这个公理,对于初中生甚至大学生都是不可能解答的,但是这丝毫不影响一个初中生做几何题目(当然,我记得老师说过,公理不需要证明)

所以这个系列博客采用知识点+应用的模式,有重点,有举例

当这些结论性的知识点积累到足够多,很多自定义view,不过就是多个结论的综合应用+小小逻辑算法,我们怕的不是小小逻辑算法,再绕的算法,多试验就出来了,但是不懂基本的结论性知识点,就很茫然了

知识点

关于MeasureSpec是什么,不懂的朋友请先搜索一下,这里对这个不做解释。

  1. 如果你的自定义view的宽高只支持MeasureSpec.EXACTLY(即:match_parent和具体的数值),那么onMeasure方法不需要重写,因为View这个基类已经默认实现了

  2. 如果你想支持MeasureSpec.AT_MOST(即:wrap_content),必须重写onMeasure方法,不然你写wrap_content和match_parent效果是一样的(即系统默认返回一个父容器所能给予你的最大尺寸)。

    想想为什么,View这个基类,不帮我们实现MeasureSpec.AT_MOST模式呢?
    因为不同的view,对于自己的MeasureSpec.AT_MOST(包裹内容)有自己特有的计算方式,例如:ImageView的MeasureSpec.AT_MOST,ImageView会根据你设置的图片,来计算在wrap_content时候的宽高。
    TextView的MeasureSpec.AT_MOST,TextView会根据你设置的文字内容多少,(因为内容多了可能换行)和你设置的TextSize(字体大了,自然需要更大的宽高)来计算Textview在wrap_content时候的宽高
    FrameLayout在MeasureSpec.AT_MOST模式下,宽度就是所有子view里面最大的那个View的宽度,高度就是所有子view里的最大的那个View的高度
    LinearLayout在MeasureSpec.AT_MOST模式下(假设是竖向布局),宽度是所有View里最大的那个View的宽度,高度是所有View的高度的总和
    所以View天生支持MeasureSpec.EXACTLY,但是他对于MeasureSpec.AT_MOST是无能为力的,需要具体的View自己具体实现

  3. 最重要就是这个方法,计算出控件在各种模式下的宽高,通过这个方法设置进去,就好了setMeasuredDimension(width, height);

以下就是View的onMeasure的默认实现(源码)

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
    
 public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST://关键在这里系统对AT_MOST和EXACTLY的处理是一样的,
        //都是返回父容器的最大尺寸,不信你可以自己打印出来看看
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

应用

1. 给出支持MeasureSpec.AT_MOST的模板代码

其实这个代码是套路代码,结构是不变的

当写wrap_content,意思是父布局不传给你确定的尺寸,需要这个view自己确定个默认的尺寸,这个尺寸是你自己根据自己的情况计算出来的。

public class CustomView extends View {


    public CustomView(Context context) {
        super(context);
    }

    public CustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }


    
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 首先计算一下在AtMost模式下,这个自定义view的宽高,
        // 这里把计算出来的宽高封装在了一个Point里,x为宽,y为高
        // 这是模板代码,你只需要按照自己的实现caculateAtMostSize()的具体逻辑,其余的不用变
                Point point = caculateAtMostSize(widthMeasureSpec,heightMeasureSpec);


        // 根据 默认宽高、AtMost下的宽高、MeasureSpec测量规格,计算出最终这个view的宽高
        int width = measureSize(0, point.x, widthMeasureSpec);
        int height = measureSize(0, point.y, heightMeasureSpec);

        // 把上面计算出来的宽高作为参数设置给setMeasuredDimension就ok了
        setMeasuredDimension(width, height);
    }


    /**
     * 通过widthMeasureSpec计算出这个View最终的宽高
     *
     * @param defalut     这个view的默认值,仅仅是为了支持下UNSPECIFIED模式,但是这个模式其实用不到
     * @param atMostSize  AT_MOST下的尺寸
     * @param measureSpec 测量规格(包含了模式+尺寸)
     * @return
     */
    private int measureSize(int defalut, int atMostSize, int measureSpec) {


        int result = defalut;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
            case MeasureSpec.UNSPECIFIED:
                result = defalut;
                break;
            case MeasureSpec.AT_MOST:
                //在AT_MOST模式下,系统传来的specSize是一个父容器所能容纳的最大值,你这个自定义view计算的尺寸不能大于这个值
                result = Math.min(atMostSize, specSize);
                break;
            case MeasureSpec.EXACTLY:
                result = specSize;
                break;
        }


        return result;
    }


    /**
     * 计算本View在AtMost模式下的宽高
     * 其他代码都是不用动的,在这里写下你特有的逻辑就可以
     * 我这里只是简单的返回宽高都是 200
     *
     * @return
     */
        private Point caculateAtMostSize(int widthMeasureSpec, int heightMeasureSpec) {
        //一般情况,写的自定义view是不需要特别计算这个值的,我会直接给一个默认值
        //但是你是自定义ViewGroup的话,这里你必须好好写了
        int width = 200;
        int height = 200;
        return new Point(width, height);
    }











2. 一个自定义ViewGroup支持MeasureSpec.AT_MOST的例子:

效果如图:
在这里插入图片描述

对应的布局文件时这样的
在这里插入图片描述
注意:这里仅仅写支持AT_MOST的代码,还没写onLayout,所以代码运行是看不到效果的,可以打印log,来看下这个view的宽高是不是正确的

/**
     * 计算本View在AtMost模式下的宽高
     * 其他代码都是不用动的,在这里写下你特有的逻辑就可以
     *
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     * @return
     */
    private Point caculateAtMostSize(int widthMeasureSpec, int heightMeasureSpec) {

        int width = 0;
        int height = 0;

        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            // 测量一下子控件的宽高
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
            // 获得子控件的宽高
            int childWidth = child.getMeasuredWidth();
            int childHeight = child.getMeasuredHeight();

            // 因为我们的自定义View模拟的是竖向的LinearLayout,所以:
            // 控件的宽度为所有子控件里,宽度最大的那个view的宽度,
            // 控件高度是所有子空间的高度之和
            width = Math.max(childWidth, width);
            height += childHeight;
        }


        return new Point(width, height);
    }

本例源码github之CustomView

参考资料:
《Android群英传》

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当 Android 中的对话框位置发生更改时,会触发对话框中子视图的 `onMeasure` 方法。如果你想在更改位置后取消子视图的 `onMeasure`,可以尝试以下方法: 1. 在对话框的 `onCreate` 方法中,将对话框的布局参数设置为 `WRAP_CONTENT`。这将使对话框的大小根据其内容自动调整,而不是强制使用固定大小。 2. 在对话框的 `onWindowAttributesChanged` 方法中,将对话框的布局参数设置为屏幕宽度的一半。这将使对话框始终处于屏幕中央,并且不会触发子视图的 `onMeasure` 方法。 以下是示例代码: ```java public class MyDialog extends Dialog { public MyDialog(Context context) { super(context); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.my_dialog_layout); // 设置对话框的布局参数为 WRAP_CONTENT WindowManager.LayoutParams params = getWindow().getAttributes(); params.width = WindowManager.LayoutParams.WRAP_CONTENT; getWindow().setAttributes(params); } @Override public void onWindowAttributesChanged(WindowManager.LayoutParams params) { super.onWindowAttributesChanged(params); // 设置对话框的布局参数为屏幕宽度的一半 DisplayMetrics metrics = getContext().getResources().getDisplayMetrics(); params.width = metrics.widthPixels / 2; getWindow().setAttributes(params); } } ``` 注意:这是一种简单的解决方案,可能不适用于所有情况。如果子视图仍然需要进行测量,请考虑使用其他方法来优化性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值