Android自定义控件之测量onMeasure

一,写在前面

当Android原生控件无法满足开发需求时,需要自己来创造view,自定义控件。自定义控件分三步来完成:测量(onMeasure),布局(onLayout),绘制(onDraw)。今天主要介绍自定义流程的第一步-测量,通常一个布局文件的控件的简单嵌套,显示如下:
LinearLayout里面有两个子view:TextView,RelativeLayout(里面又有两个TextView)。 我们知道自定义控件无非是extends View, extends ViewGroup, extends 容器控件/非容器控件,因此提取出其中的View.java, FrameLayout.java, TextView.java进行分析。下面展示涉及类,方法的结构图,看不懂互相之间关系没事,可以看完后面的分析,然后再回过头来看结构图。 结构图:

由上图可知,语法角度:子类可以重写onMeasure,只能继承View的measure,setMeasuredDimension方法。测量流程分为两种情况讨论:容器控件ViewGroup,原始的View(非容器控件)。原始的View测量,只需要测量自己的宽高;而容器控件需要先测量所有的子View的宽高,然后再测量自己的宽高。 看懂本篇文章,还需要大家自己先去研究下类View$MeasureSpec,相对比较简单,本文不描述MeasureSpec相关知识。

二,源码分析之View

先分析原始的View,打开View.java文件,查看measure方法:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int oWidth  = insets.left + insets.right;
            int oHeight = insets.top  + insets.bottom;
            widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
            heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
        }
        if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
                widthMeasureSpec != mOldWidthMeasureSpec ||
                heightMeasureSpec != mOldHeightMeasureSpec) {

            // first clears the measured dimension flag
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

            resolveRtlPropertiesIfNeeded();

            // measure ourselves, this should set the measured dimension flag back
            onMeasure(widthMeasureSpec, heightMeasureSpec);

            // flag not set, setMeasuredDimension() was not invoked, we raise
            // an exception to warn the developer
            if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
                throw new IllegalStateException("onMeasure() did not set the"
                        + " measured dimension by calling"
                        + " setMeasuredDimension()");
            }

            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
        }

        mOldWidthMeasureSpec = widthMeasureSpec;
        mOldHeightMeasureSpec = heightMeasureSpec;
    }
调用view.measure(w,h)来测量控件宽高,那么这个方法是何时调用的呢?在该view的父控件测量自己宽高时调用。因为该view所在父容器在测量自己宽高时,会先测量子view的宽高,最终都会调用child.measure(w,h),最后才测量自己的宽高。后面分析容器控件的测量流程时,会一目了然。
主要分析measure(w,h)的两个关键点:
一,字段mPrivateFlags
1.1 字段mPrivateFlags在调用onMeasure(w,h)前,执行mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET,设置 成员变量mPrivateFlags的MEASURED_DIMENSION_SET位设置为0;
1.2 在onMeasure(w,h)执行完成后,判断if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET)决定是否抛出IllegalStateException异常;
二,实际测量方法onMeasure(w,h), 进入该方法查看源码:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
进入setMeasuredDimension(w,h)查看源码:
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        boolean optical = isLayoutModeOptical(this);
        if (opt
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值