定义自己的ViewGroup

尊重原创http://blog.csdn.net/yuanzeyao/article/details/40264433

好久都没有写文章了,现在利用周末的时间对一些知识进行总结,便于加深理解,今天我就来学习一下如何实现自定义ViewGroup

在Android中比较常用的布局LinearLayout,FrameLayout,RelativeLayout。。。这些布局都是继承自ViewGroup,通过这些布局,我们几乎可以实现Android中所有的界面开发,但是对于一些比较常见并且比较复杂的布局,使用这些基本布局开发的话,就会花大量的时间在一些重复的工作上,那么我们能不能模仿这些基本布局,根据自己的需求来实现一些自己的布局,以后需要的时候直接拿来用呢?当然是可以的。

类似扑克牌的布局

在斗地主的游戏中,我们会经常遇到类似这样的布局,我就将这个布局叫级联布局吧,这个布局使用RelativeLayout和margin等熟悉是可以做出来的,但是不是很方便,我们今天就这种需求为背景讲解一下自定义ViewGroup

在学习自定义布局前,读者最好先了解一下Android的布局好似怎么绘画出来的,我推荐大家去了解一下:都是官网的一些文章
1、http://developer.android.com/guide/topics/ui/how-android-draws.html
2、http://developer.android.com/reference/android/view/ViewGroup.html
3、http://developer.android.com/reference/android/view/ViewGroup.LayoutParams.html

通过以上几篇文章,我们需要了解一下知识:
1、绘制布局是由两个过程组成:测量过程和布局过程,测量过程使用measure(int,int)方法完成,遍历完成后,所有的View的大小都确定了,布局过程使用layout方法完成,就是通过View的大小,决定View放置在上面地方,不过通过源码中measure和layout方法都是final类型的,所以我们是无法改写的,之所以定义成final的,就是避免开发者破坏了布局的绘画流程,不过测量和布局的细节我们可以通过改写onMeasure和onLayout实现。

2、ViewGroup
在前面我就说过,Android中所有的布局都是继承自ViewGroup,ViewGroup就是一个View的容器,我们可以再里面放置任务的View,至于如何放置,我们可以通过onMeasure和onLayout来定义,onMeasure在measure调用,onLayout是在layout中调用的

3、ViewGroup.LayoutParams

这个类主要是View用来告诉他的父容器它想怎么显示,如宽、高、居中等等。ViewGroup.LayoutParams里面最重要的两个参数就是width,height。如果你想使用margin属性,那么必须使用ViewGroup.MarginLayoutParams这个类,这个类继承自ViewGroup.LayoutParams。添加了对margin属性的支持,如果你想加入更多的属性,可以自己定义一个LayoutParams类加入你需要的属性,事实上LinearLayout等布局都是继承自ViewGroup.MarginLayoutParams,并加入了自己需要的属性。

实现自己的布局CascadeLayout.java

/**
 * 自定义布局,用来实现扑克牌效果
 * com.myviewgroup.CascadeLayout
 * @author yuanzeyao <br/>
 * create at 2014年10月19日 下午4:15:42
 */
public class CascadeLayout extends ViewGroup
{
  /**
   * 水平偏移距离
   */
  private int horizontal_space;

  /**
   * 垂直偏移距离
   */
  private int vertical_space;

  public CascadeLayout(Context context)
  {
    super(context);
  }

  public CascadeLayout(Context context, AttributeSet attrs, int defStyle)
  {
    super(context, attrs, defStyle);
    initAttribute(context, attrs);
  }

  public CascadeLayout(Context context, AttributeSet attrs)
  {
    super(context, attrs);
    initAttribute(context, attrs);
  }

  /**
   * 根据xml文件中的属性对horizontal_space,vertical_space来赋值
   * @param context
   * @param attrs
   */
  private void initAttribute(Context context,AttributeSet attrs)
  {
    TypedArray a=context.obtainStyledAttributes(attrs, R.styleable.CascadeLayout);
    horizontal_space=a.getDimensionPixelSize(R.styleable.CascadeLayout_horizontal_space,this.getResources().getDimensionPixelSize(R.dimen.cascade_horizontal_spacing));
    vertical_space=a.getDimensionPixelSize(R.styleable.CascadeLayout_vertical_space, this.getResources().getDimensionPixelSize(R.dimen.cascade_vertical_spacing));
    a.recycle();
  }

