自定义View与ViewGroup,自定义View——FlowLayout

时间:2023-02-09 15:11:26
自定义View与ViewGroup

一、概述: (一)、View和ViewGroup的职责
1、ViewGroup的职责是什么?         ViewGroup相当于一个放置View的容器,在写布局xml的时候,会告诉容器(凡是以layout开头的属性,都是为用于告诉容器),容器宽度(layout_width)、高度(layout_height)、对齐方式(layout_gravity),还有margin等。因此ViewGroup的职能为:给childView计算出建议的宽和高和测量模式 ,决定childView的位置。         为什么只是建议的宽和高,而不是直接确定呢,因为childView的宽和高可以设置为wrap_content,这样只有childView才能计算出自己的宽和高。
2、View的职责是什么?         View的职责,根据测量模式和ViewGroup给出的建议宽和高,计算出自己的宽和高;另外还有个更重要的职责是:在ViewGroup为其指定的区域内绘制自己的形状。
3、ViewGroup和LayoutParams之间的关系?         大家可以回忆一下,当在LinearLayout中写childView的时候,可以写layout_gravity,layout_weight属性;在RelativeLayout中的childView有layout_centerInParent属性,却没有layout_gravity,layout_weight,这是为什么呢?这是因为每个ViewGroup需要指定一个LayoutParams,用于确定支持childView支持哪些属性,比如LinearLayout指定LinearLayout.LayoutParams等。如果大家去看LinearLayout的源码,会发现其内部定义LinearLayout.LayoutParams,在此类中,你可以发现weight和gravity的身影。
(二)、View的三种测量模式         ViewGroup会为childView指定测量模式,下面简单介绍下三种测量模式:
1、EXACTLY:表示设置了精确的值,一般当childView设置其宽、高为精确值match_parent时,ViewGroup会将其设置为EXACTLY;
2、AT_MOST:表示子布局被限制在一个最大值内,一般当childView设置其宽、高为wrap_content时,ViewGroup会将其设置为AT_MOST;
3、UNSPECIFIED:表示子布局想要多大就多大,一般出现在AadapterView的item的heightMode中、ScrollView的childView的heightMode中;此种模式比较少见。
【备注】:         上面的每一行都有一个一般,意思上述不是绝对的,对于childView的mode的设置还会和ViewGroup的测量mode有一定的关系;当然了,这是第一篇自定义ViewGroup,而且绝大部分情况都是上面的规则,所以为了通俗易懂,暂不深入讨论其他内容。
(三)、从API角度进行浅析
        View根据ViewGroup传人的测量值和模式,对自己宽高进行确定(onMeasure中完成),然后在onDraw中完成对自己的绘制。
        ViewGroup需要给View传入view的测量值和模式(onMeasure中完成),而且对于此ViewGroup的父布局,自己也需要在onMeasure中完成对自己宽和高的确定。此外,需要在onLayout中完成对其childView的位置的指定。

