A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© Android_Robot 中级黑马   /  2016-5-12 10:30  /  8486 人查看  /  72 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

本帖最后由 Android_Robot 于 2016-10-19 15:12 编辑

自定义ViewGroup(一)


今天给大家带来一篇自定义ViewGroup的教程,说白了,就是教大家如何自定义ViewGroup,如果你对自定义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的3种测量模式

上面提到了ViewGroup会为childView指定测量模式,下面简单介绍下三种测量模式:


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

注:上面的每一行都有一个一般,意思上述不是绝对的,对于childView的mode的设置还会和ViewGroup的测量mode有一定的关系;当然了,这是第一篇自定义ViewGroup,而且绝大部分情况都是上面的规则,所以为了通俗易懂,暂不深入讨论其他内容。


三、从API角度进行浅析

上面叙述了ViewGroup和View的职责,下面从API角度进行浅析。

View的根据ViewGroup传人的测量值和模式,对自己宽高进行确定(onMeasure中完成),然后在onDraw中完成对自己的绘制。

ViewGroup需要给View传入view的测量值和模式(onMeasure中完成),而且对于此ViewGroup的父布局,自己也需要在onMeasure中完成对自己宽和高的确定。此外,需要在onLayout中完成对其childView的位置的指定。


四、完整的例子

需求:我们定义一个ViewGroup,内部可以传入0到4个childView,分别依次显示在左上角,右上角,左下角,右下角。


1、决定该ViewGroup的LayoutParams

对于我们这个例子,我们只需要ViewGroup能够支持margin即可,那么我们直接使用系统的MarginLayoutParam

  1.     public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs)  
  2.     {  
  3.          return new MarginLayoutParams(getContext(), attrs);  
  4.     }
复制代码

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

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

  1. /**
  2.      * 计算所有ChildView的宽度和高度 然后根据ChildView的计算结果,设置自己的宽和高
  3.      */  
  4.     @Override  
  5.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)  
  6.     {  
  7.         /**
  8.          * 获得此ViewGroup上级容器为其推荐的宽和高,以及计算模式
  9.          */  
  10.         int widthMode = MeasureSpec.getMode(widthMeasureSpec);  
  11.         int heightMode = MeasureSpec.getMode(heightMeasureSpec);  
  12.         int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);  
  13.         int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);  
  14.   
  15.   
  16.         // 计算出所有的childView的宽和高  
  17.         measureChildren(widthMeasureSpec, heightMeasureSpec);  
  18.         /**
  19.          * 记录如果是wrap_content是设置的宽和高
  20.          */  
  21.         int width = 0;  
  22.         int height = 0;  
  23.   
  24.         int cCount = getChildCount();  
  25.   
  26.         int cWidth = 0;  
  27.         int cHeight = 0;  
  28.         MarginLayoutParams cParams = null;  
  29.   
  30.         // 用于计算左边两个childView的高度  
  31.         int lHeight = 0;  
  32.         // 用于计算右边两个childView的高度,最终高度取二者之间大值  
  33.         int rHeight = 0;  
  34.   
  35.         // 用于计算上边两个childView的宽度  
  36.         int tWidth = 0;  
  37.         // 用于计算下面两个childiew的宽度,最终宽度取二者之间大值  
  38.         int bWidth = 0;  
  39.   
  40.         /**
  41.          * 根据childView计算的出的宽和高,以及设置的margin计算容器的宽和高,主要用于容器是warp_content时
  42.          */  
  43.         for (int i = 0; i < cCount; i++)  
  44.         {  
  45.             View childView = getChildAt(i);  
  46.             cWidth = childView.getMeasuredWidth();  
  47.             cHeight = childView.getMeasuredHeight();  
  48.             cParams = (MarginLayoutParams) childView.getLayoutParams();  
  49.   
  50.             // 上面两个childView  
  51.             if (i == 0 || i == 1)  
  52.             {  
  53.                 tWidth += cWidth + cParams.leftMargin + cParams.rightMargin;  
  54.             }  
  55.   
  56.             if (i == 2 || i == 3)  
  57.             {  
  58.                 bWidth += cWidth + cParams.leftMargin + cParams.rightMargin;  
  59.             }  
  60.   
  61.             if (i == 0 || i == 2)  
  62.             {  
  63.                 lHeight += cHeight + cParams.topMargin + cParams.bottomMargin;  
  64.             }
  65.             if (i == 1 || i == 3)  
  66.             {  
  67.                 rHeight += cHeight + cParams.topMargin + cParams.bottomMargin;  
  68.             }
  69.         }
  70.         width = Math.max(tWidth, bWidth);  
  71.         height = Math.max(lHeight, rHeight);  
  72.   
  73.         /**
  74.          * 如果是wrap_content设置为我们计算的值
  75.          * 否则:直接设置为父容器计算的值
  76.          */  
  77.         setMeasuredDimension((widthMode == MeasureSpec.EXACTLY) ? sizeWidth  
  78.                 : width, (heightMode == MeasureSpec.EXACTLY) ? sizeHeight  
  79.                 : height);  
  80.     }
