Android 自定义ViewGroup

来源:互联网 发布:卖家淘宝客佣金链接 编辑:程序博客网 时间:2024/05/18 02:56

上一篇学习了Android自定义View的流程及步骤,这篇学习下如何去自定义一个ViewGroup,我们知道ViewGroup就是View的容器类,它内部包含了许多个控件,即一组view,而我们经常用的LinearLayout,RelativeLayout等都是ViewGroup的子类 , 所以它的整个绘制过程相对于View会复杂一点,但还是三个步骤measure,layout,draw。

构造函数

 public MyLinearLayout(Context context) {        this(context, null);    }    public MyLinearLayout(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public MyLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }  }

代码中三个构造方法的调用时机:
在代码中直接new一个MyLinearLayout实例的时候,会调用第一个构造函数.
在xml布局文件中调用MyLinearLayout的时候,会调用第二个构造函数.
在xml布局文件中调用MyLinearLayout,并且MyLinearLayout标签中还有自定义属性时,这里调用的还是第二个构造函数.
也就是说,系统默认只会调用MyLinearLayout的前两个构造函数,至于第三个构造函数的调用,通常是我们自己在构造函数中主动调用的(例如上面代码中, 第一个和第二个构造函数通过this最终都会调用到第三个构造函数).

Measure

上一篇中我们提到了SpecMode三种模式,这里也是一样,如果layout_widht和layout_height是match_parent或具体的值时,就直接调用setMeasuredDimension()方法即可,如果layout_widht和layout_height是wrap_content时,这时我们需要遍历所有的子View,然后对每个子View进行测量,计算出最终ViewGroup的大小,(UNSPECIFIED 这个模式主要用于系统内部多次Measure的情形,一般来说用不到这个模式,不需要关注),

@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  int childCount = this.getChildCount();  for (int i = 0; i < childCount; i++) {      View child = getChildAt(i);      measureChild(child, widthMeasureSpec, heightMeasureSpec);      int childWidth= child.getMeasuredWidth();      int childHeight = child.getMeasuredHeight();  }}

上面代码中getChildCount()方法是获取子View的数量,measureChild()方法,是调用子View的测量方法,从而通过child.getMeasuredWidth(),child.getMeasuredHeight()获取每个子view的宽高.

Layout

layout过程其实就是对子View的位置进行排列.

@Overrideprotected void onLayout(boolean arg0, int arg1, int arg2, int arg3, int arg4) {  int childCount = this.getChildCount();  for (int i = 0; i < childCount; i++) {      View child = this.getChildAt(i);      LayoutParams lParams = (LayoutParams) child.getLayoutParams();      child.layout(lParams.left, lParams.top, lParams.left + childWidth,              lParams.top + childHeight);  }}

上面代码中child.layout(left,top,right,bottom)方法就是对每个子View的位置进行设置.

Draw

通常情况下, 我们一般不需要像自定义View一样重写onDraw() , 所以在这也不再介绍.

LayoutParams

通常情况下,我们需要让子view支持margin属性,如下:

 <cn.lw.view.MyLinearLayout        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:background="@color/colorAccent">        <Button            android:layout_width="200dp"            android:layout_height="50dp"            android:background="#ffffff"            android:layout_margin="10dp"            android:text="button1"/></cn.lw.view.MyLinearLayout>

然后在onMeasure方法中,让viewGroup每个子view的高度都加上设置了margin的高度,代码如下:

 @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        int widthMode = MeasureSpec.getMode(widthMeasureSpec);        int widthSize = MeasureSpec.getSize(widthMeasureSpec);        int heightMode = MeasureSpec.getMode(heightMeasureSpec);        int heightSize = MeasureSpec.getSize(heightMeasureSpec);        int lineHeight = 0;        int n = getChildCount();        for (int i = 0; i < n; i++) {            View view = getChildAt(i);            measureChild(view, widthMeasureSpec, heightMeasureSpec);            MarginLayoutParams p = (MarginLayoutParams) view.getLayoutParams();            int childheight = view.getMeasuredHeight() + p.topMargin + p.bottomMargin;            lineHeight += childheight;        }        if (n == 0) {            setMeasuredDimension(0, 0);        } else {            setMeasuredDimension(widthSize,                    heightMode == MeasureSpec.EXACTLY ? heightSize : lineHeight);        }    }