二、自定义View(一)、简介:1、关键部分:        Drawing the layout is a two pass process: a measure pass and a layout pass.         所以一个view执行OnDraw时最关键的是measurelayout。其实这很好理解的,一个view需要绘制出来,那么必须知道他要占多大的空间也就是measure,还得知道在哪里绘制,也就是把view放在哪里即layout。把这两部分掌握好也就可以随意自定义view了。至于viewGroup中如何绘制就参考官方文档,其实就是一个分发绘制,直到child是一个view自己进行绘制。2、重写一个view一般情况下只需要重写onDraw()方法。那么什么时候需要重写onMeasure()onLayout()onDraw() 方法呢,这个问题只要把这几个方法的功能弄清楚就应该知道怎么做了。①、如果需要绘制View的图像,那么需要重写onDraw()方法。(这也是最常用的重写方式。)②、如果需要改变view的大小,那么需要重写onMeasure()方法。③、如果需要改变View的(在父控件的)位置,那么需要重写onLayout()方法。④、根据上面三种不同的需要你可以组合出多种重写方案。
3、按类型划分,自定义View的实现方式可分为三种:自绘控件组合控件、以及继承控件4、如何让自定义的View在界面上显示出来? 只需要像使用普通的控件一样来使用自定义View就可以了。
【备注:官方API文档】When an Activity receives focus, it will be requested to draw its layout. The Android framework will handle the procedure for drawing, but the Activity must provide the root node of its layout hierarchy.
Drawing begins with the root node of the layout. It is requested to measure and draw the layout tree. Drawing is handled by walking the tree and rendering each View that intersects the invalid region. In turn, each View group is responsible for requesting each of its children to be drawn (with the draw() method) and each View is responsible for drawing itself. Because the tree is traversed in-order, this means that parents will be drawn before (i.e., behind) their children, with siblings drawn in the order they appear in the tree.
The framework will not draw Views that are not in the invalid region, and also will take care of drawing the Views background for you.
You can force a View to draw, by callinginvalidate().
Drawing the layout is a two pass process: a measure pass and a layout pass. The measuring pass is implemented in measure(int, int) and is a top-down traversal of the View tree. Each View pushes dimension specifications down the tree during the recursion. At the end of the measure pass, every View has stored its measurements. The second pass happens in layout(int, int, int, int) and is also top-down. During this pass each parent is responsible for positioning all of its children using the sizes computed in the measure pass.
When a View's measure() method returns, its getMeasuredWidth() and getMeasuredHeight() values must be set, along with those for all of that View's descendants. A View's measured width and measured height values must respect the constraints imposed by the View's parents. This guarantees that at the end of the measure pass, all parents accept all of their children's measurements. A parent View may call measure() more than once on its children. For example, the parent may measure each child once with unspecified dimensions to find out how big they want to be, then call measure() on them again with actual numbers if the sum of all the children's unconstrained sizes is too big or too small (i.e., if the children don't agree among themselves as to how much space they each get, the parent will intervene and set the rules on the second pass).
To initiate a layout, call requestLayout(). This method is typically called by a View on itself when it believes that is can no longer fit within its current bounds.
The measure pass uses two classes to communicate dimensions. The View.MeasureSpec class is used by Views to tell their parents how they want to be measured and positioned. The base LayoutParams class just describes how big the View wants to be for both width and height. 
(二)、自绘控件:1、概念:自绘控件的意思就是,这个View上所展现的内容全部都是自己绘制出来的。2、自绘控件的步骤:
  • 1、绘制View :
    • 绘制View主要是onDraw()方法中完成。通过参数Canvas来处理,相关的绘制主要有drawRect、drawLine、drawPath等等。
    • Canvas绘制的常用方法:
      1. drawColor()    填充颜色
      2. drawLine()     绘制线
      3. drawLines()   绘制线条
      4. drawOval()    绘制圆
      5. drawPaint()     
      6. drawPath()      绘制路径
      7. drawPicture()     绘制图片
      8. drawPoint()     绘制点
      9. drawPoints()    绘制点
      10. drawRGB()    填充颜色
      11. drawRect()     绘制矩形
      12. drawText()     绘制文本
      13. drawTextOnPath()    在路径上绘制文本
  • 2、刷新View :(刷新view的方法这里主要有:) 
    • invalidate(int l,int t,int r,int b)     
      • 刷新局部,四个参数分别为左、上、右、下 
    •  invalidate()                                          
      • 整个view刷新。执行invalidate类的方法将会设置view为无效,最终重新调用onDraw()方法。
      • invalidate()是用来刷新View的,必须是在UI线程中进行工作。在修改某个view的显示时,调用invalidate()才能看到重新绘制的界面。invalidate()的调用是把之前的旧的view从主UI线程队列中pop掉。
    • invalidate(Rect dirty)                           
      • 刷新一个矩形区域
  • 3、控制事件:(例如处理以下几个事件)
    • onSaveInstanceState() 处理屏幕切换的现场保存事件 
    • onRestoreInstanceState() 屏幕切换的现场还原事件 
    • onKeyDown() 处理按键事件
    • onMeasure()  当控件的父元素正要放置该控件时调用


3、案例核心代码:


(三)、组合控件1、概念:              组合控件的意思就是,不需要自己去绘制视图上显示的内容,而只是用系统原生的控件就好了,但可以将几个系统原生的控件组合到一起,这样创建出的控件就被称为组合控件。
2、例如:标题栏就是个很常见的组合控件,很多界面的头部都会放置一个标题栏,标题栏上会有个返回按钮和标题,点击按钮后就可以返回到上一个界面。那么下面我们就来尝试去实现这样一个标题栏控件。
3、案例核心代码——实现图片右上角显示红色圆圈,圈内显示数字

(四)、继承控件:1、概念:              继承控件的意思就是,我们并不需要自己重头去实现一个控件,只需要去继承一个现有的控件,然后在这个控件上增加一些新的功能,就可以形成一个自定义的控件了。        这种自定义控件的特点就是不仅能够按照我们的需求加入相应的功能,还可以保留原生控件的所有功能,比如 Android PowerImageView实现,可以播放动画的强大ImageView 就是一个典型的继承控件。
2、例如:对ListView进行扩展, 加入在ListView上滑动就可以显示出一个删除按钮,点击按钮就会删除相应数据的功能。
3、核心代码——实现ListView的item手势侧滑,显示删除按钮

【备注:】

1、getWidth(): View在设定好布局后整个View的宽度。

2、getMeasuredWidth(): 

        对View上的内容进行测量后得到的View内容占据的宽度,前提是你必须在父布局的onLayout()方法或者此View的onDraw()方法里调用measure(0,0);(measure中的参数的值你自己可以定义),否则你得到的结果和getWidth()得到的结果是一样的。



三、示范案例
(一)、效果图
自定义View与ViewGroup,自定义View——FlowLayout



(二)、需求:         定义一个ViewGroup,内部可以传入0到4个childView,分别依次显示在左上角,右上角,左下角,右下角。
(三)、制作步骤:
1、决定该ViewGroup的LayoutParams         对于这个例子,我们只需要ViewGroup能够支持margin即可,那么我们直接使用系统的MarginLayoutParams
[java]
    @Override           public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs)           {               return new MarginLayoutParams(getContext(), attrs);           }  

        重写父类的该方法,返回MarginLayoutParams的实例,这样就为我们的ViewGroup指定了其LayoutParams为MarginLayoutParams。

