基本概念 ViewGroup 继承 View ,但是用来作为一个容器,装载各种 View 以及对它们做 UI 布局,比如高、宽、对齐方式等等,布局文件中凡是以 layout_  开头的属性,都是传递给 ViewGroup 来解析和使用的。ViewGroup 主要是计算子 View 的测量高宽并决定他们的位置。 重写 LayoutParams 可以自定义子 View 的特定参数,比如 weight 等。
框架和层级结构 View 和 ViewGroup 的绘制流程框架:   
层级结构如下:  
重要 API 
onMeasureView 的高宽    
onLayoutView  的排列规则   
 
自定义 ViewGroup 步骤 自定义布局属性及 LayoutParams 同样在 res/values/attr.xml 文件中定义 ViewGroup 的属性及样式。   
1 2 3 4 5 6 7 8 9 <attr  name ="custom_orientation" >     <enum  name ="horizontal"  value ="0"  />      <enum  name ="vertical"  value ="1"  />  </attr > <declare-styleable  name ="CustomLayout" >     <attr  name ="custom_orientation" />      <attr  name ="custom_margin"  format ="integer" />  </declare-styleable > 
在布局文件使用时,示例如下:  
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <com.***.view.CustomLayout      android:id ="@+id/view_event_custom_layout"      android:layout_width ="300dp"      android:layout_height ="300dp"      // 自定义Layout 的属性1      app:custom_orientation ="vertical"  >     <TextView           android:id ="@+id/view_event_textview"          android:layout_width ="wrap_content"          android:layout_height ="wrap_content"          android:text ="@string/view_event_textview"          // 自定义Layout 的属性2          app:custom_margin ="@dimen/custom_margin_text" /> </com.***.view.CustomLayout > 
获取自定义布局属性 在 ViewGroup 或者自定义 LayoutParams 的构造方法中获取自定义属性值。  
1 2 3 4 5 6 7 8 9 public  CustomLayout (Context context, AttributeSet attrs, int  defStyleAttr)      super (context, attrs, defStyleAttr);     final  TypedArray a = context.obtainStyledAttributes(attrs,              R.styleable.CustomLayout);               mOrientation = a.getInt(R.styleable.CustomLayout_custom_orientation,              HORIZONTAL); } 
重写 LayoutParams 相关方法 自定义类 LayoutParams 继承 ViewGroup.LayoutParams,并定义布局所需的几个变量。  
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public  static  class  LayoutParams  extends  ViewGroup .LayoutParams          public  int  left = 0 ;     public  int  top = 0 ;     public  int  right = 0 ;     public  int  bottom = 0 ;          public  int  custom_margin = 0 ;               public  LayoutParams (Context c, AttributeSet attrs)          super (c, attrs);                  final  TypedArray a = c.obtainStyledAttributes(attrs,                  R.styleable.CustomLayout);         custom_margin =  a.getDimensionPixelSize(                 R.styleable.CustomLayout_custom_margin, 0 );     }     ... } 
如果自定义了 LayoutParams ,必须重写下面四个方法,确保能做类型转换。   
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Override public  LayoutParams generateLayoutParams (AttributeSet attrs)      return  new  CustomLayout.LayoutParams(getContext(), attrs); } @Override protected  ViewGroup.LayoutParams generateDefaultLayoutParams ()   {    return  new  CustomLayout.LayoutParams(LayoutParams.WRAP_CONTENT,          LayoutParams.WRAP_CONTENT); } @Override protected  boolean  checkLayoutParams (ViewGroup.LayoutParams p)      return  p instanceof  CustomLayout.LayoutParams; } @Override protected  ViewGroup.LayoutParams generateLayoutParams (ViewGroup.LayoutParams p)   {    return  new  CustomLayout.LayoutParams(p); } 
注意 :如果没有重写这四个方法,会导致子 View 获取的 LayoutParams 转换为自定义时抛出异常:CustomLayout.LayoutParams lp = (CustomLayout.LayoutParams) childView.getLayoutParams();Log 打印如下:java.lang.ClassCastException: android.view.ViewGroup$LayoutParams cannot be cast to com.***.view.CustomLayout$LayoutParams  
重写 onMeasure 实现两个功能:  
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 @Override protected  void  onMeasure (int  widthMeasureSpec, int  heightMeasureSpec)      super .onMeasure(widthMeasureSpec, heightMeasureSpec);          int  widthMode = MeasureSpec.getMode(widthMeasureSpec);     int  widthSize = MeasureSpec.getSize(widthMeasureSpec);     int  heightMode = MeasureSpec.getMode(heightMeasureSpec);     int  heightSize = MeasureSpec.getSize(heightMeasureSpec);     int  measureWidth = 0 , measureHeight = 0 ;     if (widthMode != MeasureSpec.AT_MOST){         measureWidth = widthSize;     }     if (heightMode != MeasureSpec.AT_MOST){         measureHeight = heightSize;     }     int  totalLeft = 0 , totalTop = 0 ;     int  count = getChildCount();     for (int  i = 0 ; i < count; i++){         View childView = getChildAt(i);                  measureChild(childView, widthMeasureSpec, heightMeasureSpec);         int  measureChildWidth = childView.getMeasuredWidth();         int  measureChildHeight = childView.getMeasuredHeight();         LayoutParams lp = (LayoutParams) childView.getLayoutParams();                  if (mOrientation == VERTICAL) {             lp.left = 0 ;             lp.top = totalTop + lp.custom_margin;             lp.right = measureChildWidth;             lp.bottom = lp.top + measureChildHeight;             totalTop = lp.bottom;                          if (widthMode == MeasureSpec.AT_MOST){                 measureWidth = Math.max(measureWidth, measureChildWidth);             }             if (heightMode == MeasureSpec.AT_MOST){                 measureHeight = lp.bottom;             }         }         ...     }          setMeasuredDimension(measureWidth, measureHeight); } 
重写 onLayout 计算子 View 的具体布局位置  
1 2 3 4 5 6 7 8 9 10 11 @Override protected  void  onLayout (boolean  changed, int  l, int  t, int  r, int  b)      int  count = getChildCount();     for (int  i = 0 ; i < count; i++){         View childView = getChildAt(i);         CustomLayout.LayoutParams lp = (CustomLayout.LayoutParams)                childView.getLayoutParams();                  childView.layout(lp.left, lp.top, lp.right, lp.bottom);     } } 
自定义 ViewGroup 中,至少需要重写 onMeasure 和 onLayout     
 
总结 
onMeasure 主要计算 wrap_content 模式下的测量高宽,包含自己和所有的子 View   onLayout 主要计算子 View 布局的具体位置  onDraw 绘制自己,展现需要显示的内容   
自定义 ViewGroup 主要计算自身和子 View 的测量高宽,以及子 View 布局的具体位置。View 主要计算自身的测量高宽,以及绘制自己。  
问题 在 Log 跟踪中发现 onLayout 和 onMeasure 会被调用执行两次  
目标 
自定义 ViewGroup 常见流程   
必须重写 onLayout 及需要实现那些功能   
是否处理事件分发流程   
 
参考文档 
http://www.jianshu.com/p/3d2c49315d68   http://blog.csdn.net/lmj623565791/article/details/38339817/   http://www.jianshu.com/p/138b98095778