任何一個視圖都是要通過很是科學的繪製流程後才能顯示出來的,每個視圖的繪製過程其實就是一個完整的生命週期,咱們從這裏開始入手,一塊兒學習自定義View。android
一.準備工做canvas
佈局文件:ide
<org.daliang.xiaohehe.androidartstudy.MyView android:id="@+id/my_view" android:layout_width="match_parent" android:layout_height="match_parent"> </org.daliang.xiaohehe.androidartstudy.MyView>
Activity代碼:函數
public class FiveActivity extends AppCompatActivity { private MyView myView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.e("log", "Activity生命週期:onCreate"); setContentView(R.layout.activity_five); initView(); } private void initView() { myView = (MyView) findViewById(R.id.my_view); } @Override protected void onStart() { super.onStart(); Log.e("log", "Activity生命週期:onStart"); } @Override protected void onResume() { super.onResume(); Log.e("log", "Activity生命週期:onResume"); } @Override protected void onRestart() { super.onRestart(); Log.e("log", "Activity生命週期:onRestart"); } @Override protected void onPause() { super.onPause(); Log.e("log", "Activity生命週期:onPause"); } @Override protected void onStop() { super.onStop(); Log.e("log", "Activity生命週期:onStop"); } @Override protected void onDestroy() { super.onDestroy(); Log.e("log", "Activity生命週期:onDestroy"); } }
自定義View代碼:工具
public class MyView extends View { public MyView(Context context, AttributeSet attrs) { super(context, attrs); Log.e("log", "onCreate"); } @Override protected void onFinishInflate() { super.onFinishInflate(); Log.e("log", "onFinishInflate"); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); Log.e("log", "onAttachedToWindow"); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); Log.e("log", "onMeasure"); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); Log.e("log", "onSizeChanged"); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); Log.e("log", "onLayout"); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); Log.e("log", "onDraw"); } @Override public void onWindowFocusChanged(boolean hasWindowFocus) { super.onWindowFocusChanged(hasWindowFocus); Log.e("log", "onWindowFocusChanged" + " " + hasWindowFocus); } }
很簡單的代碼,整個佈局文件裏面一個自定義View,進入這個界面,而後返回。依次打印各個生命週期,咱們看一下打印的結果:佈局
二.結果分析學習
1.Activity生命週期:onCreatespa
Activity生命週期的第一個方法,表示此Activity正在被建立,這裏咱們主要進行一些初始化的工做。好比調用setContentView()方法去加載界面佈局資源。.net
2.View生命週期:onCreaterest
當Activity在onCreate加載界面佈局資源的時候,咱們自定義的View會在xml文件中被加載,而且調用構造函數 MyView(Context context, AttributeSet attrs)來加載自定義View。
3.View生命週期:onFinishInflate
源碼是這樣告訴咱們的:
本身蹩腳的英語水平加上網上的一些解釋,以爲這樣理解仍是比較靠譜的:
onFinishInflate是在自定義View中全部的子控件均被映射成xml後調用。這樣View就完成了初始準備工做,Activity也完成了對應佈局資源的加載。
4.View生命週期:onAttachedToWindow
再來瞅一眼源碼:
onAttachedToWindow是在將view綁定到activity所在window時調用,附加到window後,程序開始進行自定義View的繪製。
5.View生命週期:onMeasure
參考資料:
Android視圖繪製流程徹底解析,帶你一步步深刻了解View(二)
做爲自定義View三部曲的第一步,onMeasure方法有着極其重要的做用,因此深刻理解其中原理對咱們之後自定義View開發有着很大的幫助。
MeasureSpec測量規範:
系統顯示一個View,首先須要經過測量(measure)該View來獲取其長和寬從而肯定顯示該View時須要多大的空間。在測量的過程當中MeasureSpec貫穿全程,發揮着不可或缺的做用,先瞅一眼源碼:
從上面這張圖片中咱們能夠獲取到許多重要的信息:
1.MeasureSpec封裝了父佈局傳遞給子View的佈局要求
2. MeasureSpec能夠表示寬和高
3 MeasureSpec由size(大小)和mode(模式)組成
MeasureSpec是一個32位的int數據,其中高2位表明SpecMode即某種測量模式,低30位爲SpecSize表明在該模式下的規格大小.
能夠經過以下方式分別獲取這兩個值:
獲取SpecSize
int specSize = MeasureSpec.getSize(measureSpec)
獲取specMode
int specMode = MeasureSpec.getMode(measureSpec)
經過這兩個值生成新的MeasureSpec
int measureSpec=MeasureSpec.makeMeasureSpec(size, mode);
SpecMode一共有三種:
MeasureSpec.UNSPECIFIED,MeasureSpec.EXACTLY , MeasureSpec.AT_MOST , 咱們依次看看每種測量模式表明什麼意思:
MeasureSpec.UNSPECIFIED:父視圖不對子視圖施加任何限制,子視圖能夠獲得任意想要的大小,表示子佈局想要多大就多大。這種模式通常用做Android系統內部,或者ListView和ScrollView等滑動控件,不多使用。
MeasureSpec.EXACTLY :父視圖但願子視圖的大小是specSize中指定的大小,在該模式下,View的測量大小即爲SpecSize。
MeasureSpec.AT_MOST:父容器未能檢測出子View所須要的精確大小,可是指定了一個可用大小即specSize ,在該模式下,View的測量大小不能超過SpecSize
可用表格來規整各一下MeasureSpec的生成:
子View的MeasureSpec由其父容器的MeasureSpec和該子View自己的佈局參數LayoutParams共同決定。
當設置View的參數等於MATCH_PARENT的時候,MeasureSpec的specMode就等於EXACTLY,當設置View的參數等於WRAP_CONTENT的時候,MeasureSpec的specMode就等於AT_MOST。而且MATCH_PARENT和WRAP_CONTENT時的specSize都是等於windowSize的,也就意味着根視圖老是會充滿全屏的。因此自定義View在重寫onMeasure()的過程當中應該手動處理View的寬或高爲wrap_content的狀況。
通過測量得出的子View的MeasureSpec是系統給出的一個指望值(參考值),咱們也可摒棄系統的這個測量流程,直接調用setMeasuredDimension( )設置子View的寬和高的測量值。
須要注意的是,在setMeasuredDimension()方法調用以後,咱們才能使用getMeasuredWidth()和getMeasuredHeight()來獲取視圖測量出的準確的寬度與高度。
因而可知,視圖大小的控制是由父視圖、佈局文件、以及視圖自己共同完成的,父視圖會提供給子視圖參考的大小,開發人員能夠在XML文件中指定視圖的大小,最後視圖自己纔會肯定最終的大小。
6.View生命週期:onSizeChanged
繼續看看源碼是怎麼解釋的:
onSizeChanged是在佈局文件中自定義View的大小發生改變時被調用。四個參數依次表明變化以後的寬高以及變化以前的寬高。在onMeasure方法結束以後第一次進行調用,將測量的寬高做爲前兩個參數,0做爲後兩個默認參數。
7.View生命週期:onLayout
測量過程結束後,視圖的大小就已經測量好了,接下來就是layout的過程了。正如其名字所描述的同樣,這個方法是用於給視圖進行佈局的,也就是肯定視圖的具體位置。
看看源碼怎麼解釋的:
onLayout是在layout方法中指定子View的大小和位置。其實就是ViewGroup會調用onLayout()決定子View的顯示位置。其中四個參數l, t, r, b分別表示子View相對於父View的左、上、右、下的座標。
getMeasureWidth()方法在measure()過程結束後就能夠獲取到了,而getWidth()方法要在layout()過程結束後才能獲取到。在某些複雜或者極端的狀況下系統會屢次執行measure過程,因此在onMeasure()中去獲取View的測量大小獲得的是一個不許確的值。爲了不該狀況,最好在onMeasure()的下一階段即onLayout()中去獲取View的寬高。
8.View生命週期:onDraw
看看源碼的解釋:
onDraw是真正地開始對視圖進行繪製。
在Andorid官方文檔中將該過程概況成了六步:
第一步:繪製背景:Draw the background
第二步:保存當前畫布的堆棧狀態並在該畫布上建立Layer用於繪製View在滑動時的邊框漸變效果,一般狀況下咱們不須要處理這一步:If necessary, save the canvas’ layers to prepare for fading。
第三步:繪製View的內容:Draw view’s content。這一步是整個draw階段的核心,在此會調用onDraw()方法繪製View的內容。 以前咱們在分析layout的時候發現onLayout()方法是一個抽象方法,具體的邏輯由ViewGroup的子類去實現。與之相似,在此onDraw()是一個空方法;由於每一個View所要繪製的內容不一樣,因此須要由具體的子View去實現各自不一樣的需求。
第四步:調用dispatchDraw()繪製View的子View:Draw children
第五步:繪製當前視圖在滑動時的邊框漸變效果,一般咱們是不須要處理這一步的:If necessary, draw the fading edges and restore layers
第六步:繪製View的滾動條:Draw decorations (scrollbars for instance)
在繪圖時須要明確四個核心的東西(basic components):
用什麼工具畫?
這個小問題很簡單,咱們須要用一支畫筆(Paint)來繪圖。
固然,咱們能夠選擇不一樣顏色的筆,不一樣大小的筆。
把圖畫在哪裏呢?
咱們把圖畫在了Bitmap上,它保存了所繪圖像的各個像素(pixel)。
也就是說Bitmap承載和呈現了畫的各類圖形。
畫的內容?
根據本身的需求畫圓,畫直線,畫路徑。
怎麼畫?
調用canvas執行繪圖操做。
好比,canvas.drawCircle(),canvas.drawLine(),canvas.drawPath()將咱們須要的圖像畫出來。
關於繪製View的內容,這裏就很少作討論,之後實戰中根據實際狀況來進行詳細的分析,繼續下一個生命週期。
9.View生命週期:onWindowFocusChanged
瞅一眼源碼:
判斷view是否獲取焦點,參數hasWindowFocus 對應返回true 和false 能夠用來判斷view是否進出後臺。第一次進入當前Activity的時候,返回true,返回上一個Activity,返回了false。