前言
這篇主要還是介紹一些知識點,包括上一篇的知識點在內,我們都是需要理解,只有這樣,才能更好的製作更多酷炫的自定義 View 。當然每一篇文章都會越來越深入,一步一個臺階,慢慢攀登。
目錄
一、自定義 View 分類
常見的 Android 自定義 View 主要有兩種型別:
1、組合控制元件
通過 Android 的基礎控制元件(TextView、ImageView、Button、ProgressBar 等)組合而成,比如下拉重新整理、瀑布流控制元件、帶左/右滑功能的控制元件、視訊控制元件等,這種自定義View的難點在於程式的邏輯處理
2、完全自定義控制元件
繼承自 View、TextureView 或 SurfaceView ,然後重寫核心的回撥方法,以View 為例,按需複寫其構造、onMeasure、onLayout、onTouchEvent、onDraw、onAttachedToWindow、onDetachedFromWindow 等方法,這種自定義 View 的難點在於程式的設計、效率優化和排版,比如輸入法中的手寫控制元件、圖文混排控制元件(現在很多都是通過webview載入網頁實現了)、個性化進度條、彈幕顯示控制元件、Markdown控制元件、IDE程式碼編輯控制元件等
注意:
我們需要合理的使用自定義 View ,千萬不能濫用,不要動不動就自定義 View ,基礎控制元件能完成的功能,千萬別自定義 View,因為基礎空間 Android,本身就有效能優化的,自定義 View 的價值在於做到基礎控制元件無法做到的效果,為應用的表現增色;,將公用的互動效果提取成自定義控制元件,方便複用,減少不必要的重複勞動。
二、自定義 View 核心知識點
這部分主要是介紹自定義 View 的核心知識點,上面提到過,完全自定義 View 通常是繼承 View ,TextureView,SurfaceView,所以先來了解下這三者之間的區別所在。
1、 View、SurfaceView、TextureView 的區別
View
普通的 View,與宿主視窗共享同一個繪圖表面,UI 在主執行緒中繪製,在有無硬體加速的情況下都能工作(沒有硬體加速的情況下,canvas 的有些方法會失效)
SurfaceView
繼承自 View,繪製和顯示效率高,因為擁有獨立的繪圖表面,UI 在一個獨立的執行緒中進行繪製,不會佔用主執行緒的資源。SurfaceView 的使用和普通的 View 不一樣,需要結合 SurfaceHodler 一起使用。因為和宿主視窗不是共享同一個繪圖表面的原因,對其做動畫操作可能會得不到想要的效果
TextureView
繼承自 View,與 SurfaceView 相比,TextureView 不會建立一個單獨的繪圖表面,這使得它可以像一般的 View 一樣執行一些變換操作,比如移動、動畫等等,但 TextureView 必須在硬體加速開啟的視窗中才能正常工作;
2、 幾個重要的函式
最後通過自定義 View 的流程圖來了解一下自定義 View 幾個重要的函式。
(1)建構函式
建構函式是View的入口,可以用於初始化一些的內容,和獲取自定義屬性
View的建構函式有四種過載
從上面的圖也可以看出,自定 View 的建構函式的引數最多有四個,而且有四個引數的建構函式只能在 API 21 以上使用,因此四個引數的建構函式先不考慮,不過我們也需要了解這四個引數具體代表什麼?
Context: View 中隨處都會用到
AttributeSet: XML 屬性(從 XML inflate 的時候使用)
int defStyleAttr: 應用到 View 的預設風格(定義在主題中)
int defStyleRes: 如果沒有使用 defStyleAttr,應用到 View 的預設風格
那麼這裡就有個問題了,有四種建構函式,我們該怎麼選擇呢?
比如上面圖片顯示的自定義的 MyView ,繼承 View 物件,如果我們想在普通的程式碼中新建一個 MyView,可以直接使用一個引數的建構函式,這也是大多數選擇使用的。
那麼兩個引數的建構函式什麼時候使用呢?比如有時候在 xml 中新增一個自定義 View ,並且加了一些佈局屬性,寬高屬性以及 margin 屬性,這些屬性會存放在第二個建構函式的 AttributeSet 引數裡。
有三個引數的建構函式比第二個建構函式多了一個 int 型的值,名字叫 defStyleAttr ,從名稱上判斷,這是一個關於自定義屬性的引數。第三個建構函式不會被系統預設呼叫,而是需要我們自己去顯式呼叫,比如在第二個建構函式裡呼叫呼叫第三個函式,並將第三個引數設為0 。defStyleAttr 指定的是在Theme style 定義的一個 attr,它的型別是 reference 主要生效在 obtainStyledAttributes 方法裡,obtainStyledAttributes 方法有四個引數,第三個引數是 defStyleAttr ,第四個引數是自己指定的一個 style ,當且僅當 defStyleAttr 為 0 或者在 Theme 中找不到 defStyleAttr 指定的屬性時,第四個引數才會生效,這些指的都是預設屬性,當在 xml 裡面定義的,就以在 xml 檔案裡指定的為準,所以優先順序大概是:xml>style>defStyleAttr>defStyleRes>Theme 指定,當defStyleAttr 為 0 時,就跳過 defStyleAttr 指定的 reference ,所以一般用 0 就能滿足一些基本開發。
(2)onMeasure(測量 View 大小)
這個函式有什麼用呢?將這個問題轉化一下,就是問為什麼要測量 View 的大小呢?
因為 View 的大小不僅由自身所決定,同時也會受到父控制元件的影響,為了我們的控制元件能更好的適應各種情況,一般會自己進行測量
MeasureSpce 的 mode 有三種:EXACTLY, AT_MOST,UNSPECIFIED,除去 UNSPECIFIED 不談,其他兩種 mode:
當父佈局是 EXACTLY 時,子控制元件確定大小或者 match_parent,mode 都是 EXACTLY,子控制元件是 wrap_content 時,mode 為 AT_MOST;
當父佈局是 AT_MOST 時,子控制元件確定大小,mode 為 EXACTLY,子控制元件 wrap_content 或者 match_parent 時,mode 為 AT_MOST。
所以在確定控制元件大小時,需要判斷 MeasureSpec 的 mode,不能直接用 MeasureSpec 的 size。在進行一些邏輯處理以後,呼叫 setMeasureDimension() 方法,將測量得到的寬高傳進去供 layout 使用。
在實際運用之中只需要記住有測量模式有三種,用 MeasureSpec 的 getSize是獲取數值, getMode是獲取模式即可。如果對 View 的寬高進行修改了,不要呼叫 super.onMeasure( widthMeasureSpec, heightMeasureSpec); 要呼叫 setMeasuredDimension( widthsize, heightsize); 這個函式。
(3)onSizeChanged(確定 View 大小)
那麼這個函式又是什麼時候呼叫呢?
這個函式在檢視大小發生改變時呼叫。
那麼問題又來了,上面的 onMeasure 函式中不是說對 View 的寬高進行了修改後,要呼叫 setMeasuredDimension 嗎?呼叫這個方法後,View 的大小基本已經確定了啊,View 的檢視大小還會發生變化嗎?這是因為 View 的大小不僅僅只是由其本身來決定的,也受它的父控制元件影響,所以在確定 View 大小的時候最好使用系統提供的 onSizeChanged 回撥函式。
(4)onLayout(確定子 View 佈局位置)
確定佈局的函式是 onLayout ,它用於確定子 View 的位置,在自定義 ViewGroup 中會用到,他呼叫的是子 View 的 layout 函式。比如,有時候我們自定義 View 的時候,需要獲取 View 的一些資訊就需要用到這個函式。當然,如果是單純的 View 就沒與必要重寫這個方法,為什麼這麼說呢?
因為單純的 View ,不是一個 View 容器,沒有子 View ,而 onLayout 方法裡主要是具體擺放子View 的位置,水平擺放或者垂直襬放,所以在單純的自定義 View 是不需要重寫 onLayout 方法。不過需要注意的一點是,子 View 的 margin 屬性是否生效就要看 parent 是否在自身的 onLayout 方法進行處理,而 View 的 padding 屬性是在 onDraw 方法中生效的
(5)onDraw(繪製內容)
重頭戲,onDraw,也就是是實際繪製的部分。
一般自定義控制元件耗費心思最多的就是這個方法了,需要在這個方法裡,用 Paint 在 Canvas 上畫出你想要的圖案。如果是直接繼承的 View,那麼在重寫 onDraw 的方法是時候完全可以把 super.ondraw(canvas) 刪掉,因為它的預設實現是空。