2、onMeasure
        在onMeasure中计算childView的测量值以及模式,以及设置自己的宽和高:

[java] 

    /** 
         * 计算所有ChildView的宽度和高度 然后根据ChildView的计算结果,设置自己的宽和高 
         */  
        @Override  
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)  
        {  
            /** 
             * 获得此ViewGroup上级容器为其推荐的宽和高,以及计算模式 
             */  
            int widthMode = MeasureSpec.getMode(widthMeasureSpec);  
            int heightMode = MeasureSpec.getMode(heightMeasureSpec);  
            int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);  
            int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);  
      
      
            // 计算出所有的childView的宽和高  
            measureChildren(widthMeasureSpec, heightMeasureSpec);  
            /** 
             * 记录如果是wrap_content是设置的宽和高 
             */  
            int width = 0;  
            int height = 0;  
      
            int cCount = getChildCount();  
      
            int cWidth = 0;  
            int cHeight = 0;  
            MarginLayoutParams cParams = null;  
      
            // 用于计算左边两个childView的高度  
            int lHeight = 0;  
            // 用于计算右边两个childView的高度,最终高度取二者之间大值  
            int rHeight = 0;  
      
            // 用于计算上边两个childView的宽度  
            int tWidth = 0;  
            // 用于计算下面两个childiew的宽度,最终宽度取二者之间大值  
            int bWidth = 0;  
      
            /** 
             * 根据childView计算的出的宽和高,以及设置的margin计算容器的宽和高,主要用于容器是warp_content时 
             */  
            for (int i = 0; i < cCount; i++)  
            {  
                View childView = getChildAt(i);  
                cWidth = childView.getMeasuredWidth();  
                cHeight = childView.getMeasuredHeight();  
                cParams = (MarginLayoutParams) childView.getLayoutParams();  
      
                // 上面两个childView  
                if (i == 0 || i == 1)  
                {  
                    tWidth += cWidth + cParams.leftMargin + cParams.rightMargin;  
                }  
      
                if (i == 2 || i == 3)  
                {  
                    bWidth += cWidth + cParams.leftMargin + cParams.rightMargin;  
                }  
      
                if (i == 0 || i == 2)  
                {  
                    lHeight += cHeight + cParams.topMargin + cParams.bottomMargin;  
                }  
      
                if (i == 1 || i == 3)  
                {  
                    rHeight += cHeight + cParams.topMargin + cParams.bottomMargin;  
                }  
      
            }  
              
            width = Math.max(tWidth, bWidth);  
            height = Math.max(lHeight, rHeight);  
      
            /** 
             * 如果是wrap_content设置为我们计算的值 
             * 否则:直接设置为父容器计算的值 
             */  
            setMeasuredDimension((widthMode == MeasureSpec.EXACTLY) ? sizeWidth  
                    : width, (heightMode == MeasureSpec.EXACTLY) ? sizeHeight  
                    : height);  
        }  


10-14行,获取该ViewGroup父容器为其设置的计算模式和尺寸,大多情况下,只要不是wrap_content,父容器都能正确的计算其尺寸。所以我们自己需要计算如果设置为wrap_content时的宽和高,如何计算呢?那就是通过其childView的宽和高来进行计算。

17行,通过ViewGroup的measureChildren方法为其所有的孩子设置宽和高,此行执行完成后,childView的宽和高都已经正确的计算过了

43-71行,根据childView的宽和高,以及margin,计算ViewGroup在wrap_content时的宽和高。

80-82行,如果宽高属性值为wrap_content,则设置为43-71行中计算的值,否则为其父容器传入的宽和高。


3、onLayout对其所有childView进行定位(设置childView的绘制区域)

