android 开发技巧(3)--创建定制的 ViewGroup

来源:互联网 发布:网络大专报名条件 编辑:程序博客网 时间:2024/06/06 19:36

在项目中,有的时候需要用到自定义的ViewGroup
一、绘制过程

绘制布局由两个遍历过程组成:测量过程和布局过程。测
量过程由 measure(int, int) 方法完成,该方法从上到下遍历视图
树。在递归遍历过程中,每个视图都会向下层传递尺寸和规格。当
measure 方法遍历结束,每个视图都保存了各自的尺寸信息。第二
个过程由 layout(int, int, int,int) 方法完成,该方法也是由上而下遍
历视图树,在遍历过程中,每个父视图通过测量过程的结果定位所
有子视图的位置信息。”
第一步是测量 ViewGroup 的宽度和高度,在 onMeasure() 方法中完成这
步操作。在该方法中, ViewGroup 通过遍历所有子视图计算出它
的大小。在 onLayout() 方法中完成,在该方法中,
ViewGroup 利用上一步计算出的测量信息,布局所有子视图。

二、ViewGroup和LayoutParams

每个ViewGroup需要指定一个LayoutParams,用于确定支持childView支持哪些属性,比如LinearLayout指定LinearLayout.LayoutParams等在编写 onMeasure() 方法之前,先创建自定义 LayoutParams类,该类用于保存每个子视图的 xy 轴位置。把 LayoutParams 定义为 的内部类

三、View的3种测量模式

EXACTLY:表示设置了精确的值,一般当childView设置其宽、高为精确值、match_parent时,ViewGroup会将其设置为EXACTLY;
AT_MOST:表示子布局被限制在一个最大值内,一般当childView设置其宽、高为wrap_content时,ViewGroup会将其设置为AT_MOST;
UNSPECIFIED:表示子布局想要多大就多大,一般出现在AadapterView的item的heightMode中、ScrollView的childView的heightMode中;

四、自定义ViewGroup需要实现的方法

用新定义的 ViewGroup中的LayoutParams 类,还需要重
写 CascadeLayout 类中的其他一些方法。这些方法是 checkLayoutParams()、 generateDefaultLayoutParams()、 generateLayoutPar
ams(AttributeSetattrs) 和 generateLayoutParams(ViewGroup.
LayoutParams p)。这些方法的代码在不同 ViewGroup 之间往往是
相同的。

效果图
这里写图片描述
主界面java代码就不贴了,只是引用xml而已
主界面布局activity_hack03.xml

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:cascade="http://schemas.android.com/apk/res-auto"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:paddingBottom="@dimen/activity_vertical_margin"    android:paddingLeft="@dimen/activity_horizontal_margin"    android:paddingRight="@dimen/activity_horizontal_margin"    android:paddingTop="@dimen/activity_vertical_margin"    tools:context="boerpower.com.android50hacks.Hack03Activity">    <boerpower.com.android50hacks.CascadeLayout        android:layout_width="fill_parent"        android:layout_height="fill_parent"        cascade:horizontal_spacing="30dp"        cascade:vertical_spacing="20dp">        <View            android:layout_width="100dp"            android:layout_height="150dp"            android:background="#FF0000" />        <View            android:layout_width="100dp"            android:layout_height="150dp"            android:background="#00FF00" />        <View            android:layout_width="100dp"            android:layout_height="150dp"            android:background="#0000FF" />    </boerpower.com.android50hacks.CascadeLayout></RelativeLayout>

自定义ViewGroup类:CascadeLayout.java

public class CascadeLayout extends ViewGroup {  private int mHorizontalSpacing;  private int mVerticalSpacing;  public CascadeLayout(Context context, AttributeSet attrs) {//当通过XML文件创建该视图的实例时会调用构造函数    super(context, attrs);    TypedArray a = context.obtainStyledAttributes(attrs,        R.styleable.CascadeLayout);//mHorizontalSpacing和mVerticalSpacing由自定义属性中获取,如果其值未指定,就使用默认值    try {      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;    final 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(getChildCount() - 1).getMeasuredHeight()        + getPaddingBottom();//在 LayoutParams中保存每个子视图的 x 和 y 坐标使用计算所得的宽和高设置整个布局的 测 量尺寸    setMeasuredDimension(resolveSize(width, widthMeasureSpec),        resolveSize(height, heightMeasureSpec));  }  @Override  protected void onLayout(boolean changed, int l, int t, int r, int b) {    final 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());    }  }  @Override  protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {    return p instanceof LayoutParams;  }  @Override  protected LayoutParams generateDefaultLayoutParams() {    return new LayoutParams(LayoutParams.WRAP_CONTENT,        LayoutParams.WRAP_CONTENT);  }  @Override  public LayoutParams generateLayoutParams(AttributeSet attrs) {    return new LayoutParams(getContext(), attrs);  }  @Override  protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {    return new LayoutParams(p.width, p.height);  }  public static class LayoutParams extends ViewGroup.LayoutParams {    int x;    int y;    public int verticalSpacing;    public LayoutParams(Context context, AttributeSet attrs) {      super(context, attrs);      TypedArray a = context.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);    }  }}

定义那些定制的属性,在res/values 目录下创建一个属性文件 attrs.xml

<?xml version="1.0" encoding="utf-8"?><resources>    <declare-styleable name="CascadeLayout">        <attr name="horizontal_spacing" format="dimension" />        <attr name="vertical_spacing" format="dimension" />    </declare-styleable>    <declare-styleable name="CascadeLayout_LayoutParams">        <attr name="layout_vertical_spacing" format="dimension" />    </declare-styleable></resources>

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

<resources>    <!-- Default screen margins, per the Android Design guidelines. -->    <dimen name="activity_horizontal_margin">16dp</dimen>    <dimen name="activity_vertical_margin">16dp</dimen>    <dimen name="cascade_horizontal_spacing">10dp</dimen>    <dimen name="cascade_vertical_spacing">10dp</dimen></resources>

参考资料
Android 手把手教您自定ViewGroup
Android 自定义ViewGroup 实战篇 -> 实现FlowLayout

0 0
原创粉丝点击