创建定制的ViewGroup

来源:互联网 发布:linux最新内核编译 编辑:程序博客网 时间:2024/05/17 08:52

如需建如下的布局,我们应该如何创建这样的布局呢?

这里写图片描述

虽然使用margin属性便足以实现这种布局,XML布局文件源码如下:

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"                android:layout_width="fill_parent"                android:layout_height="fill_parent">    <View        android:layout_width="100dp"        android:layout_height="150dp"        android:background="#FF0000"/>    <View        android:layout_width="100dp"        android:layout_height="150dp"        android:layout_marginLeft="30dp"        android:layout_marginTop="20dp"        android:background="#00FF00"/>    <View        android:layout_width="100dp"        android:layout_height="150dp"        android:layout_marginLeft="60dp"        android:layout_marginTop="40dp"        android:background="#0000FF"/></RelativeLayout>

但我们可以通过创建自定义的ViewGroup来实现上述功能。该方法相对于在XML文件中手工指定margin值有如下优点:
1、在不同Activity中复用该视图时,更易维护。
2、开发者可以使用自定义属性来定制ViewGroup中子视图的位置。
3、布局文件更简明,更容易理解。
4、如果需要修改margin,不必重新手动计算每个子视图的margin。

理解Android绘制视图的方式

Android如何绘制一个布局:
”绘制布局由两个遍历过程组成:测量过程和布局过程。测量过程由measure(int, int)方法完成,该方法从上到下遍历视图树。在递归遍历过程中,每个视图都会向下层传递尺寸和规格。当measure方法遍历结束,每个视图都保存了各自的尺寸信息。第二个过程由layout(int, int, int, int)方法完成,该方法也是从上到下遍历视图树,在遍历过程中,每个父视图通过测量过程的结果定位所有子视图的位置信息“。

为了理解这个概念,下面分析ViewGroup的绘制过程。第一步是测量ViewGroup的宽度和高度。在onMeasure()方法中完成这步操作。在该方法中,ViewGroup通过遍历所有子视图计算出它的大小。最后一步操作,在onLayout()方法中完成,在该方法中,ViewGroup利用上一步计算出的测量信息,布局所有子视图。

创建自定义CascadeLayout

首先要定义CascadeLayout的定制属性,需要在res/values目录下创建一个属性文件attrs.xml,该文件的内容如下:

<?xml version="1.0" encoding="utf-8"?><resources>    <!--为CascadeLayout视图添加自定义属性。    declare-styleable中的name属性值必为自定义控件的类名(CascadeLayout),不然在布局文件中将无法使自定义控件的定制属性-->    <declare-styleable name="CascadeLayout">        <attr name="horizontal_spacing" format="dimension"/>        <attr name="vertical_spacing" format="dimension"/>    </declare-styleable>    <!--为CascadeLayout的子视图添加自定义属性。添加为特定子视图重写(override)垂直间距的方法,name属性值必为父控件类名_父控件的LayoutParams内部类(CascadeLayout_LayoutParams)-->    <declare-styleable name="CascadeLayout_LayoutParams">        <!--因为属性名的前缀是layout_,没有包含一个视图属性,因此该属性会被添加到LayoutParams的属性表中-->        <attr name="layout_vertical_spacing" format="dimension"/>    </declare-styleable></resources>

同时还需要指定水平间距和垂直间距的默认值,以便在未指定这些值时使用。把这些默认值保存在dimens.xml文件中,该文件同样位于res/values文件夹下。dimens.xml文件如下:

<resources>       <dimen name="cascade_horizontal_spacing">10dp</dimen>    <dimen name="cascade_vertical_spacing">10dp</dimen></resources>

创建CascadeLayout类