[java] 

    // abstract method in viewgroup  
        @Override  
        protected void onLayout(boolean changed, int l, int t, int r, int b)  
        {  
            int cCount = getChildCount();  
            int cWidth = 0;  
            int cHeight = 0;  
            MarginLayoutParams cParams = null;  
            /** 
             * 遍历所有childView根据其宽和高,以及margin进行布局 
             */  
            for (int i = 0; i < cCount; i++)  
            {  
                View childView = getChildAt(i);  
                cWidth = childView.getMeasuredWidth();  
                cHeight = childView.getMeasuredHeight();  
                cParams = (MarginLayoutParams) childView.getLayoutParams();  
      
                int cl = 0, ct = 0, cr = 0, cb = 0;  
      
                switch (i)  
                {  
                case 0:  
                    cl = cParams.leftMargin;  
                    ct = cParams.topMargin;  
                    break;  
                case 1:  
                    cl = getWidth() - cWidth - cParams.leftMargin  
                            - cParams.rightMargin;  
                    ct = cParams.topMargin;  
      
                    break;  
                case 2:  
                    cl = cParams.leftMargin;  
                    ct = getHeight() - cHeight - cParams.bottomMargin;  
                    break;  
                case 3:  
                    cl = getWidth() - cWidth - cParams.leftMargin  
                            - cParams.rightMargin;  
                    ct = getHeight() - cHeight - cParams.bottomMargin;  
                    break;  
      
                }  
                cr = cl + cWidth;  
                cb = cHeight + ct;  
                childView.layout(cl, ct, cr, cb);  
            }  
      
        }  


代码比较容易懂:遍历所有的childView,根据childView的宽和高以及margin,然后分别将0,1,2,3位置的childView依次设置到左上、右上、左下、右下的位置。

如果是第一个View(index=0) :则childView.layout(cl, ct, cr, cb); cl为childView的leftMargin , ct 为topMargin , cr 为cl+ cWidth , cb为 ct + cHeight

如果是第二个View(index=1) :则childView.layout(cl, ct, cr, cb); 

cl为getWidth() - cWidth - cParams.leftMargin- cParams.rightMargin;

ct 为topMargin , cr 为cl+ cWidth , cb为 ct + cHeight

剩下两个类似~

这样就完成了,我们的ViewGroup代码的编写,下面我们进行测试,分别设置宽高为固定值,wrap_content,match_parent


5、测试结果

A、布局1:

[xml]

    <com.example.zhy_custom_viewgroup.CustomImgContainer xmlns:android="http://schemas.android.com/apk/res/android"  
        xmlns:tools="http://schemas.android.com/tools"  
        android:layout_width="200dp"  
        android:layout_height="200dp"  
        android:background="#AA333333" >  
      
        <TextView  
            android:layout_width="50dp"  
            android:layout_height="50dp"  
            android:background="#FF4444"  
            android:gravity="center"  
            android:text="0"  
            android:textColor="#FFFFFF"  
            android:textSize="22sp"  
            android:textStyle="bold" />  
      
        <TextView  
            android:layout_width="50dp"  
            android:layout_height="50dp"  
            android:background="#00ff00"  
            android:gravity="center"  
            android:text="1"  
            android:textColor="#FFFFFF"  
            android:textSize="22sp"  
            android:textStyle="bold" />  
      
        <TextView  
            android:layout_width="50dp"  
            android:layout_height="50dp"  
            android:background="#ff0000"  
            android:gravity="center"  
            android:text="2"  
            android:textColor="#FFFFFF"  
            android:textSize="22sp"  
            android:textStyle="bold" />  
      
        <TextView  
            android:layout_width="50dp"  
            android:layout_height="50dp"  
            android:background="#0000ff"  
            android:gravity="center"  
            android:text="3"  
            android:textColor="#FFFFFF"  
            android:textSize="22sp"  
            android:textStyle="bold" />  
      
    </com.example.zhy_custom_viewgroup.CustomImgContainer>  

ViewGroup宽和高设置为固定值
效果图:



B、布局2:

[xml] 

    <com.example.zhy_custom_viewgroup.CustomImgContainer xmlns:android="http://schemas.android.com/apk/res/android"  
        xmlns:tools="http://schemas.android.com/tools"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:background="#AA333333" >  
      
        <TextView  
            android:layout_width="150dp"  
            android:layout_height="150dp"  
            android:background="#E5ED05"  
            android:gravity="center"  
            android:text="0"  
            android:textColor="#FFFFFF"  
            android:textSize="22sp"  
            android:textStyle="bold" />  
      
        <TextView  
            android:layout_width="50dp"  
            android:layout_height="50dp"  
            android:background="#00ff00"  
            android:gravity="center"  
            android:text="1"  
            android:textColor="#FFFFFF"  
            android:textSize="22sp"  
            android:textStyle="bold" />  
      
        <TextView  
            android:layout_width="50dp"  
            android:layout_height="50dp"  
            android:background="#ff0000"  
            android:gravity="center"  
            android:text="2"  
            android:textColor="#FFFFFF"  
            android:textSize="22sp"  
            android:textStyle="bold" />  
      
        <TextView  
            android:layout_width="50dp"  
            android:layout_height="50dp"  
            android:background="#0000ff"  
            android:gravity="center"  
            android:text="3"  
            android:textColor="#FFFFFF"  
            android:textSize="22sp"  
            android:textStyle="bold" />  
      
    </com.example.zhy_custom_viewgroup.CustomImgContainer>  

ViewGroup的宽和高设置为wrap_content
效果图:
自定义View与ViewGroup,自定义View——FlowLayout


C、布局3:

[xml] 

    <com.example.zhy_custom_viewgroup.CustomImgContainer xmlns:android="http://schemas.android.com/apk/res/android"  
        xmlns:tools="http://schemas.android.com/tools"  
        android:layout_width="match_parent"  
        android:layout_height="match_parent"  
        android:background="#AA333333" >  
      
        <TextView  
            android:layout_width="150dp"  
            android:layout_height="150dp"  
            android:background="#E5ED05"  
            android:gravity="center"  
            android:text="0"  
            android:textColor="#FFFFFF"  
            android:textSize="22sp"  
            android:textStyle="bold" />  
      
        <TextView  
            android:layout_width="50dp"  
            android:layout_height="50dp"  
            android:background="#00ff00"  
            android:gravity="center"  
            android:text="1"  
            android:textColor="#FFFFFF"  
            android:textSize="22sp"  
            android:textStyle="bold" />  
      
        <TextView  
            android:layout_width="50dp"  
            android:layout_height="50dp"  
            android:background="#ff0000"  
            android:gravity="center"  
            android:text="2"  
            android:textColor="#FFFFFF"  
            android:textSize="22sp"  
            android:textStyle="bold" />  
      
        <TextView  
            android:layout_width="150dp"  
            android:layout_height="150dp"  
            android:background="#0000ff"  
            android:gravity="center"  
            android:text="3"  
            android:textColor="#FFFFFF"  
            android:textSize="22sp"  
            android:textStyle="bold" />  
      
    </com.example.zhy_custom_viewgroup.CustomImgContainer>  


ViewGroup的宽和高设置为match_parent
自定义View与ViewGroup,自定义View——FlowLayout


可以看到无论ViewGroup的宽和高的值如何定义,我们的需求都实现了预期的效果~~


自定义View——FlowLayout

一、概述:(一)、什么是FlowLayout?        何为FlowLayout,就是控件根据ViewGroup的宽,自动的往右添加,如果当前行剩余空间不足,则自动添加到下一行。有点所有的控件都往左飘的感觉,第一行满了,往第二行飘,所以也叫流式布局。        Android并没有提供流式布局,但是某些场合中,流式布局还是非常适合使用的,比如关键字标签,搜索热词列表等,如下图: 自定义View与ViewGroup,自定义View——FlowLayout

自定义View与ViewGroup,自定义View——FlowLayout

        这些都特别适合使用FlowLayout,github已经有了这样FlowLayout,当然使用我们自己定义的FlowLayout实现上面的标签效果,就不仅仅是学会使用一个控件,而是学会写一个控件。

二、制作步骤:
(一)、分析1、对于FlowLayout,需要指定的LayoutParams,我们目前只需要能够识别margin即可,即使用MarginLayoutParams.
2、onMeasure中计算所有childView的宽和高,然后根据childView的宽和高,计算自己的宽和高。(当然,如果不是wrap_content,直接使用父ViewGroup传入的计算值即可)
3、onLayout中对所有的childView进行布局。

(二)、generateLayoutParams        因为我们只需要支持margin,所以直接使用系统的MarginLayoutParams
[java]
    @Override  
    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs)   {  
        return new MarginLayoutParams(getContext(), attrs);  
    }  


