自定义ViewGroup知识总结

本文详细介绍了自定义ViewGroup的基本步骤,包括重载构造函数、复写onMeasure方法以确定ViewGroup及其子View的宽高,以及复写onLayout方法以摆放子View。同时提供了onMeasure和onLayout的具体实现代码示例。

自定义ViewGroup的大体步骤:

1.重载构造函数

2.复写onMeasure,返回值给getMeasuredWidth和getMeasuredHeight(optional)

3.复写onLayout,负责摆放子View。

下面是ViewGroup API文档对于这两个方法的介绍:

protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec)

Added in API level 1
Measure the view and its content to determine the measured width and the measured height. This method is invoked by measure(int, int) and should be overridden by subclasses to provide accurate and efficient measurement of their contents.

CONTRACT: When overriding this method, you must call setMeasuredDimension(int, int) to store the measured width and height of this view. Failure to do so will trigger an IllegalStateException, thrown by measure(int, int). Calling the superclass' onMeasure(int, int) is a valid use.

The base class implementation of measure defaults to the background size, unless a larger size is allowed by the MeasureSpec. Subclasses should override onMeasure(int, int) to provide better measurements of their content.

If this method is overridden, it is the subclass's responsibility to make sure the measured height and width are at least the view's minimum height and width (getSuggestedMinimumHeight() and getSuggestedMinimumWidth()).

Parameters
widthMeasureSpec	horizontal space requirements as imposed by the parent. The requirements are encoded with View.MeasureSpec.
heightMeasureSpec	vertical space requirements as imposed by the parent. The requirements are encoded with View.MeasureSpec.

------------------------------------------分割线--------------------------------------------------------------------


protected void onLayout (boolean changed, int left, int top, int right, int bottom)

Added in API level 1
Called from layout when this view should assign a size and position to each of its children. Derived classes with children should override this method and call layout on each of their children.

Parameters
changed	This is a new size or position for this view
left	Left position, relative to parent
top	Top position, relative to parent
right	Right position, relative to parent
bottom	Bottom position, relative to parent

关于android绘图过程,这是官方的介绍


绘图分为Measure和Layout两个步骤(当然后面还有Draw)


1.onMeasure(测量所有的childView从而确定自己的宽高)

体现在我们的OnMeasure步骤里,就是根据xml文件的设定,把所有的child View都测量一遍,然后设置自己的宽高,具体的值可以通过

getMeasuredWidth和getMeasuredHeight获取。

一般的onMeasure是这么写的(别处抄来的代码):

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        
super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
        int measureWidth = measureWidth(widthMeasureSpec);  
        int measureHeight = measureHeight(heightMeasureSpec);  
        // 计算自定义的ViewGroup中所有子控件的大小,和遍历一遍子View是一样的  
        measureChildren(widthMeasureSpec, heightMeasureSpec);  
        // 设置自定义的控件MyViewGroup的大小  
        setMeasuredDimension(measureWidth, measureHeight);  
    }  
  
    private int measureWidth(int pWidthMeasureSpec) {  
        int result = 0;  
        int widthMode = MeasureSpec.getMode(pWidthMeasureSpec);// 得到模式  
        int widthSize = MeasureSpec.getSize(pWidthMeasureSpec);// 得到尺寸  
  
        switch (widthMode) {  
        /** 
         * mode共有三种情况,取值分别为MeasureSpec.UNSPECIFIED, MeasureSpec.EXACTLY, 
         * MeasureSpec.AT_MOST。 
         *  
         *  
         * MeasureSpec.EXACTLY是精确尺寸, 
         * 当我们将控件的layout_width或layout_height指定为具体数值时如andorid 
         * :layout_width="50dip",或者为FILL_PARENT是,都是控件大小已经确定的情况,都是精确尺寸。 
         *  
         *  
         * MeasureSpec.AT_MOST是最大尺寸, 
         * 当控件的layout_width或layout_height指定为WRAP_CONTENT时 
         * ,控件大小一般随着控件的子空间或内容进行变化,此时控件尺寸只要不超过父控件允许的最大尺寸即可 
         * 。因此,此时的mode是AT_MOST,size给出了父控件允许的最大尺寸。 
         *  
         *  
         * MeasureSpec.UNSPECIFIED是未指定尺寸,这种情况不多,一般都是父控件是AdapterView, 
         * 通过measure方法传入的模式。 
         */  
        case MeasureSpec.AT_MOST:  
        case MeasureSpec.EXACTLY:  
            result = widthSize;  
            break;  
        }  
        return result;  
    }  
  
    private int measureHeight(int pHeightMeasureSpec) {  
        int result = 0;  
  
        int heightMode = MeasureSpec.getMode(pHeightMeasureSpec);  
        int heightSize = MeasureSpec.getSize(pHeightMeasureSpec);  
  //AT_MOST和EXACTLY的话就直接使用从MeasureSpec里面取出来的值(xml文件里设定的)
        switch (heightMode) {  
        case MeasureSpec.AT_MOST:  
        case MeasureSpec.EXACTLY:  
            result = heightSize;  
            break;  
        }  
        return result;  
    }  

