ViewGroup 自定义演示

来源:互联网 发布:页面加载执行js方法 编辑:程序博客网 时间:2024/06/06 02:30

第一部分:利用系统属性自定义ViewGroup

1、ViewGroup的职责是啥?
ViewGroup相当于一个放置View的容器,ViewGroup的职能为:给childView计算出建议的宽和高和测量模式 ;决定childView的位置;为什么只是建议的宽和高,而不是直接确定呢,因为childView宽和高可以设置为wrap_content,这样只有childView才能计算出自己的宽和高。
2、View的职责是啥?
View的职责,根据测量模式和ViewGroup给出的建议的宽和高,计算出自己的宽和高;同时还有个更重要的职责是:在ViewGroup为其指定的区域内绘制自己的形态。
总结: 根据ViewGroup传人的测量值和模式,View对自己宽高进行确定(onMeasure中完成),然后在onDraw中完成对自己的绘制。
ViewGroup需要给View传入view的测量值和模式(onMeasure中完成),而且对于此ViewGroup的父布局,自己也需要在onMeasure中完成对自己宽和高的确定。此外,需要在onLayout中完成对其childView的位置的指定。
举例1: 定义一个ViewGroup,内部可以传入0到4个childView,分别依次显示在左上角,右上角,左下角,右下角。利用系统的 MarginLayoutParams,因为只需要ViewGroup能够支持margin即可

