View是屏幕善的一塊矩形區域,它負責來顯示一個區域,而且響應這個區域內的事件。能夠說,手機屏幕上任意能夠看得見的地方都是View。html
對於Activity來講,咱們經過setContentView(view)添加的佈局到Activity上,實際上都是添加到了Activity內部的DecorView上面,這個DecorView,其實就是一個FrameLayout,所以咱們的佈局實際上添加到了FrameLayout裏面。java
View的工做流程分爲兩部分,第一部分是顯示在屏幕上的過程,第二部分是響應屏幕上的觸摸事件的過程。android
對於顯示在屏幕上的過程:是View從無到有,通過測量大小(Measure)、肯定在屏幕中的位置(Layout)、以及最終繪製在屏幕上(Draw)這一系列的過程。canvas
對於響應屏幕上的觸摸事件的過程:則是Touch事件的分發過程。c#
Measure()、Layout()方法是final修飾的,沒法重寫,Draw()雖然不是final的,可是耶不建議重寫該方法。app
View爲咱們提供了onMeasure()、onLayout()、onDraw()這樣的方法,其實所謂的自定義View,也就是對這三個方法的重寫過程。less
View的onMeasure()方法以下:ide
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }onMeasure經過父View傳遞過來額大小和模式,以及自身的背景圖片的大小得出自身最終的大小,經過setMeasuredDimension()方法設置給mMeasuredWidth和mMeasuredHeight。佈局
普通的View的onMeasure邏輯大同小異,基本都是測量自身內容和背景,而後根據父View傳遞過來的MeasureSpec進行最終的大小斷定,例如TextView會根據文字的長度,文字的大小,文字行高,文字的寬度,顯示方法,背景圖片以及父View傳遞過來的模式和大小最終肯定自身的大小。post
這是Android中本身定義的測量規格。由於測量過程當中,系統會把咱們代碼或者佈局中設置的View的LayoutParams轉換成MeasureSpec,而後經過MeasureSpec來測量肯定View的寬高。
MeasureSpec由32位int值組成,高2位表示的測量模式specMode,後30位表明的是測量大小specSize
public static class MeasureSpec { private static final int MODE_SHIFT = 30; private static final int MODE_MASK = 0x3 << MODE_SHIFT; /** 測量模式:父View不對子View的大小作任何限制,能夠是子佈局須要的任意大小 * Measure specification mode: The parent has not imposed any constraint * on the child. It can be whatever size it wants. */ public static final int UNSPECIFIED = 0 << MODE_SHIFT; /** 已經爲子View給出了確切的值 至關於給View的LayoutParams指定了具體數值,或者match_parent. * Measure specification mode: The parent has determined an exact size * for the child. The child is going to be given those bounds regardless * of how big it wants to be. */ public static final int EXACTLY = 1 << MODE_SHIFT; /**子View能夠跟本身的測量大小同樣大。對應於View的LayoutParams的wrap_content * Measure specification mode: The child can be as large as it wants up * to the specified size. */ public static final int AT_MOST = 2 << MODE_SHIFT; /**根據 size和mode 去生成一個MeasureSpec值 * Creates a measure specification based on the supplied size and mode. * * The mode must always be one of the following: * <li>{@link android.view.View.MeasureSpec#UNSPECIFIED}</li> * <li>{@link android.view.View.MeasureSpec#EXACTLY}</li> * <li>{@link android.view.View.MeasureSpec#AT_MOST}</li> * @return the measure specification based on size and mode */ public static int makeMeasureSpec(int size, int mode) { if (sUseBrokenMakeMeasureSpec) { return size + mode; } else { return (size & ~MODE_MASK) | (mode & MODE_MASK); } } /**獲取測量模式 * Extracts the mode from the supplied measure specification. * * @param measureSpec the measure specification to extract the mode from * @return {@link android.view.View.MeasureSpec#UNSPECIFIED}, * {@link android.view.View.MeasureSpec#AT_MOST} or * {@link android.view.View.MeasureSpec#EXACTLY} */ public static int getMode(int measureSpec) { return (measureSpec & MODE_MASK); } /**獲取測量值大小 * Extracts the size from the supplied measure specification. * * @param measureSpec the measure specification to extract the size from * @return the size in pixels defined in the supplied measure specification */ public static int getSize(int measureSpec) { return (measureSpec & ~MODE_MASK); } static int adjust(int measureSpec, int delta) { return makeMeasureSpec(getSize(measureSpec + delta), getMode(measureSpec)); } }MeasureSpec是由父佈局和View自身的LayoutParams來決定的
PS:通過實際使用,父View的佈局爲match_parent或指定具體數字時是EXACTLY;
父View的佈局爲wrap_content時是AT_MOST。
通過measure後,咱們就能夠經過getMeasuredWidth/Height獲取View的寬高。
普通的View對於layout方法直接空實現便可。
draw()的過程就是繪製View到屏幕上的規程,draw()的執行遵照以下步驟:
/* * Draw traversal performs several drawing steps which must be executed * in the appropriate order: * * 1. Draw the background(繪製背景) * 2. If necessary, save the canvas' layers to prepare for fading(保存畫布的圖層來準備色變) * 3. Draw view's content(繪製內容) * 4. Draw children(繪製children) * 5. If necessary, draw the fading edges and restore layers(畫出褪色的邊緣和恢復層) * 6. Draw decorations (scrollbars for instance)(繪製裝飾 好比scollbar) */通常2和5是能夠跳過的。
在TextView中在該方法中繪製文字、光標和CompoundDrawable、ImageView中相對簡單,只是繪製了圖片。View的繪製主要經過dispatchDraw()。
requestLayout - View從新調用一次layout過程。
invalidate - View從新調用一次draw過程。
postInvalidate - 在非UI線程發起invalidate動做。
forceLayout - 標識View在下一次重繪,須要從新調用layout過程。
一個自定義方塊,循環顯示不一樣顏色
/** * Created by xupeng on 16/12/25. */ public class MyCustomView extends View { private Paint mPaint = null; private int[] colors = {Color.BLUE, Color.GREEN, Color.YELLOW}; public MyCustomView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public MyCustomView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyCustomView, 0, defStyleAttr); int color = typedArray.getColor(R.styleable.MyCustomView_custom_color, Color.BLUE); mPaint = new Paint(); mPaint.setColor(color); new Thread(new Runnable() { @Override public void run() { int i = 0; while (true){ mPaint.setColor(colors[i++ % 3]); postInvalidate(); try { Thread.sleep(1500); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // super.onMeasure(widthMeasureSpec, heightMeasureSpec); setMeasuredDimension(getDefaultSize(getMinimumWidth(), widthMeasureSpec), getDefaultSize(getMinimumHeight(), heightMeasureSpec)); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); } @Override protected void onDraw(final Canvas canvas) { super.onDraw(canvas); canvas.drawRect(0, 0, 200, 300, mPaint); } }
View原理簡介
http://blog.csdn.net/u011733020/article/details/50849475
幫你搞定Android自定義View
http://www.jianshu.com/p/84cee705b0d3
Android手把手教您自定義ViewGroup
http://blog.csdn.net/lmj623565791/article/details/38339817/
爲何自定義ViewGroup ondraw方法不會被調用
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2014/1014/1765.html
關於getMeasuredHeight和getHeight區別(getMeasureHeight是xml或代碼中制定的測量高度,setMeasuredHeight; getHeight是實際顯示出來的高度,經過view.layout()方即可以改變其值)