这里,需要注意的是 MarginLayoutParams p = (MarginLayoutParams) view.getLayoutParams();如果不重写LayoutParams相关的代码,这样直接转换会出现问题。所以,我们需要重写如下代码:让它返回MarginLayoutParams类型的对象:

   /**     *  获取布局文件中的布局参数     */    @Override    public LayoutParams generateLayoutParams(AttributeSet attrs) {        return new MarginLayoutParams(getContext(),attrs);    }    /**     *  获取默认的布局参数     */    @Override    protected LayoutParams generateDefaultLayoutParams() {        return new MarginLayoutParams(LayoutParams.MATCH_PARENT,                LayoutParams.MATCH_PARENT);    }    /**     *  生成自己的布局参数     */    @Override    protected LayoutParams generateLayoutParams(LayoutParams p) {        return new MarginLayoutParams(p);    }

简单的viewGroup例子:

public class MyLinearLayout extends ViewGroup {    public MyLinearLayout(Context context) {        this(context, null);    }    public MyLinearLayout(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public MyLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    /**     *  获取布局文件中的布局参数     */    @Override    public LayoutParams generateLayoutParams(AttributeSet attrs) {        return new MarginLayoutParams(getContext(),attrs);    }    /**     *  获取默认的布局参数     */    @Override    protected LayoutParams generateDefaultLayoutParams() {        return new MarginLayoutParams(LayoutParams.MATCH_PARENT,                LayoutParams.MATCH_PARENT);    }    /**     *  生成自己的布局参数     */    @Override    protected LayoutParams generateLayoutParams(LayoutParams p) {        return new MarginLayoutParams(p);    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        int widthMode = MeasureSpec.getMode(widthMeasureSpec);        int widthSize = MeasureSpec.getSize(widthMeasureSpec);        int heightMode = MeasureSpec.getMode(heightMeasureSpec);        int heightSize = MeasureSpec.getSize(heightMeasureSpec);        int lineHeight = 0;        int n = getChildCount();        for (int i = 0; i < n; i++) {            View view = getChildAt(i);            measureChild(view, widthMeasureSpec, heightMeasureSpec);            MarginLayoutParams p = (MarginLayoutParams) view.getLayoutParams();            int childheight = view.getMeasuredHeight() + p.topMargin + p.bottomMargin;            lineHeight += childheight;        }        if (n == 0) {            setMeasuredDimension(0, 0);        } else {            setMeasuredDimension(widthSize,                    heightMode == MeasureSpec.EXACTLY ? heightSize : lineHeight);        }    }    @Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {        int mWidth = getWidth();        int count = getChildCount();        int lineHeight = 0;        for (int i = 0; i < count; i++) {            View child = getChildAt(i);            MarginLayoutParams p = (MarginLayoutParams) child.getLayoutParams();            int left = (mWidth / 2) - (child.getMeasuredWidth() / 2);            int lc = left;            int tc = p.topMargin + lineHeight;            int rc = child.getMeasuredWidth() + left;            int bc = tc + child.getMeasuredHeight();            child.layout(lc, tc, rc, bc);            lineHeight += child.getMeasuredHeight() + p.bottomMargin + p.topMargin;        }    }}

上面例子很简单,就是让子view垂直并且居中排列,主要通过onMeasure方法遍历所有view算出viewGroup的高度,然后在layout中通过child.layout(lc, tc, rc, bc)方法对每个子view进行布局,效果图如下:
这里写图片描述

完整代码链接:https://github.com/ivluowei/ViewTest

原创粉丝点击