package com.example.huangfei.demo;import android.content.Context;import android.content.res.TypedArray;import android.util.AttributeSet;import android.view.View;import android.view.ViewGroup;/** * Created by huangfeihong on 2015/10/25. */public class CascadeLayout extends ViewGroup {    private int mHorizontalSpacing;//水平间距    private int mVerticalSpacing;//垂直间距    /**     * 当通过XML文件创建该视图的实例时会调用该构造函数     */    public CascadeLayout(Context context, AttributeSet attrs) {        super(context, attrs);        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CascadeLayout);        try {            /**             * mHorizontalSpacing和mVerticalSpacing由自定义属性中获取,如果其值未指定,就使用默认值             */            mHorizontalSpacing = a.getDimensionPixelSize(R.styleable.CascadeLayout_horizontal_spacing,                    getResources().getDimensionPixelSize(R.dimen.cascade_horizontal_spacing));            mVerticalSpacing = a.getDimensionPixelSize(R.styleable.CascadeLayout_vertical_spacing,                    getResources().getDimensionPixelSize(R.dimen.cascade_vertical_spacing));        } finally {            a.recycle();        }    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        //使用宽和高计算布局的最终大小以及子视图的x与y轴位置        int width = getPaddingLeft();        int height = getPaddingTop();        int verticalSpacing;//子视图垂直间距        int count = getChildCount();        for (int i = 0; i < count; i++) {            verticalSpacing = mVerticalSpacing;            View child = getChildAt(i);            //令每个子视图测量自身            measureChild(child, widthMeasureSpec, heightMeasureSpec);            LayoutParams lp = (LayoutParams) child.getLayoutParams();            width = getPaddingLeft() + mHorizontalSpacing * i;            //在LayoutParams中保存每个子视图的x和y坐标            lp.x = width;            lp.y = height;            if(lp.verticalSpacing >= 0){                verticalSpacing = lp.verticalSpacing;            }            width += child.getMeasuredWidth();            height += verticalSpacing;        }        width += getPaddingRight();        height += getChildAt(count - 1).getMeasuredHeight() + getPaddingBottom();        //使用计算所得的宽和高设置整个布局的测量尺寸        setMeasuredDimension(resolveSize(width, widthMeasureSpec), resolveSize(height, heightMeasureSpec));    }    /**     * 该方法以onMeasure()计算出的值为参数循环调用子View的layout()方法     */    @Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {        int count = getChildCount();        for (int i = 0; i < count; i++) {            View child = getChildAt(i);            LayoutParams lp = (LayoutParams) child.getLayoutParams();            child.layout(lp.x, lp.y, lp.x + child.getMeasuredWidth(), lp.y + child.getMeasuredHeight());        }    }    /**     * 要使自新定义的CascadeLayout.LayoutParams类,必须重写以下四个方法。     * 这些方法的代码在不同ViewGroup之间往往是相同的。     */    @Override    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {        return p instanceof LayoutParams;    }    @Override    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {        return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);    }    @Override    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {        return new LayoutParams(getContext(), attrs);    }    @Override    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {        return new LayoutParams(p.width, p.height);    }    /**     * 该类用于保存每个子视图的x、y轴位置     */    public static class LayoutParams extends ViewGroup.LayoutParams {        int x;        int y;        int verticalSpacing;//子视图自定义垂直间距        public LayoutParams(Context c, AttributeSet attrs) {            super(c, attrs);            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.CascadeLayout_LayoutParams);            try {                verticalSpacing = a.getDimensionPixelSize(                        R.styleable.CascadeLayout_LayoutParams_layout_vertical_spacing, -1);            } finally {                a.recycle();            }        }        public LayoutParams(int w, int h) {            super(w, h);        }    }}

创建Activity的布局文件

<?xml version="1.0" encoding="utf-8"?><!--在XML中使用自定义属性时指定自定义命名空间--><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"             xmlns:cascade="http://schemas.android.com/apk/res-auto"             android:layout_width="fill_parent"             android:layout_height="fill_parent">    <!--通过cascade命名空间,就可以使用其自定义属性-->    <com.example.huangfei.demo.CascadeLayout        android:layout_width="fill_parent"        android:layout_height="fill_parent"        cascade:horizontal_spacing="50dp"        cascade:vertical_spacing="20dp">        <!--父视图添加自定义属性-->        <View            android:layout_width="100dp"            android:layout_height="150dp"            android:background="#FF0000"            cascade:layout_vertical_spacing="90dp"/>        <!--子视图添加自定义属性-->        <View            android:layout_width="100dp"            android:layout_height="150dp"            android:background="#00FF00"            cascade:layout_vertical_spacing="50dp"/>        <View            android:layout_width="100dp"            android:layout_height="150dp"            android:background="#0000FF"/>    </com.example.huangfei.demo.CascadeLayout></FrameLayout>

代码地址

0 0
原创粉丝点击