  /**
   * onMeasure在measure中调用,参数分别是CascadeLayout的宽度和高度
   */
  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
  {
    //使用measureChildren对所有的孩子进行测量,你也可以使用for循环,然后掉哦弄个measurechild函数进行测量
    measureChildren(widthMeasureSpec, heightMeasureSpec);
    //将宽度和高度赋值为CascadeLayout的measureWidth,measureHeight变量
    setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
  }

  /**
   * 在该函数中添加如何布置各个View的逻辑
   */
  @Override
  protected void onLayout(boolean changed, int l, int t, int r, int b)
  {
    for(int i=0;i<getChildCount();i++)
    {
      //依次遍历孩子节点
      View child=getChildAt(i);

     // MarginLayoutParams mlp=(MarginLayoutParams) child.getLayoutParams();
      LayoutParams lp=child.getLayoutParams();
      //计算左边和顶部的距离
      int left=horizontal_space*i;
      int top=vertical_space*i;

      child.layout(left,top,left+child.getMeasuredWidth(),top+child.getMeasuredHeight());
    }
  }

  /**
   * 改写生成LayoutParams的方法
   */
  @Override
  protected LayoutParams generateLayoutParams(LayoutParams p)
  {
    //return super.generateLayoutParams(p);
    //return new ViewGroup.MarginLayoutParams(p.width,p.height);
    return new ViewGroup.LayoutParams(p.width,p.height);
  }

  @Override
  public LayoutParams generateLayoutParams(AttributeSet attrs)
  {
    //return super.generateLayoutParams(attrs);
    //return new ViewGroup.MarginLayoutParams(this.getContext(), attrs);
    return new ViewGroup.LayoutParams(this.getContext(), attrs);
  }

}

在CascadeLayout中,我们定义了两个属性,horizontal_space,vertical_space,这两个属性用来记录该布局中每个子View之前的垂直距离和水平距离,他们都是在构造函数中初始化。由于我们添加了属性,所以在res/values中定义attrs.xml文件

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CascadeLayout">
        <attr name="horizontal_space" format="dimension"></attr>
        <attr name="vertical_space" format="dimension"></attr>
    </declare-styleable>
</resources>

在layout文件中使用CascadeLayout

<com.myviewgroup.CascadeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:cascade ="http://schemas.android.com/apk/res/com.myviewgroup"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    cascade:horizontal_space="40dip"
    cascade:vertical_space="40dip"

   >

    <View
        android:layout_width="200dip"
        android:layout_height="200dip"
        android:background="@color/view1"
        />

     <View
        android:layout_width="200dip"
        android:layout_height="200dip"
        android:background="@color/view2"
        />

      <View
        android:layout_width="200dip"
        android:layout_height="200dip"
        android:background="@color/view3"
        />
</com.myviewgroup.CascadeLayout>

运行效果如下

自定义自己的LayoutParams

在CascadeLayout中,我们使用的是ViewGroup.LayoutParams类,但是这个类仅仅有width和height属性,如果我们想加入其他属性,那么我们需要自己定义LayoutParams类
下面我们自定义自己的LayoutParams类,这个类继承ViewGroup.MarginLayoutParams,所以我自定义的LayoutPrams具有margin的属性

在CascadeLayout中定义如下LayoutParams类

public static class LayoutParams extends ViewGroup.MarginLayoutParams
  {
    /**
     * 定义在垂直方向的偏移距离,可以覆盖CascadeLayout中的vertical_space属性
     */
    private int layout_vertical_spacing;

    public LayoutParams(Context c, AttributeSet attrs)
    {
      super(c, attrs);
      TypedArray a = c.obtainStyledAttributes(attrs,
          R.styleable.CascadeLayout_LayoutParams);
      try {
        layout_vertical_spacing = a
            .getDimensionPixelSize(
                R.styleable.CascadeLayout_LayoutParams_layout_vertical_spacing,
                -1);
      } finally {
        a.recycle();
      }
    }

    public LayoutParams(int width, int height)
    {
      super(width, height);
    }
  }

该LayoutParams就支持了layout_vertical_spaceing属性了。
好了,先写到这里吧,有什么不懂的欢迎大家留言。。。

时间: 2024-11-08 21:58:09

定义自己的ViewGroup的相关文章

如何定义自己的ViewGroup