复制代码

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的绘制区域)

  1. // abstract method in viewgroup  
  2.     @Override  
  3.     protected void onLayout(boolean changed, int l, int t, int r, int b)  
  4.     {  
  5.         int cCount = getChildCount();  
  6.         int cWidth = 0;  
  7.         int cHeight = 0;  
  8.         MarginLayoutParams cParams = null;  
  9.         /**
  10.          * 遍历所有childView根据其宽和高,以及margin进行布局
  11.          */  
  12.         for (int i = 0; i < cCount; i++)  
  13.         {  
  14.             View childView = getChildAt(i);  
  15.             cWidth = childView.getMeasuredWidth();  
  16.             cHeight = childView.getMeasuredHeight();  
  17.             cParams = (MarginLayoutParams) childView.getLayoutParams();  
  18.   
  19.             int cl = 0, ct = 0, cr = 0, cb = 0;  
  20.   
  21.             switch (i)  
  22.             {  
  23.             case 0:  
  24.                 cl = cParams.leftMargin;  
  25.                 ct = cParams.topMargin;  
  26.                 break;  
  27.             case 1:  
  28.                 cl = getWidth() - cWidth - cParams.leftMargin  
  29.                         - cParams.rightMargin;  
  30.                 ct = cParams.topMargin;  
  31.   
  32.                 break;  
  33.             case 2:  
  34.                 cl = cParams.leftMargin;  
  35.                 ct = getHeight() - cHeight - cParams.bottomMargin;  
  36.                 break;  
  37.             case 3:  
  38.                 cl = getWidth() - cWidth - cParams.leftMargin  
  39.                         - cParams.rightMargin;  
  40.                 ct = getHeight() - cHeight - cParams.bottomMargin;  
  41.                 break;  
  42.   
  43.             }  
  44.             cr = cl + cWidth;  
  45.             cb = cHeight + ct;  
  46.             childView.layout(cl, ct, cr, cb);  
  47.         }  
  48.     }  
复制代码

代码比较容易懂:遍历所有的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


五、测试结果
布局1:
    1. <com.example.zhy_custom_viewgroup.CustomImgContainer xmlns:android="http://schemas.android.com/apk/res/android"  
    2.     xmlns:tools="http://schemas.android.com/tools"  
    3.     android:layout_width="200dp"  
    4.     android:layout_height="200dp"  
    5.     android:background="#AA333333" >  
    6.   
    7.     <TextView  
    8.         android:layout_width="50dp"  
    9.         android:layout_height="50dp"  
    10.         android:background="#FF4444"  
    11.         android:gravity="center"  
    12.         android:text="0"  
    13.         android:textColor="#FFFFFF"  
    14.         android:textSize="22sp"  
    15.         android:textStyle="bold" />  
    16.   
    17.     <TextView  
    18.         android:layout_width="50dp"  
    19.         android:layout_height="50dp"  
    20.         android:background="#00ff00"  
    21.         android:gravity="center"  
    22.         android:text="1"  
    23.         android:textColor="#FFFFFF"  
    24.         android:textSize="22sp"  
    25.         android:textStyle="bold" />  
    26.   
    27.     <TextView  
    28.         android:layout_width="50dp"  
    29.         android:layout_height="50dp"  
    30.         android:background="#ff0000"  
    31.         android:gravity="center"  
    32.         android:text="2"  
    33.         android:textColor="#FFFFFF"  
    34.         android:textSize="22sp"  
    35.         android:textStyle="bold" />  
    36.   
    37.     <TextView  
    38.         android:layout_width="50dp"  
    39.         android:layout_height="50dp"  
    40.         android:background="#0000ff"  
    41.         android:gravity="center"  
    42.         android:text="3"  
    43.         android:textColor="#FFFFFF"  
    44.         android:textSize="22sp"  
    45.         android:textStyle="bold" />  
    46.   
    47. </com.example.zhy_custom_viewgroup.CustomImgContainer>  
    复制代码


ViewGroup宽和高设置为固定值

效果图:



布局2:
  1. <com.example.zhy_custom_viewgroup.CustomImgContainer xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="wrap_content"  
  4.     android:layout_height="wrap_content"  
  5.     android:background="#AA333333" >  
  6.   
  7.     <TextView  
  8.         android:layout_width="150dp"  
  9.         android:layout_height="150dp"  
  10.         android:background="#E5ED05"  
  11.         android:gravity="center"  
  12.         android:text="0"  
  13.         android:textColor="#FFFFFF"  
  14.         android:textSize="22sp"  
  15.         android:textStyle="bold" />  
  16.   
  17.     <TextView  
  18.         android:layout_width="50dp"  
  19.         android:layout_height="50dp"  
  20.         android:background="#00ff00"  
  21.         android:gravity="center"  
  22.         android:text="1"  
  23.         android:textColor="#FFFFFF"  
  24.         android:textSize="22sp"  
  25.         android:textStyle="bold" />  
  26.   
  27.     <TextView  
  28.         android:layout_width="50dp"  
  29.         android:layout_height="50dp"  
  30.         android:background="#ff0000"  
  31.         android:gravity="center"  
  32.         android:text="2"  
  33.         android:textColor="#FFFFFF"  
  34.         android:textSize="22sp"  
  35.         android:textStyle="bold" />  
  36.   
  37.     <TextView  
  38.         android:layout_width="50dp"  
  39.         android:layout_height="50dp"  
  40.         android:background="#0000ff"  
  41.         android:gravity="center"  
  42.         android:text="3"  
  43.         android:textColor="#FFFFFF"  
  44.         android:textSize="22sp"  
  45.         android:textStyle="bold" />  
  46.   
  47. </com.example.zhy_custom_viewgroup.CustomImgContainer>
复制代码

ViewGroup的宽和高设置为wrap_content
效果图:



布局3:
  1. <com.example.zhy_custom_viewgroup.CustomImgContainer xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:background="#AA333333" >  
  6.   
  7.     <TextView  
  8.         android:layout_width="150dp"  
  9.         android:layout_height="150dp"  
  10.         android:background="#E5ED05"  
  11.         android:gravity="center"  
  12.         android:text="0"  
  13.         android:textColor="#FFFFFF"  
  14.         android:textSize="22sp"  
  15.         android:textStyle="bold" />  
  16.   
  17.     <TextView  
  18.         android:layout_width="50dp"  
  19.         android:layout_height="50dp"  
  20.         android:background="#00ff00"  
  21.         android:gravity="center"  
  22.         android:text="1"  
  23.         android:textColor="#FFFFFF"  
  24.         android:textSize="22sp"  
  25.         android:textStyle="bold" />  
  26.   
  27.     <TextView  
  28.         android:layout_width="50dp"  
  29.         android:layout_height="50dp"  
  30.         android:background="#ff0000"  
  31.         android:gravity="center"  
  32.         android:text="2"  
  33.         android:textColor="#FFFFFF"  
  34.         android:textSize="22sp"  
  35.         android:textStyle="bold" />  
  36.   
  37.     <TextView  
  38.         android:layout_width="150dp"  
  39.         android:layout_height="150dp"  
  40.         android:background="#0000ff"  
  41.         android:gravity="center"  
  42.         android:text="3"  
  43.         android:textColor="#FFFFFF"  
  44.         android:textSize="22sp"  
  45.         android:textStyle="bold" />  
  46.   
  47. </com.example.zhy_custom_viewgroup.CustomImgContainer>
复制代码

ViewGroup的宽和高设置为match_parent



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

好了,通过这篇教程,希望大家已经能够初步掌握自定义ViewGroup的步骤,大家可以尝试自定义ViewGroup去实现LinearLayout的效果~~

最后说明下,此为第一篇ViewGroup的教程,以后还会进一步的探讨如何更好的自定义ViewGroup~~~


如果你觉得这篇文章对你有用,那么赞一个或者留个言吧~


其他精华资源推荐:



72 个回复

正序浏览
yang0907 来自手机 中级黑马 2016-8-4 00:28:39
72#
收藏,以后会用到的,自己还需努力
回复 使用道具 举报
赞  已收藏!!!
回复 使用道具 举报
楼主   666666666666
回复 使用道具 举报
楼主   666666666666
回复 使用道具 举报
感谢楼主分享
回复 使用道具 举报
赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞
回复 使用道具 举报
mark一下
回复 使用道具 举报
别让自己后悔!
回复 使用道具 举报
很好很好,总结的太全了
回复 使用道具 举报
貌似很厉害的样子呀
回复 使用道具 举报
看看,赞一个
回复 使用道具 举报
qinxuezilu1314 来自手机 中级黑马 2016-7-15 23:27:59
61#
其实我是来回复的,顶
回复 使用道具 举报
看看!!!
回复 使用道具 举报
好东西,收藏 了
回复 使用道具 举报
貌似很厉害的样子呀
回复 使用道具 举报
可以!顶一个
回复 使用道具 举报
6666666666666
回复 使用道具 举报
学习中...
回复 使用道具 举报
学习中!!!!!!
回复 使用道具 举报
赞赞赞赞赞赞赞赞赞赞赞赞赞
回复 使用道具 举报
1234下一页
您需要登录后才可以回帖 登录 | 加入黑马