(三)、onMeasure
[java] 
    /** 
         * 负责设置子控件的测量模式和大小 根据所有子控件设置自己的宽和高 
         */  
        @Override          protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)     {  
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
            // 获得它的父容器为它设置的测量模式和大小  
            int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);  
            int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);  
            int modeWidth = MeasureSpec.getMode(widthMeasureSpec);  
            int modeHeight = MeasureSpec.getMode(heightMeasureSpec);  
      
            Log.e(TAG, sizeWidth + "," + sizeHeight);  
      
            // 如果是warp_content情况下,记录宽和高  
            int width = 0;  
            int height = 0;  
            /** 
             * 记录每一行的宽度,width不断取最大宽度 
             */  
            int lineWidth = 0;  
            /** 
             * 每一行的高度,累加至height 
             */  
            int lineHeight = 0;  
      
            int cCount = getChildCount();  
      
            // 遍历每个子元素  
            for (int i = 0; i < cCount; i++)  
            {  
                View child = getChildAt(i);  
                // 测量每一个child的宽和高  
                measureChild(child, widthMeasureSpec, heightMeasureSpec);  
                // 得到child的lp  
                MarginLayoutParams lp = (MarginLayoutParams) child  
                        .getLayoutParams();  
                // 当前子空间实际占据的宽度  
                int childWidth = child.getMeasuredWidth() + lp.leftMargin  
                        + lp.rightMargin;  
                // 当前子空间实际占据的高度  
                int childHeight = child.getMeasuredHeight() + lp.topMargin  
                        + lp.bottomMargin;  
                /** 
                 * 如果加入当前child,则超出最大宽度,则的到目前最大宽度给width,类加height 然后开启新行 
                 */  
                if (lineWidth + childWidth > sizeWidth)  
                {  
                    width = Math.max(lineWidth, childWidth);// 取最大的  
                    lineWidth = childWidth; // 重新开启新行,开始记录  
                    // 叠加当前高度,  
                    height += lineHeight;  
                    // 开启记录下一行的高度  
                    lineHeight = childHeight;  
                } else  
                // 否则累加值lineWidth,lineHeight取最大高度  
                {  
                    lineWidth += childWidth;  
                    lineHeight = Math.max(lineHeight, childHeight);  
                }  
                // 如果是最后一个,则将当前记录的最大宽度和当前lineWidth做比较  
                if (i == cCount - 1)  
                {  
                    width = Math.max(width, lineWidth);  
                    height += lineHeight;  
                }  
            }  
            setMeasuredDimension((modeWidth == MeasureSpec.EXACTLY) ? sizeWidth  
                    : width, (modeHeight == MeasureSpec.EXACTLY) ? sizeHeight  
                    : height);  
        }  
        首先得到其父容器传入的测量模式和宽高的计算值,然后遍历所有的childView,使用measureChild方法对所有的childView进行测量。然后根据所有childView的测量得出的宽和高得到该ViewGroup如果设置为wrap_content时的宽和高。最后根据模式,如果是MeasureSpec.EXACTLY则直接使用父ViewGroup传入的宽和高,否则设置为自己计算的宽和高。

(四)、onLayout
        onLayout中完成对所有childView的位置以及大小的指定
[java]
    /** 
         * 存储所有的View,按行记录 
         */  
        private List<List<View>> mAllViews = new ArrayList<List<View>>();  
        /** 
         * 记录每一行的最大高度 
         */  
        private List<Integer> mLineHeight = new ArrayList<Integer>();  
        @Override  
        protected void onLayout(boolean changed, int l, int t, int r, int b)  
        {  
            mAllViews.clear();  
            mLineHeight.clear();  
      
            int width = getWidth();  
      
            int lineWidth = 0;  
            int lineHeight = 0;  
            // 存储每一行所有的childView  
            List<View> lineViews = new ArrayList<View>();  
            int cCount = getChildCount();  
            // 遍历所有的孩子  
            for (int i = 0; i < cCount; i++)  
            {  
                View child = getChildAt(i);  
                MarginLayoutParams lp = (MarginLayoutParams) child  
                        .getLayoutParams();  
                int childWidth = child.getMeasuredWidth();  
                int childHeight = child.getMeasuredHeight();  
      
                // 如果已经需要换行  
                if (childWidth + lp.leftMargin + lp.rightMargin + lineWidth > width)  
                {  
                    // 记录这一行所有的View以及最大高度  
                    mLineHeight.add(lineHeight);  
                    // 将当前行的childView保存,然后开启新的ArrayList保存下一行的childView  
                    mAllViews.add(lineViews);  
                    lineWidth = 0;// 重置行宽  
                    lineViews = new ArrayList<View>();  
                }  
                /** 
                 * 如果不需要换行,则累加 
                 */  
                lineWidth += childWidth + lp.leftMargin + lp.rightMargin;  
                lineHeight = Math.max(lineHeight, childHeight + lp.topMargin  
                        + lp.bottomMargin);  
                lineViews.add(child);  
            }  
            // 记录最后一行  
            mLineHeight.add(lineHeight);  
            mAllViews.add(lineViews);  
      
            int left = 0;  
            int top = 0;  
            // 得到总行数  
            int lineNums = mAllViews.size();  
            for (int i = 0; i < lineNums; i++)  
            {  
                // 每一行的所有的views  
                lineViews = mAllViews.get(i);  
                // 当前行的最大高度  
                lineHeight = mLineHeight.get(i);  
      
                Log.e(TAG, "第" + i + "行 :" + lineViews.size() + " , " + lineViews);  
                Log.e(TAG, "第" + i + "行, :" + lineHeight);  
      
                // 遍历当前行所有的View  
                for (int j = 0; j < lineViews.size(); j++)  
                {  
                    View child = lineViews.get(j);  
                    if (child.getVisibility() == View.GONE)  
                    {  
                        continue;  
                    }  
                    MarginLayoutParams lp = (MarginLayoutParams) child  
                            .getLayoutParams();  
      
                    //计算childView的left,top,right,bottom  
                    int lc = left + lp.leftMargin;  
                    int tc = top + lp.topMargin;  
                    int rc =lc + child.getMeasuredWidth();  
                    int bc = tc + child.getMeasuredHeight();  
      
                    Log.e(TAG, child + " , l = " + lc + " , t = " + t + " , r ="  
                            + rc + " , b = " + bc);  
      
                    child.layout(lc, tc, rc, bc);  
                      
                    left += child.getMeasuredWidth() + lp.rightMargin  
                            + lp.leftMargin;  
                }  
                left = 0;  
                top += lineHeight;  
            }  
      
        }  