在发展中,有时会遇到一些要求.布局和控制系统不仅提供使用,以满足我们的发展,所以这一次就行,通常是你自己的自定义布局(ViewGroup)并控制(View)该.我在这里,我们将用一个简单的例子,当他们解释他们的定义ViewGroup基本流程,我希望能帮助朋友不理解这个过程. 首先,我们想要实现的布局图例如以下: watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd3V6aGlwZW5nMTk5MQ==/font/5a6L5L2T/fontsize/400/

Android自己定义组件系列【1】——自己定义View及ViewGroup

View类是ViewGroup的父类,ViewGroup具有View的全部特性.ViewGroup主要用来充当View的容器.将当中的View作为自己孩子,并对其进行管理.当然孩子也能够是ViewGroup类型. View类一般用于画图操作,重写它的onDraw方法,但它不能够包括其它组件,没有addView(View view)方法. ViewGroup是一个组件容器,它能够包括不论什么组件,但必须重写onLayout(boolean changed,int l,int t,int r,int

Android自己定义组件系列【2】——Scroller类

在上一篇中介绍了View类的scrollTo和scrollBy两个方法,对这两个方法不太了解的朋友能够先看<自己定义View及ViewGroup> scrollTo和scrollBy尽管实现了视图的偏移,可是却没有更好的控制移动过程,移动是瞬间进行的.Scroller类就是为解决问题而设计的. 打开Scroller的源码,能够看到startScroll方法: /** * Start scrolling by providing a starting point and the distance

Android Touch系统简介(二):实例详解onInterceptTouchEvent与onTouchEvent的调用过程

上一篇文章主要讲述了Android的TouchEvent的分发过程,其中有两个重要的函数:onInterceptTouchEvent和onTouchEvent,这两个函数可被重装以完成特定的逻辑.onInterceptTouchEvent的定义为于ViewGroup中,默认返回值为false,表示不拦截TouchEvent.onTouchEvent的定义位于View中,当ViewGroup要调用onTouchEvent时,会利用super.onTouchEvent.ViewGroup调用onTo

Android中Touch手势分发

在android中的Touch分发中,经常可以看到从ACTION_DOWN->ACTION_MOVE->ACTION_UP,当我们不了解它是如何分发的话总感觉一头雾水,所以有必要了解它的整体分发过程,所以很有必要好好的记录下. 事件分发函数 public void dispatchTouchEvent(MotionEvent ev). public void onInterceptTouchEvent(MotionEvent ev). public void onTouchEvent(Moti

Android应用Activity、Dialog、PopWindow窗口显示机制及源码分析

[工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重劳动成果] 1 背景 之所以写这一篇博客的原因是因为之前有写过一篇<Android应用setContentView与LayoutInflater加载解析机制源码分析>,然后有人在文章下面评论和微博私信中问我关于Android应用Dialog.PopWindow.Toast加载显示机制是咋回事,所以我就写一篇文章来分析分析吧(本文以Android5.1.1 (API 22)源码为基础分析),以便大家在应

Android应用Activity、Dialog、PopWindow、Toast窗体加入机制及源代码分析

[工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处.尊重劳动成果] 1 背景 之所以写这一篇博客的原因是由于之前有写过一篇<Android应用setContentView与LayoutInflater载入解析机制源代码分析>.然后有人在文章以下评论和微博私信中问我关于Android应用Activity.Dialog.PopWindow载入显示机制是咋回事,所以我就写一篇文章来分析分析吧(本文以Android5.1.1 (API 22)源代码为基础分析),以

Android Touch系统简介(二):实例详解onInterceptTouchEvent与onTouchEvent的调用过程

上一篇文章主要讲述了Android的TouchEvent的分发过程,其中有两个重要的函数:onInterceptTouchEvent和onTouchEvent,这两个函数可被重装以完成特定的逻辑.onInterceptTouchEvent的定义为于ViewGroup中,默认返回值为false,表示不拦截TouchEvent.onTouchEvent的定义位于View中,当ViewGroup要调用onTouchEvent时,会利用super.onTouchEvent.ViewGroup调用onTo

Android Touch系统简介(二):实例详解onInterceptTouchEvent与onT

上一篇文章主要讲述了Android的TouchEvent的分发过程,其中有两个重要的函数:onInterceptTouchEvent和onTouchEvent,这两个函数可被重装以完成特定的逻辑.onInterceptTouchEvent的定义为于ViewGroup中,默认返回值为false,表示不拦截TouchEvent.onTouchEvent的定义位于View中,当ViewGroup要调用onTouchEvent时,会利用super.onTouchEvent.ViewGroup调用onTo