public class ViewGroupTest1 extends ViewGroup {    public ViewGroupTest1(Context context) {        super(context);    }    public ViewGroupTest1(Context context, AttributeSet attrs) {        super(context, attrs);    }    public ViewGroupTest1(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    //该方法是用来设置ViewGroup 布局参数  指定了其LayoutParams为MarginLayoutParams    @Override    public LayoutParams generateLayoutParams(AttributeSet attrs) {        return new MarginLayoutParams(getContext(),attrs);    }    @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);        //对子view 进行测量        measureChildren(widthMeasureSpec,heightMeasureSpec);        /**         * 如果ViewGroup是wrap_content时,需要对ViewGroup采用自定义的测试方式进行测量它的宽和高         */        int width = 0;        int height = 0;        int cWidth = 0;        int cHeight = 0;        MarginLayoutParams cParams = null;        // 用于计算左边两个childView的高度        int lHeight = 0;        // 用于计算右边两个childView的高度,最终高度取二者之间大值        int rHeight = 0;        // 用于计算上边两个childView的宽度        int tWidth = 0;        // 用于计算下面两个childiew的宽度,最终宽度取二者之间大值        int bWidth = 0;        int count = getChildCount();        for (int i=0;i<count;i++){            View viewChildren = getChildAt(i);            cWidth = viewChildren.getMeasuredWidth();            cHeight = viewChildren.getMeasuredHeight();            cParams = (MarginLayoutParams) viewChildren.getLayoutParams();            // 上面两个childView            if (i == 0 || i == 1)            {                tWidth += cWidth + cParams.leftMargin + cParams.rightMargin;            }            if (i == 2 || i == 3)            {                bWidth += cWidth + cParams.leftMargin + cParams.rightMargin;            }            if (i == 0 || i == 2)            {                lHeight += cHeight + cParams.topMargin + cParams.bottomMargin;            }            if (i == 1 || i == 3)            {                rHeight += cHeight + cParams.topMargin + cParams.bottomMargin;            }        }        width = Math.max(tWidth, bWidth);        height = Math.max(lHeight, rHeight);        boolean isWidthExActly = widthMode == MeasureSpec.EXACTLY;        boolean isHeightWxactly = heightMode == MeasureSpec.EXACTLY;        Log.i("niuniu " , " widthMode  " + isWidthExActly + "  widthSize " + widthSize + "   width " +width);        Log.i("niuniu " , " heightMode  " + isHeightWxactly + "  heightSize " + heightSize + "   height " +height);        //将得到的宽高 通过setMeasuredDimension方法设置进去,完成测量工作.        setMeasuredDimension(widthMode == MeasureSpec.EXACTLY? widthSize:width,heightMode == MeasureSpec.EXACTLY?heightSize:height);    }    @Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {        int cCount = getChildCount();        int cWidth = 0;        int cHeight = 0;        MarginLayoutParams cParams = null;        /**         * 根据childView的宽和高,以及margin,计算childView在GruopView的位置(l, t, r, b) 并使用layout进行布局         */        for (int i = 0; i < cCount; i++)        {            View childView = getChildAt(i);            cWidth = childView.getMeasuredWidth();            cHeight = childView.getMeasuredHeight();            cParams = (MarginLayoutParams) childView.getLayoutParams();//            Log.i("niuniu", " cParams.leftMargin :" +cParams.leftMargin + " cParams.topMargin " +//                    cParams.topMargin + "  cParams.rightMargin:  " +cParams.rightMargin + "  cParams.bottomMargin  " + cParams.bottomMargin);            int cl = 0, ct = 0, cr = 0, cb = 0;            switch (i)            {                case 0:                    cl = cParams.leftMargin;                    ct = cParams.topMargin;                    break;                case 1:                    cl = getWidth() - cWidth - cParams.rightMargin;                    ct = cParams.topMargin;                    break;                case 2:                    cl = cParams.leftMargin;                    ct = getHeight() - cHeight - cParams.bottomMargin;                    break;                case 3:                    cl = getWidth() - cWidth- cParams.rightMargin;                    ct = getHeight() - cHeight - cParams.bottomMargin;                    break;            }            cr = cl + cWidth;            cb = cHeight + ct;            childView.layout(cl, ct, cr, cb);        }    }}//activity_main.xml 中引用 <com.example.nft.myapplication.ViewGroupTest1        android:layout_width="match_parent"        android:layout_height="500dp"        android:id="@+id/viewGruop1"        android:layout_marginTop="15dp">        <TextView            android:layout_width="200dp"            android:layout_height="200dp"            android:layout_marginLeft="20dp"            android:layout_marginBottom="800dp"            android:textSize="50dp"            android:text="1"            android:background="#FF4444"            android:gravity="center"            android:textStyle="bold"/>        <TextView            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_marginTop="50dp"            android:textSize="50dp"            android:text="2"            android:background="#00ff00"            android:gravity="center"            android:textStyle="bold"/>        <TextView            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_margin="45dp"            android:textSize="50dp"            android:text="3"            android:background="#0044ff"            android:gravity="center"            android:textStyle="bold"/>        <TextView            android:layout_width="100dp"            android:layout_height="100dp"            android:layout_marginHorizontal="50dp"            android:textSize="50dp"            android:text="4"            android:background="#ff6600"            android:gravity="center"            android:textStyle="bold"/>    </com.example.nft.myapplication.ViewGroupTest1>

对子view进行测量 也可以使用measureChild方法

  int count = getChildCount();  for (int i = 0; i < count; i++) {    final View child = getChildAt(i);    measureChild(child, widthMeasureSpec, heightMeasureSpec);  }

而使用measureChildren() 方法来简化上面的代码,这个方法将自动遍历所有子view并让它们测量自己,还可以忽略那些visibility 设置为gone的子view,因此它支持visibility gone标志.

第二部分 自定义ViewGroup 定义自己的属性

当使用不同的布局方式时,子view得布局属性就不太一样,比如当父布局是LinearLayout时,子view可以使用父布局属性如layout_weight、weightSum、layout_gravity等;当使用的是RelativeLayout时,其子view就能使用属于父布局的有效属性layout_centerInParent等;因此不同的布局容器,有不同的布局属性, 当需要我们的自定义容器需要定义自己的布局属性时,就必须使用LayoutParams来实现.
先来简单看看viewGroup的addView方法

public void addView(View child, int index) {        ...        LayoutParams params = child.getLayoutParams();        if (params == null) {            params = generateDefaultLayoutParams();         ....        }        addView(child, index, params);    } public void addView(View child, int index, LayoutParams params) {     ...    addViewInner(child, index, params, false);}private void addViewInner(View child, int index, LayoutParams params,        boolean preventRequestLayout) {  ...    if (!checkLayoutParams(params)) {        params = generateLayoutParams(params);    }    if (preventRequestLayout) {        child.mLayoutParams = params;    } else {        child.setLayoutParams(params);    }}

首先是checkLayoutParams,目的是检测这个参数是否为空,如果为空的话就给它生成一个普通的LayoutParams; 实现布局参数转换成自定义的参数,如下三个方法就显得尤为重要了。

    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {        return  p != null;    }  protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {      return p;  }  public LayoutParams generateLayoutParams(AttributeSet attrs) {      return new LayoutParams(getContext(), attrs);  }

比如 FrameLayout.LayoutParams中就自定义了一个Gravity属性,FrameLayout实现了addView的这三个方法

public static class LayoutParams extends MarginLayoutParams {    public int gravity = -1;    public LayoutParams(Context c, AttributeSet attrs) {        super(c, attrs);        TypedArray a = c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.FrameLayout_Layout);        gravity = a.getInt(com.android.internal.R.styleable.FrameLayout_Layout_layout_gravity, -1);        a.recycle();    }    public LayoutParams(int width, int height) {        super(width, height);    }    public LayoutParams(int width, int height, int gravity) {        super(width, height);        this.gravity = gravity;    }    ....    public LayoutParams(LayoutParams source) {        super(source);        this.gravity = source.gravity;    }}
    @Override    public LayoutParams generateLayoutParams(AttributeSet attrs) {        return new FrameLayout.LayoutParams(getContext(), attrs);            }    @Override    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {        return p instanceof LayoutParams;    }    @Override    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {        return new LayoutParams(p);    }

举例2 自定义Group中添加layout_bg,layout_orientation 属性,供子view来使用.

public class ViewGroupTest2 extends ViewGroup {    private int orientation = 0;    public ViewGroupTest2(Context context) {        super(context);    }    public ViewGroupTest2(Context context, AttributeSet attrs) {        super(context, attrs);        TypedArray typedArray  = context.obtainStyledAttributes(attrs,R.styleable.ViewGroupTest2);        orientation = typedArray.getInt(R.styleable.ViewGroupTest2_layout_orientation,0);        typedArray.recycle();    }    public ViewGroupTest2(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        measureChildren(widthMeasureSpec,heightMeasureSpec);        int widthMode = MeasureSpec.getMode(widthMeasureSpec);        int widthSize = MeasureSpec.getSize(widthMeasureSpec);        int heightMode = MeasureSpec.getMode(heightMeasureSpec);        int heightSize = MeasureSpec.getSize(heightMeasureSpec);        int width;        int height;        if(widthMode == MeasureSpec.EXACTLY){            width = widthSize;        } else{            width = widthSize+500;        }        if (heightMode == MeasureSpec.EXACTLY){            height = heightSize;        }else {            height = heightSize+600;        }        setMeasuredDimension(width,height);    }    @Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {        int count = getChildCount();        int width ;        int height ;        int distance = 0;        MyLayoutParams params = null;        for (int i = 0;i<count;i++){             View view = getChildAt(i);             params = (MyLayoutParams) view.getLayoutParams();             view.setBackgroundColor(params.color);             width = view.getMeasuredWidth();             height = view.getMeasuredHeight();            int cl = 0, ct = 0, cr = 0, cb = 0;            if(orientation == 0){ // 水平一次排列                cl = distance;                ct = 80;            } else { //垂直依次排列                ct = distance;                cl = 80;            }            cr = cl + width;            cb = height + ct;            view.layout(cl, ct, cr, cb);            // 计算下一个子view的左边距离 或者顶部距离            if (orientation == 0){                distance += width +20;            } else {                distance += height+40;            }        }    }    @Override    public LayoutParams generateLayoutParams(AttributeSet attrs) {        Log.i("niuniu", " generateLayoutParams attrs ");        return new MyLayoutParams(getContext(),attrs);    }    @Override    protected LayoutParams generateLayoutParams(LayoutParams p) {        Log.i("niuniu", " generateLayoutParams  p ");        return new MyLayoutParams(p);    }    @Override    protected boolean checkLayoutParams(LayoutParams p) {        boolean params  = p instanceof  LayoutParams;        Log.i("niuniu", " checkLayoutParams  params " + params);        return params;    }}//创建自己的LayoutParams 并获取父容器所支持的属性public class MyLayoutParams extends ViewGroup.LayoutParams {    public int color ;    public MyLayoutParams(Context c, AttributeSet attrs) {        super(c, attrs);        Log.i("niuniu", " MyLayoutParams attrs ");        TypedArray ta = c.obtainStyledAttributes(attrs,R.styleable.MyParams);        color = ta.getColor(R.styleable.MyParams_layout_bg, Color.DKGRAY);        ta.recycle();    }    public MyLayoutParams(int width, int height) {        super(width, height);    }    public MyLayoutParams(ViewGroup.LayoutParams source) {        super(source);    }    public MyLayoutParams(MyLayoutParams source) {        super(source);        Log.i("niuniu", " MyLayoutParams source ");        this.color = source.color;    }}activity_main.xml中引入该父容器     <com.example.nft.myapplication.ViewGroupTest2        xmlns:viewGroupTest2 = "http://schemas.android.com/apk/res/com.example.nft.myapplication"        android:layout_width="wrap_content"        android:layout_height="800dp"        android:id="@+id/viewGroup2"        android:layout_marginTop="15dp"        viewGroupTest2:layout_orientation = "horital"        >        <TextView            android:layout_width="50dp"            android:layout_height="50dp"            android:textStyle="bold"            android:text="1"            android:textSize="24dp"            viewGroupTest2:layout_bg="#FF4444"/>        <TextView            android:layout_width="100dp"            android:layout_height="100dp"            android:textStyle="bold"            android:text="2"            android:textSize="24dp"            viewGroupTest2:layout_bg="#bb6cc0"/>        <TextView            android:layout_width="50dp"            android:layout_height="50dp"            android:textStyle="bold"            android:text="3"            android:textSize="24dp"            viewGroupTest2:layout_bg="#66ff00"/>        <TextView            android:layout_width="100dp"            android:layout_height="100dp"            android:textStyle="bold"            android:text="4"            android:textSize="24dp"            viewGroupTest2:layout_bg="#6f60f0"/>    </com.example.nft.myapplication.ViewGroupTest2>

log 输出:
generateLayoutParams attrs ;
MyLayoutParams attrs ;
checkLayoutParams params true
总结 :
在xml中引入这个ViewGroupTest2 布局,会调用public 的generateLayoutParams(atters)方法来给子view生成自定义的布局参数MyLayoutParam.

原创粉丝点击