zoukankan      html  css  js  c++  java
  • 50个Android开发技巧(03 自己定义ViewGroup)

    问题:怎样创建一个例如以下图所看到的的布局?

                   图1
    (原文地址:http://blog.csdn.net/vector_yi/article/details/24415537)
     你可能会说,利用RelativeLayout和margins就能够实现。的确,例如以下XML代码能够简单地构建一个类似的布局:
    <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>
    
    效果如图2:

                 图2
    可是当遇到复杂、要求可变的类似布局时,利用margins可能就会显得操作非常繁杂。
    在此,我们来看还有一种创建类似上图布局的方式---自己定义ViewGroup
    优点有下面几点:
    • 当你将这个布局应用到不同Activity中时更加easy维护
    • 能够利用自己定义属性来自己定义ViewGroup中的每一个子View
    • 更加简洁可读的XML文件内容
    • 假设须要改变margin的时候。不须要手动的去计算每一个子View的margin
    一、理解Android绘制一个View的步骤
         关于绘制View的步骤。能够參见Android官方文档:http://developer.android.com/guide/topics/ui/how-android-draws.html
            在此,我们重点来关注ViewGroup的绘制过程:
              1.处理ViewGroup的width和height.
                 处理width及height的操作在onMeasure()方法中进行,在此方法内。ViewGroup会依据它的子View来计算自身所占用的布局空间。
              2.布局到页面上
                 这点操作在onLayout()方法中进行,在此方法中,ViewGroup会依据从onMeasure()中得到的信息将其每个子View绘制出来。


    二、构建CascadeLayout类
             首先在XML布局文件里加入CascadeLayout:
    <FrameLayout 
        <!--自己定义命名空间,以便在下文中使用自己定义的属性-->
        xmlns: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 ="30dp"<!--由于前面加入了cascade命名空间,所以此处能够使用自己定义属性-->
            cascade:vertical_spacing ="20dp" >
    
            <View
                android:layout_width ="100dp"
                android:layout_height ="150dp"
                cascade:layout_vertical_spacing ="90dp"<!--为子View加入的自己定义属性,将在本文第三部分用到-->
                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>
    
    要使用这些自己定义的属性,我们必需要定义它。

         在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>
    </resources>
    
    然后。当我们在创建CascadeLayout且没有为其指定horizontal_spacing与vertical_spacing时,须要有一个默认值。

    我们将这个默认值预先定义好并存放在res/values目录下的dimens.xml中:
    <?

    xml version ="1.0" encoding= "utf-8" ?

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

    最后,我们须要创建一个名为CascadeLayout的Java类。它继承了ViewGroup并重写了onMeasure()与OnLayout()方法。
    1.CascadeLayout的构造函数
    public CascadeLayout (Context context, AttributeSet attrs) {
        super( context, attrs);
    
        TypedArray a = context .obtainStyledAttributes (attrs ,
            R. styleable. CascadeLayout );
    
        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 ();
        }
    
    2.构建自己定义的LayoutParams类
         LayoutParams类将作为CascadeLayout的内部类存在,它将存储每一个子View的x。y坐标。定义例如以下:
    public static class LayoutParams extends ViewGroup .LayoutParams {
        int x;
        int y;
    
        public LayoutParams( Context context , AttributeSet attrs) {
          super (context , attrs );
        }
    
        public LayoutParams( int w , int h ) {
          super (w , h );
        }
    
      }
    

    3.重写onMeasure()方法
         onMeasure()方法将是CascadeLayout类中最关键的部分,这种方法不仅计算整个ViewGroup所占用的布局空间。还将计算出每一个子View所占用的布局空间。
    @Override
      protected void onMeasure (int widthMeasureSpec , int heightMeasureSpec ) {
        int width = 0;
        int height = getPaddingTop ();
    
        final int count = getChildCount ();
        for ( int i = 0; i < count; i++) {
          View child = getChildAt (i );
          measureChild (child , widthMeasureSpec , heightMeasureSpec );
           LayoutParams lp = (LayoutParams ) child .getLayoutParams ();
          width = getPaddingLeft () + mHorizontalSpacing * i;
    
          lp .x = width;
          lp .y = height;
    
          width += child .getMeasuredWidth ();
          height += mVerticalSpacing ;
        }
    
        width += getPaddingRight ();
        height += getChildAt (getChildCount () - 1). getMeasuredHeight ()
            + getPaddingBottom ();
    
        setMeasuredDimension ( resolveSize( width, widthMeasureSpec ),
            resolveSize( height, heightMeasureSpec ));
      }
    

    4.最后一步,重写onLayout()方法
         代码非常easy,就是让每一个子View都调用layout()方法。
    @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 ());
        }
      }
    

    至此,就利用自己定义的ViewGroup创建了一个和图2一样效果的布局页面。


    三、为子View加入自己定义属性

    既然费了这么大劲,怎么可能就和之前几行XML代码效果一样?
    以下,我们就来为CascadeLayout中的子View加入自己定义属性:
         首先,在之前创建的attrs.xml中加入例如以下代码:

    <declare-styleable name="CascadeLayout_LayoutParams">
         <attr name="layout_vertical_spacing" format="dimension" />
    </declare-styleable>
    
    由于这个新加入的属性是以 layout_ 开头的。所以它会被加入到LayoutParams中去。
    我们能够在之前自己定义的内部类LayoutParams中的构造函数中读取到这个属性,将第一个构造函数改为:
    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 ();
          }
        }
    

    既然加入了新的自己定义属性。就必须在onMeasure()方法中对其加以处理:
    @Override
      protected void onMeasure (int widthMeasureSpec , int heightMeasureSpec ) {
        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;
    
          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 ();
    
        setMeasuredDimension ( resolveSize( width, widthMeasureSpec ),
            resolveSize( height, heightMeasureSpec ));
      }
    



    最后附上完整的CascadeLayout代码:

    package com.manning.androidhacks.hack003.view;
    
    import android.content.Context;
    import android.content.res.TypedArray;
    import android.util.AttributeSet;
    import android.view.View;
    import android.view.ViewGroup;
    
    import com.manning.androidhacks.hack003.R;
    
    public class CascadeLayout extends ViewGroup {
    
      private int mHorizontalSpacing;
      private int mVerticalSpacing;
    
      public CascadeLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    
        TypedArray a = context.obtainStyledAttributes(attrs,
            R.styleable.CascadeLayout);
    
        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) {
        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;
    
          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();
    
        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);
        }
    
      }
    }
    

    project文件夹结构:


    (原文地址:http://blog.csdn.net/vector_yi/article/details/24415537)


  • 相关阅读:
    思科完成收买Pari Networks 增强智能办事
    天音控股拟与外企协作
    运营商竞速搭建手机支出公司
    C版iPhone 4入华期近 高性价比将引追逐
    诺基亚否认将中断塞班办事
    联通守旧国际遨游短信提醒
    思科宣布揭晓已完成收买Pari Networks的买卖
    各种“Phone”辈出 购置裸机或更划算
    台湾运营商看好中低端智能机加大推销量
    诺基亚Ovi商店开卖《水果忍者》
  • 原文地址:https://www.cnblogs.com/blfbuaa/p/6696317.html
Copyright © 2011-2022 走看看