allViews的每个Item为每行所有View的List集合。

mLineHeight记录的为每行的最大高度。

23-48行,遍历所有的childView,用于设置allViews的值,以及mLineHeight的值。

57行,根据allViews的长度,遍历所有的行数

67-91行,遍历每一行的中所有的childView,对childView的left , top , right , bottom 进行计算,和定位。

92-93行,重置left和top,准备计算下一行的childView的位置。

好了,到此完成了所有的childView的绘制区域的确定,到此,我们的FlowLayout的代码也结束了~~静下心来看一看是不是也不难~
(五)、测试
        使用TextView作为我们的标签,所以为其简单写了一点样式:
res/values/styles.xml中:
[xml] 
    <style name="text_flag_01">  
           <item name="android:layout_width">wrap_content</item>  
           <item name="android:layout_height">wrap_content</item>  
           <item name="android:layout_margin">4dp</item>  
           <item name="android:background">@drawable/flag_01</item>  
           <item name="android:textColor">#ffffff</item>  
       </style>  


flag_01.xml
[xml]
    <?xml version="1.0" encoding="utf-8"?>  
    <shape xmlns:android="http://schemas.android.com/apk/res/android" >        
        <solid android:color="#7690A5" >  
        </solid>        
        <corners android:radius="5dp"/>  
        <padding  
            android:bottom="2dp"  
            android:left="10dp"  
            android:right="10dp"  
            android:top="2dp" />  
    </shape>  


布局文件:
[xml] 

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
        xmlns:tools="http://schemas.android.com/tools"  
        android:layout_width="fill_parent"  
        android:layout_height="fill_parent"  
        android:background="#E1E6F6"  
        android:orientation="vertical" >  
      
        <com.zhy.zhy_flowlayout02.FlowLayout  
            android:layout_width="fill_parent"  
            android:layout_height="wrap_content" >  
      
            <TextView  
                style="@style/text_flag_01"  
                android:text="Welcome" />  
      
            <TextView  
                style="@style/text_flag_01"  
                android:text="IT工程师" />  
      
            <TextView  
                style="@style/text_flag_01"  
                android:text="学习ing" />  
      
            <TextView  
                style="@style/text_flag_01"  
                android:text="恋爱ing" />  
      
            <TextView  
                style="@style/text_flag_01"  
                android:text="挣钱ing" />  
      
            <TextView  
                style="@style/text_flag_01"  
                android:text="努力ing" />  
      
            <TextView  
                style="@style/text_flag_01"  
                android:text="I thick i can" />  
        </com.zhy.zhy_flowlayout02.FlowLayout>  
          
        </LinearLayout>  


效果图:自定义View与ViewGroup,自定义View——FlowLayout



res/drawble/flog_02.xml

[xml]

    <?xml version="1.0" encoding="utf-8"?>  
    <shape xmlns:android="http://schemas.android.com/apk/res/android" >  
      
        <solid android:color="#FFFFFF" >  
        </solid>  
      
        <corners android:radius="40dp"/>  
        <stroke android:color="#C9C9C9" android:width="2dp"/>  
          
        <padding  
            android:bottom="2dp"  
            android:left="10dp"  
            android:right="10dp"  
            android:top="2dp" />  
      
    </shape>  


