自定义ViewGroup(4)等分格子布局

来源:互联网 发布:转帖软件 编辑:程序博客网 时间:2024/06/05 16:48

继承自ViewGroup的自定义等分格子布局容器。

定义等分格子容器重要的步骤有两个,测量确定容器和子控件的宽度和高度由onMeasure方法完成。

摆放步骤有onLayout完成,等分格子布局需要指定列数,并根据容器宽度和列数计算出

格子宽度,再根据容器的高度和子元素需要摆放的行数计算出格子的高度,然后根据计算的结果,

在onLayout中摆放子控件。

第一步,定义容器的两个属性:子元素列数和元素之间的间隔

        <declare-styleable name="CustomViewGroupGridLayout"><attr name="numColumns" format="integer" /><attr name="itemMargin" format="integer" /></declare-styleable>

第二步,在布局文件中添加容器

        <com.twelve.custom.CustomViewGroupGridLayoutandroid:id="@+id/list"android:layout_width="fill_parent"android:layout_height="fill_parent"android:background="#1e1d1d"android:orientation="vertical"app:itemMargin="1"app:numColumns="4" ></com.twelve.custom.CustomViewGroupGridLayout>
注意,要在布局文件头部添加引用

xmlns:app="http://schemas.android.com/apk/res-auto"

第三步,初始化容器类CustomViewGroupGridLayout,获取自定义的属性值

    /**     * 构造方法:获取自定义属性值     * :View对象之间的距离     * :View对象列数     * @param context 上下文     * @param attrs 属性容器     * @param defStyle 未知     */public CustomViewGroupGridLayout(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);if (attrs != null) {            /**             * 获取自定义属性列表             */TypedArray a = getContext().obtainStyledAttributes(attrs,R.styleable.CustomViewGroupGridLayout);            int n = a.length();            for (int i = 0; i < n; i++) {                int attr = a.getIndex(i);                switch (attr) {                    case R.styleable.CustomViewGroupGridLayout_numColumns:                        /**                         * 获取给定的列数,默认值为2                         */                        mIntColumns = a.getInteger(R.styleable.CustomViewGroupGridLayout_numColumns, 2);                        break;                    case R.styleable.CustomViewGroupGridLayout_itemMargin:                        /**                         * 获取指定的距离,默认值为2                         */                        mIntMargin = a.getInteger(R.styleable.CustomViewGroupGridLayout_itemMargin, 2);                        break;                }            }}}public CustomViewGroupGridLayout(Context context, AttributeSet attrs) {this(context, attrs, 0);}public CustomViewGroupGridLayout(Context context) {this(context, null);}
第四步,设置子元素

    /**     * 从给定的View携带工具中提取View对象     * */public void setGridChildViewContainer(GridChildViewContainer container) {this.mGridChildViewContainer = container;        /**         * 提取View对象         */int size = container.getCount();for (int i = 0; i < size; i++) {addView(container.getView(i));}}
自定义一个GridChildViewContainer类,他负责携带一个View列表,通过getView(1)获取相对应的列表元素,添加到ViewGroup中。
第五步,测量和设置子元素高度和宽度,并获取容器的大小

        @Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        /**         * 获取ChildView的measureMode         */        int childModeWidth = 0, childModeHeight = 0;        if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED)            childModeWidth = MeasureSpec.UNSPECIFIED;        if (MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.UNSPECIFIED)            childModeHeight = MeasureSpec.UNSPECIFIED;        /**         * 生成子控件的childWidthMeasureSpec和childHeightMeasureSpec         * 这两个参数包含measureWidth&measureModeWidth和measureHeight&measureModeHeight         * 注意这里的measureWidth和measureHeight初始化值为0,也可以是其他的任意值         */        final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(0, childModeWidth);        final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, childModeHeight);        mIntViewCount = getChildCount();        if (mIntViewCount == 0) {            super.onMeasure(childWidthMeasureSpec, childHeightMeasureSpec);            return;        }        for (int i = 0; i < mIntViewCount; i++) {            final View child = getChildAt(i);            if (child.getVisibility() == GONE) {                continue;            }            /**             * 测量并设置子控件的宽度和高度             */            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);        }        /**         * 设置容器的高度和宽度         */        setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),MeasureSpec.getSize(heightMeasureSpec));        }
第六步,根据第五步的测量结果和第三步获取的列数和间隔值,确定子控件的摆放格局。

       /**        * 这里传入的四个参数l,t,r,b是onMeasure测量的容器的矩形区域        * (l,t)是容器的左上角起点坐标,(r,b)是容器的右下角终点坐标        */@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {        /**         * 布局区域高度         */int height = b - t;        /**         * 布局区域宽度         */int width =  r - l;        /**         * 计算控件摆放需要的行数         */int rows = mIntViewCount % mIntColumns == 0 ? mIntViewCount / mIntColumns                : mIntViewCount / mIntColumns + 1;if (mIntViewCount == 0)return;        /**         * 计算每个格子的宽度:除去间隔之后控件所占的净宽度         */int gridW = (width - mIntMargin * (mIntColumns - 1)) / mIntColumns;        /**         * 计算每个格子的高度:除去间隔之后控件所占的净高度         */int gridH = (height - mIntMargin * rows) / rows;        /**         * 开始摆放控件:初始化位置左边顶边界,上面留间隔         */int leftPointer = 0;int topPointer = mIntMargin;        /**         * 遍历行         */for (int row = 0; row < rows; row++) {            /**             * 遍历列             */for (int column = 0; column < mIntColumns; column++) {                /**                 * 获取某行某列的子元素                 */View child = this.getChildAt(row * mIntColumns + column);if (child == null)return;                /**                 * 左上角起点x坐标leftPointer由列宽gridW列序号和间隔数决定;                 * 左上角起点y坐标topPointer由行高gridH行序号和间隔数决定,                 * 在某一行中所有元素都是相同的;                 */leftPointer = column * gridW + column * mIntMargin;                /**                 * 如果当前布局宽度和测量宽度不一样,就直接用当前布局的宽度重新测量                 */if (gridW != child.getMeasuredWidth()|| gridH != child.getMeasuredHeight()) {child.measure(makeMeasureSpec(gridW, EXACTLY),makeMeasureSpec(gridH, EXACTLY));}                /**                 * 摆放子控件                 */child.layout(leftPointer, topPointer, leftPointer + gridW, topPointer + gridH);}            /**             * 一行遍历结束,行起点y坐标下移             */topPointer += gridH + mIntMargin;}}



0 0