Android —— 自定义View

本文详细解析了Android自定义View的测量机制,包括ViewGroup如何通过onMeasure方法传递宽度和高度规格给View,以及View如何解析这些规格并确定自身大小。此外,还介绍了View与ViewGroup之间的尺寸协商过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

之前博主学习期间写了不少自定义view 的博文,今天博主发现一篇不错的写自定义view 的文章,这里再来回味一下。
不错的文章链接

一 、确定view的大小

文章已经写的很清楚了,这里再总结一下。
首先,开发者在布局中设置的view的android:layout_width=”“android:layout_height=”“两个配置,告诉viewGroup所需要的view 的大小;然后viewGroup通过onMeasure方法传入的widthMeasureSpec和heightMeasureSpec参数告诉view开发者的需要,即protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) 方法;最后view在重写的onMeasue方法中解译出ViewGroup对自己的要求:

int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize =  MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize =  MeasureSpec.getSize(heightMeasureSpec);

取出来的mode有三种类型,它代表viewGroup对view的态度,这里也按照我的理解大概介绍一下吧(假设现在的size = 100dp):
1.EXACTLY 表示:view你只能用100dp,原因可能是开发者对你的需要,或者是我本身就有100dp,开发者要求你match。
2.AT_MOST表示:view最多能用100dp,原因可能是开发者让你wrap,而viewGroup最大就100dp.
3.UNSPECIFIED表示:view可以随意,将你最理想的大小告诉viewGroup即可。

代码展现:

  @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        width = MeasureSpec.getMode(widthMeasureSpec);
        widthSize = MeasureSpec.getSize(widthMeasureSpec);
        height = MeasureSpec.getMode(heightMeasureSpec);
        heightSize = MeasureSpec.getMode(heightMeasureSpec);
         setMeasuredDimension(width,height);
    }

还有点比较特殊的点:如果view本身并不满意ViewGroup的大小设置,有没有办法协商一下呢?答案是:有。
我可以遵守要求的情况下,同时告诉ViewGroup,虽然我告诉你的我要求的尺寸是遵照你的旨意来的,但实际上我是委屈求全的,我真实想要的大小不是这样的,你能不能再考虑一下。当然ViewGroup会不会理你,那就不一定了。-_-
那就是用这个方法:

resolveSizeAndState((int)(wantedWidth), widthMeasureSpec, 0),    
resolveSizeAndState((int) (wantedHeight), heightMeasureSpec, 0);

具体代码:

public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {  
      final int specMode = MeasureSpec.getMode(measureSpec);  
      final int specSize = MeasureSpec.getSize(measureSpec);  
      final int result;  
      switch (specMode) {     
         case MeasureSpec.AT_MOST:         
             if (specSize < size) {            
                  result = specSize | MEASURED_STATE_TOO_SMALL;//在子View想要的空间太大时设置的标记了         
             } else {            
                  result = size;      
             }         
             break;      
         case MeasureSpec.EXACTLY:          
              result = specSize;      
              break;       
         case MeasureSpec.UNSPECIFIED:   
         default:        
              result = size;   
       }   
       return result | (childMeasuredState & MEASURED_STATE_MASK);
}

注:当然一般的viewGroup并没有理睬view的标记,如果自己来自定义的viewGroup倒是可以关注一下旗下子view的诉求啊。^_^

小要点

上面的onMeasure方法中最后多加了行代码,setMeasuredDimension(width,height);想必很多人注意到了,这是为什么呢?好奇驱动下,我去瞄了瞄源码,贴出来欣赏一下:

 protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int opticalWidth  = insets.left + insets.right;
            int opticalHeight = insets.top  + insets.bottom;

            measuredWidth  += optical ? opticalWidth  : -opticalWidth;
            measuredHeight += optical ? opticalHeight : -opticalHeight;
        }
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
    }

其中的isLayoutModeOptical方法,源码解释是 “Return true if o is a ViewGroup that is laying out using optical bounds.”大概意思是如果viewGroup是一个可见的布局就返回true。这是对viewGroup是否可见状态子view的大小处理,最终都setMeasuredDimension,将宽和高告诉了ViewGroup。
总的来说,setMeasuredDimension这个方法,就是将view确定好的大小告知ViewGroup,但是这里又有一个问题,如果view在接收到viewGroup对自己的大小安排之后,并没有进行强制改变,再将这个大小传给viewGroup,貌似并没有什么意义,那么什么情况下我们才来充写onMeasure方法,不使用super方法呢?
如果我们接受viewgroup的安排,并没有必要重写onMeasure方法,那有同学不高兴了,如果我其他地方要通过width和height来onDraw其他控件呢,孩纸,你可以用getMeasuredHeight()和getMeasuredWidth()来获得呀,获得的大小就是viewGroup要求你这样干的。晕了不,贴代码看看:

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        widthMode = MeasureSpec.getMode(widthMeasureSpec);
        widthSize = MeasureSpec.getSize(widthMeasureSpec);//540这是viewGroup告诉你可以最大是540(这是由于布局设置的自适应)
        heightMode = MeasureSpec.getMode(heightMeasureSpec);
        heightSize = MeasureSpec.getMode(heightMeasureSpec);
        Log.d("dongmj","width0:"+getMeasuredWidth()+";"+"height0:"+getMeasuredHeight());// width0:0;height0:0
        if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {//判断开发者在布局中设置的是否是wrap自适应
            widthSize = 20;
            heightSize = 20;

        }
        setMeasuredDimension(widthSize, heightSize);
        Log.d("dongmj","width1:"+getMeasuredWidth()+";"+"height1:"+getMeasuredHeight());
    }

log打印结果:
09-09 15:34:29.163 1986-1986/com.example.customizeview D/dongmj: width0:0;height0:0
09-09 15:36:42.633 1986-1986/com.example.customizeview D/dongmj: width1:20;height1:20

我在布局里设置的宽和高都是wrap自适应。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值