flag_03.xml

[xml] 

    <?xml version="1.0" encoding="utf-8"?>  
    <shape xmlns:android="http://schemas.android.com/apk/res/android" >  
      
        <solid android:color="#FFFFFF" >  
        </solid>  
      
        <corners android:radius="40dp"/>  
          
        <padding  
            android:bottom="2dp"  
            android:left="10dp"  
            android:right="10dp"  
            android:top="2dp" />  
      
    </shape>  


布局文件:

[xml] 

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
        xmlns:tools="http://schemas.android.com/tools"  
        android:layout_width="fill_parent"  
        android:layout_height="fill_parent"  
        android:background="#E1E6F6"  
        android:orientation="vertical" >  
      
        <com.zhy.zhy_flowlayout02.FlowLayout  
            android:layout_width="fill_parent"  
            android:layout_height="wrap_content" >  
      
            <TextView  
                style="@style/text_flag_01"  
                android:text="Welcome" />  
      
            <TextView  
                style="@style/text_flag_01"  
                android:text="IT工程师" />  
      
            <TextView  
                style="@style/text_flag_01"  
                android:text="学习ing" />  
      
            <TextView  
                style="@style/text_flag_01"  
                android:text="恋爱ing" />  
      
            <TextView  
                style="@style/text_flag_01"  
                android:text="挣钱ing" />  
      
            <TextView  
                style="@style/text_flag_01"  
                android:text="努力ing" />  
      
            <TextView  
                style="@style/text_flag_01"  
                android:text="I thick i can" />  
        </com.zhy.zhy_flowlayout02.FlowLayout>  
          
      
        <com.zhy.zhy_flowlayout02.FlowLayout  
            android:layout_width="fill_parent"  
            android:layout_height="wrap_content"  
            android:layout_marginTop="20dp" >  
      
            <TextView  
                style="@style/text_flag_01"  
                android:background="@drawable/flag_02"  
                android:text="Welcome"  
                android:textColor="#888888" />  
      
            <TextView  
                style="@style/text_flag_01"  
                android:background="@drawable/flag_02"  
                android:text="IT工程师"  
                android:textColor="#888888" />  
      
            <TextView  
                style="@style/text_flag_01"  
                android:background="@drawable/flag_02"  
                android:text="学习ing"  
                android:textColor="#888888" />  
      
            <TextView  
                style="@style/text_flag_01"  
                android:background="@drawable/flag_02"  
                android:text="恋爱ing"  
                android:textColor="#888888" />  
      
            <TextView  
                style="@style/text_flag_01"  
                android:background="@drawable/flag_02"  
                android:text="挣钱ing"  
                android:textColor="#888888" />  
      
            <TextView  
                style="@style/text_flag_01"  
                android:background="@drawable/flag_02"  
                android:text="努力ing"  
                android:textColor="#888888" />  
      
            <TextView  
                style="@style/text_flag_01"  
                android:background="@drawable/flag_02"  
                android:text="I thick i can"  
                android:textColor="#888888" />  
        </com.zhy.zhy_flowlayout02.FlowLayout>  
      
        <com.zhy.zhy_flowlayout02.FlowLayout  
            android:layout_width="fill_parent"  
            android:layout_height="wrap_content"  
            android:layout_marginTop="20dp" >  
      
            <TextView  
                style="@style/text_flag_01"  
                android:background="@drawable/flag_03"  
                android:text="Welcome"  
                android:textColor="#43BBE7" />  
      
            <TextView  
                style="@style/text_flag_01"  
                android:background="@drawable/flag_03"  
                android:text="IT工程师"  
                android:textColor="#43BBE7" />  
      
            <TextView  
                style="@style/text_flag_01"  
                android:background="@drawable/flag_03"  
                android:text="学习ing"  
                android:textColor="#43BBE7" />  
      
            <TextView  
                style="@style/text_flag_01"  
                android:background="@drawable/flag_03"  
                android:text="恋爱ing"  
                android:textColor="#43BBE7" />  
      
            <TextView  
                style="@style/text_flag_01"  
                android:background="@drawable/flag_03"  
                android:text="挣钱ing"  
                android:textColor="#43BBE7" />  
      
            <TextView  
                style="@style/text_flag_01"  
                android:background="@drawable/flag_03"  
                android:text="努力ing"  
                android:textColor="#43BBE7" />  
      
            <TextView  
                style="@style/text_flag_01"  
                android:background="@drawable/flag_03"  
                android:text="I thick i can"  
                android:textColor="#43BBE7" />  
        </com.zhy.zhy_flowlayout02.FlowLayout>  
      
    </LinearLayout>  


效果图:
自定义View与ViewGroup,自定义View——FlowLayout