注意onMeasure的两个参数,widthMeasureSpec(存储了所有子View以及该ViewGroup的MODE和Size信息,xml文件里设定的值)和heightMeasureSpec,

而widthMeasureSpec和heightMeasureSpec里面又分别存储了宽高的MODE和Size,一共四个:

 /*     int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
        int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
        int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
        int modeHeight = MeasureSpec.getMode(heightMeasureSpec);*/

MeasureSpec有三种MODE,一般来讲

1.EXACTLY,像是60dp,match_parent这种;

2.AT_MOST,像是wrap_content这种;

3.unspecified用的不多。


很显然,如果不想用xml里面的MODE和Size的话你可以这样:

MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST)

你可以根据取出来的MODE switch要用哪种size。

下面代码表示除非我们自定义的ViewGroup设置了layout_width = "100dp"或者layout_width = "match_parent"这些情况,使用我们上面计算的width和height。

setMeasuredDimension((modeWidth == MeasureSpec.EXACTLY)?sizeWidth:width
                ,(modeHeight == MeasureSpec.EXACTLY)?sizeHeight:height);

所以如果不是打算让自定义ViewGroup的宽高来点花样,直接两句话搞定onMeasure:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

//测量所有child的宽高
        measureChildren(widthMeasureSpec,heightMeasureSpec);
//设置自己的宽高,效果和super是一样的。
        setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),MeasureSpec.getSize(heightMeasureSpec));
    }



关于super.onMeasure要不要调用的问题,看下ViewGroup的onMeasure都干了些什么

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),  
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));  
    }  

protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {  
        mMeasuredWidth = measuredWidth;  
        mMeasuredHeight = measuredHeight;  
  
        mPrivateFlags |= MEASURED_DIMENSION_SET;  
    }  

这里引用别人的总结:onMeasure默认的实现仅仅调用了setMeasuredDimension,setMeasuredDimension函数是一个很关键的函数,它对View的成员变量mMeasuredWidth和mMeasuredHeight变量赋值,而measure的主要目的就是对View树中的每个View的mMeasuredWidth和mMeasuredHeight进行赋值,一旦这两个变量被赋值,则意味着该View的测量工作结束。

也就是说,调用super.onMeasure只是简单设置了该自定义ViewGroup的宽和高,注意,并不依赖于子View。所以,当我们需要自己设定ViewGroup的宽高时,就不要调用这个方法了。


2.onLayout(摆放自己的childView)

这部分就是把所有的控件摆放位置了,一般的做法是遍历所有的child,计算出left,top,right和bottom的值,并调用

child.layout(l,t,r,b)完成摆放,就像这样:

@Override
  protected void onLayout(
                   boolean changed, int l, int t, int r, int b) {
    int count = getChildCount();
    int prevChildRight = 0;
    int prevChildBottom = 0;
    for (int i = 0; i < count; i++) {
      final View child = getChildAt(i);
      child.layout(prevChildRight, prevChildBottom, 
                   prevChildRight + child.getMeasuredWidth(), 
                   prevChildBottom + child.getMeasuredHeight());
      prevChildRight += child.getMeasuredWidth();
    }
  }


onLayout传进来的五个参数,第一个不管,后四个限制了该自定义ViewGroup的范围,也就是说,自定义ViewGroup就好像在屏幕上给你开辟了一块四边形,允许你摆放控件。像自动换行那种ViewGroup用的就是width累加是否超过r来判断的。


摆放子控件的过程中Margin可以通过MarginLayoutParams对象获得,不过使用MarginLayoutParams需要复写

public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs)
    {
        return new MarginLayoutParams(getContext(), attrs);
    }
否则报错。

Padding。。。


3.onMeasure和onLayout都是回调方法。。。。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值