基本概念 测量模式 MeasureSpec MeasureSpec 由两部分组成:
mode : 测量模式   
size : 测量的尺寸大小   
 
 
其中模式有三种:   
UNSPECIFIEDViewGroup 没有做约束,想要多大就多大,一般用于系统内部,如 ListView 等。       
EXACTLY 默认模式,按照给定的值精确计算,具体高宽值和 match_parent 都是这种模式。     
AT_MOST 相当于 wrap_content ,根据自身的内容的高宽来计算。    
 
View 根据 ViewGroup 传入的测量值和模式,对自己宽高进行确定,并完成绘制。 onMeasure 实现测量,然后在 onDraw 中完成对自己的绘制。  
重要 API 
onMeasure 测量,决定高宽等,不是必须但大部分都会重写,重写主要需要针对 wrap_content 模式计算自身实际的高宽,通过调用 setMeasuredDimension 来设置。如果指定具体的高宽或者 match_parent 可以不用重写,父类默认是以这种方式来测量的。   
onDraw 绘制,即如何展现,必须重写   
 
自定义 View 步骤 自定义属性和样式 在 res/values/  下建立一个 attrs.xml , 在里面定义 View 的属性和声明整个样式。  
1 2 3 4 5 <declare-styleable  name ="CustomView" >     <attr  name ="custom_text"  format ="string"  />      <attr  name ="custom_color"  format ="color"  />      <attr  name ="custom_size"  format ="integer"  />  </declare-styleable > 
 
其中,format 一共有如下几种类型:string, boolean, color, dimension, enum, flag, float, fraction, integer, reference。 在布局文件中需要引入这个自定义的属性,先加上这一句(老版本需要手动导入自定义 View 的包名):xmlns:app="http://schemas.android.com/apk/res-auto"。后续就可以通过 app:custom*** 来设置自定义的属性了,如下所示:    
1 2 3 4 5 6 7 8 <com.***.view.CustomView      android:id ="@+id/view_event_custom_view_button"      android:layout_width ="wrap_content"      android:layout_height ="wrap_content"      // 自定义属性     app:custom_text ="@string/view_event_custom_view_button"      app:custom_color ="@color/colorYellow"      app:custom_size ="@dimen/smallTextSize"  /> 
 
在构造方法中获得自定义属性的值 解析自定义属性时,注意 styleable 是通过 declare-styleable 中名称拼接来的。  
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 final  Resources.Theme theme = context.getTheme();TypedArray a = theme.obtainStyledAttributes(attrs,                R.styleable.CustomView, defStyleAttr, 0 ); int  n = a.getIndexCount();for  (int  i = 0 ; i < n; i++) {    int  attr = a.getIndex(i);     switch  (attr) {                  case  R.styleable.CustomView_custom_text:             mText = a.getText(attr).toString();             break ;         case  R.styleable.CustomView_custom_color:             mColor = a.getColor(attr, Color.BLACK);             break ;         case  R.styleable.CustomView_custom_size:             int  defaultSize = (int ) TypedValue.applyDimension(                     TypedValue.COMPLEX_UNIT_SP,                     24 , getResources().getDisplayMetrics());             mSize = a.getDimensionPixelSize(attr, defaultSize);             break ;     } } 
 
重写 onMesure 不是必须的,但是大部分都会重写。如果没有重写,当我们设置明确的宽度和高度时,系统帮我们测量的结果就是我们设置的结果;当我们设置为 WRAP_CONTENT 或者 MATCH_PARENT 系统帮我们测量的结果就是 MATCH_PARENT 的长度。所以当设置了 WRAP_CONTENT 时,需要代码中进行测量,即重写 onMesure 方法。  
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 @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);     Log.d(TAG, "onMeasure: widthMeasureSpec = "  + MeasureSpec.toString(widthMeasureSpec)             + ", heightMeasureSpec = "  + MeasureSpec.toString(heightMeasureSpec));          mPaint.setTextSize(mSize);     mPaint.getTextBounds(mText, 0 , mText.length(), mBound);          int  measureWidth, measureHeight;          if (widthMode == MeasureSpec.EXACTLY){         measureWidth = widthSize;     }else {              float  textWidth = mBound.width();         measureWidth = (int ) (getPaddingLeft() + textWidth + getPaddingRight());     }     if (heightMode == MeasureSpec.EXACTLY){         measureHeight = heightSize;     }else {         float  textHeight = mBound.height();         measureHeight = (int ) (getPaddingTop() + textHeight + getPaddingBottom());     }          setMeasuredDimension(measureWidth, measureHeight); } 
 
重写 onDraw 主要是通过 Paint 和 Canvas 将需要表达的内容画出来。本例只是仿照 TextView 显示一段文本比较简单,所以只需要画出文本就行。  
1 2 3 4 5 6 7 8 9 10 11 12 @Override protected  void  onDraw (Canvas canvas)   {    super .onDraw(canvas);          mPaint.setColor(Color.BLUE);     canvas.drawRect(0 , 0 , getMeasuredWidth(), getMeasuredHeight(), mPaint);          mPaint.setColor(mColor);     canvas.drawText(mText, getWidth()/2  - mBound.width()/2 ,              getHeight()/2  + mBound.height()/2 , mPaint); } 
 
View.draw() 的流程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 public  void  draw (Canvas canvas)   {drawBackground(canvas); onDraw(canvas); dispatchDraw(canvas); onDrawForeground(canvas); ... } 
 
重新布局和绘制 API 
requestLayout 会调用 onMeasure 和 onLayout 进行重新测量及布局,但不会调用 draw 的过程,不会重新绘制任何 View 包括该调用者本身    
invalidate 只能在 UI 线程中执行。请求重绘 View (也就是 draw方法),哪个 View 请求 invalidate 系列方法,就重绘该 View。即 View 只绘制该 View,ViewGroup 绘制整个 ViewGroup   
postInvalidate 非 UI 线程中请求重绘 View   
 
示例 CustomView 设置文本时请求重新布局和绘制  
1 2 3 4 5 public  void  setText (String text)  {    mText = text;     requestLayout();     invalidate(); } 
 
目标 
自定义 View 常见流程   
View 的绘制   
处理事件分发流程   
 
参考文档 
http://blog.csdn.net/lmj623565791/article/details/24252901/    
http://blog.csdn.net/congqingbin/article/details/7869730    
http://blog.csdn.net/yanbober/article/details/46128379/