一段自定义布局的源码分析

自定义布局效果如图所示:


MainActivity的布局文件如下所示:

<FrameLayoutxmlns:cascade="http://schemas.android.com/apk/res/com.manning.androidhacks.hack003"

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

   android:layout_width="fill_parent"

    android:layout_height="fill_parent">

   <com.manning.androidhacks.hack003.view.CascadeLayout

       android:layout_width="fill_parent"

       android:layout_height="fill_parent"

       cascade:horizontal_spacing="50dp"

       cascade:vertical_spacing="50dp" >

        <View

           android:layout_width="100dp"

           android:layout_height="150dp"

                             cascade:layout_vertical_spacing ="10dp"

           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" />

   </com.manning.androidhacks.hack003.view.CascadeLayout>

</FrameLayout>


通过布局文件可以看出,在自定义组建CascadeLayout中放了三个ImageView,每一个ImageView都设置了一个纯色的背景色。

既然CascadeLayout可以放置其它的视图组件,说明它必然是一个容器。该自定义容器有3个自定义的属性horizontal_spacing,vertical_spacing,layout_vertical_spacing。这3个属性定义在values/attrs.xml文件中:

<resources>

    <declare-styleablename="CascadeLayout">

        <attrname="horizontal_spacing" format="dimension" />

        <attrname="vertical_spacing" format="dimension" />

    </declare-styleable>

 

    <declare-styleablename="CascadeLayout_LayoutParams">

        <attrname="layout_vertical_spacing" format="dimension" />

    </declare-styleable>

</resources>


这三个属性的作用是:

horizontal_spacing和vertical_spacing用来设定自定义容器,兄弟视图之间的水平和垂直间距。如果一个容器中的视图使用了layout_vertical_spacing属性,则它与它后面的兄弟元素的垂直间距大小为layout_vertical_spacing而不是vertical_spacing了。就如本例中,3个视图组件,1组件设置了layout_vertical_spacing为10dp,因此1组件和2组件间的垂直距离就是10dp,而2组件和3组件之间的间距是vertical_spacing为50dp。1和2,2和3的水平间距均为horizantol_spacing,50dp。

接下来看一下CascadeLayout的源代码:

public classCascadeLayout extends ViewGroup {

  //用来存储兄弟视图间的水平间距

  private int mHorizontalSpacing;

  //用来存储兄弟视图间的垂直间距

  private int mVerticalSpacing;

 

  public CascadeLayout(Context context,AttributeSet attrs) {

    super(context, attrs);

    //通过TypedArray获取布局文件中为R.styleable.CascadeLayout中定义的属性所设定的数值

    //CascadeLayout是attrs.xml中一组styleable的name

    //因此返回的a相当于获得了name="CascadeLayout"组中声明的所有属性组成的一个“数组”

    //利用“下标”可以访问到这个“数组”中的内容,下标分别是“R.styleable.CascadeLayout_horizontal_spacing”和

   //“R.styleable.CascadeLayout_vertical_spacing”

    TypedArray a = context.obtainStyledAttributes(attrs,

        R.styleable.CascadeLayout);

 

    try {

      //mHorizontalSpacing的值为布局文件中为horizontal_spacing指定的值并转为px单位

      //如果没有设定R.styleable.CascadeLayout中定义的horizontal_spacing属性的值

      //则mHorizontalSpacing设定为R.dimen.cascade_horizontal_spacing定义的值

      mHorizontalSpacing =a.getDimensionPixelSize(

         R.styleable.CascadeLayout_horizontal_spacing,

          getResources().getDimensionPixelSize(

             R.dimen.cascade_horizontal_spacing));

      //mVerticalSpacing的值为布局文件中为vertical_spacing指定的值并转为px单位

      //如果没有设定R.styleable.CascadeLayout中定义的vertical_spacing属性的值

      //则mVerticalSpacing设定为R.dimen.cascade_vertical_spacing定义的值

      mVerticalSpacing =a.getDimensionPixelSize(

          R.styleable.CascadeLayout_vertical_spacing,getResources()

             .getDimensionPixelSize(R.dimen.cascade_vertical_spacing));

    } finally {

      //TypedArray使用完毕后,一定要回收            

      a.recycle();

    }

 

  }

  /**

   * onMeasure方法中测量设定整个布局的宽和高

   * 整体的宽为容器左内边距+子控件间的水平间距+最后一个子控件的宽度+容器右内边距

   * 整体的高为容器上内边距+子控件间的垂直间距+最后一个子控件的高度+容器下内边距

   */

  @Override

  protected void onMeasure(intwidthMeasureSpec, int heightMeasureSpec) {

              //自定义容器CascadeLayout的左内边距

    int width = getPaddingLeft();

    //自定义容器CascadeLayout的上内边距

    int height = getPaddingTop();

    int verticalSpacing;

 

    final int count = getChildCount();

    //这个循环中的代码对容器宽度和高度采用了不同的处理方式

    //个人认为没有必要,尤其是width += child.getMeasuredWidth();这行代码

    //只有在最后一次循环的时候才会起作用。其余的时候,上面代码设置的width的值都会在下一次循环开始的时候

    //被 width = getPaddingLeft() + mHorizontalSpacing * i;重新覆盖掉

    //循环结束时,宽度会在Left Padding的基础上增加每一个子控件间的水平间距和最后一个子控件的宽度

    //高度会在Top Padding的基础上增加每一个子控件的高度(最后一个子控件的高度是在循环结束后的外面才添加的)

    for (int i = 0; i < count; i++) {

      //先将vertiSpacing局部变量的值设定为mVerticalSpacing

      //随着代码的执行,如果这个子控件被指定了layout_vertical_spacing属性

      //则vertiSpacing局部变量的值设定为layout_vertical_spacing属性值

      verticalSpacing = mVerticalSpacing;

      //依次去获得容器中的每一个子控件

      View child = getChildAt(i);

      //调用measureChild方法,对该子控件进行测量,不然后面拿不到getMeasuredWidth的值,会返回0

      measureChild(child, widthMeasureSpec,heightMeasureSpec);

      //获得容器中子控件上被作为容器的父控件设定的LayouParrams,此时默认获得的是ViewGroup.LayoutParams

      //当容器的构造器调用的时候,LayoutParams就会被创建出来(通过generateLayoutParams)

      //LayoutParams的创建先于onMeasure方法的调用

      //这里将默认的ViewGroup.LayoutParams强制类型转化为CascadeLayout.LayoutParams

      LayoutParams lp = (LayoutParams)child.getLayoutParams();

      width = getPaddingLeft() +mHorizontalSpacing * i;

      //获得子视图的LayoutParams对象并强制转换为CascadeLayout.LayoutParams类型后,进行设置

      //设置好的值会在onLayout方法中被使用,每一个子控件就根据lp.x和lp.y的值进行摆放

      lp.x = width;

      lp.y = height;

      //接下来调整width和height的值,以便设定下一个子视图的起始位置(左上角位置)

      //如果在布局文件中设置了layout_vertical_spacing属性,

      //那么CascadeLayout.LayoutParams的verticalSpacing属性值就不为0

      //这里的源码如果给layout_vertical_spacing属性设置了负值,则该设置无效

      if (lp.verticalSpacing >= 0) {

        //此时将verticalSpacing属性的值设置为layout_vertical_spacing属性的值

        verticalSpacing = lp.verticalSpacing;

      }

      //这里修改的width值会在下一次循环开始的时候被覆盖掉。所以,这个width值只有在循环的最后一次才能起作用

      //换句话说,在循环的最后一次的时候,width的值加上了最后一个子视图的宽度值

      width += child.getMeasuredWidth();

      //加上子视图间的垂直间距

      height += verticalSpacing;

    }

    //循环结束后,将宽度的值加上容器CascadeLayout的右内边距

    width += getPaddingRight();

    //循环结束后,将高度的值再加上容器中最后一个子控件的高度再加上容器CascadeLayout下内边距

    height += getChildAt(getChildCount() -1).getMeasuredHeight()

        + getPaddingBottom();

    //利用resolveSize来综合考量CascadeLayout中子容器需要的尺寸与CascadeLayout父容器允许的CascadeLayout的尺寸

    //得到适当的值后,再进行setMeasuredDimension

    setMeasuredDimension(resolveSize(width,widthMeasureSpec),

        resolveSize(height,heightMeasureSpec));

  }

 

  @Override

  protected void onLayout(boolean changed, intl, int t, int r, int b) {

             

    final int count = getChildCount();

    //依次把容器中所有的子控件摆放到应该的位置(左上角的坐标为(lp.x,lp.y))

    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 booleancheckLayoutParams(ViewGroup.LayoutParams p) {

    return p instanceof LayoutParams;

  }

 

  @Override

  protected LayoutParams generateDefaultLayoutParams(){

    return newLayoutParams(LayoutParams.WRAP_CONTENT,

        LayoutParams.WRAP_CONTENT);

  }

 

  @Override

  public LayoutParamsgenerateLayoutParams(AttributeSet attrs) {

    return new LayoutParams(getContext(),attrs);

  }

 

  @Override

  protected LayoutParamsgenerateLayoutParams(ViewGroup.LayoutParams p) {

    return new LayoutParams(p.width, p.height);

  }

 

  public static class LayoutParams extendsViewGroup.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);

    }

  }

}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值