前言
這篇主要還是介紹一些知識(shí)點(diǎn)井佑,包括上一篇的知識(shí)點(diǎn)在內(nèi),我們都是需要理解眠寿,只有這樣躬翁,才能更好的制作更多酷炫的自定義 View 。當(dāng)然每一篇文章都會(huì)越來越深入盯拱,一步一個(gè)臺(tái)階盒发,慢慢攀登例嘱。
目錄
一、自定義 View 分類
常見的 Android 自定義 View 主要有兩種類型:
1宁舰、組合控件
通過 Android 的基礎(chǔ)控件(TextView拼卵、ImageView、Button蛮艰、ProgressBar 等)組合而成腋腮,比如下拉刷新、瀑布流控件壤蚜、帶左/右滑功能的控件即寡、視頻控件等,這種自定義View的難點(diǎn)在于程序的邏輯處理
2袜刷、完全自定義控件
繼承自 View聪富、TextureView 或 SurfaceView ,然后重寫核心的回調(diào)方法著蟹,以View 為例墩蔓,按需復(fù)寫其構(gòu)造、onMeasure萧豆、onLayout奸披、onTouchEvent、onDraw炕横、onAttachedToWindow源内、onDetachedFromWindow 等方法,這種自定義 View 的難點(diǎn)在于程序的設(shè)計(jì)份殿、效率優(yōu)化和排版膜钓,比如輸入法中的手寫控件、圖文混排控件(現(xiàn)在很多都是通過webview加載網(wǎng)頁實(shí)現(xiàn)了)卿嘲、個(gè)性化進(jìn)度條颂斜、彈幕顯示控件、Markdown控件拾枣、IDE代碼編輯控件等
注意:
我們需要合理的使用自定義 View 沃疮,千萬不能濫用,不要?jiǎng)硬粍?dòng)就自定義 View 梅肤,基礎(chǔ)控件能完成的工能司蔬,千萬別自定義 View,因?yàn)榛A(chǔ)空間 Android姨蝴,本身就有性能優(yōu)化的俊啼,自定義 View 的價(jià)值在于做到基礎(chǔ)控件無法做到的效果,為應(yīng)用的表現(xiàn)增色左医;授帕,將公用的交互效果提取成自定義控件同木,方便復(fù)用,減少不必要的重復(fù)勞動(dòng)跛十。
二彤路、自定義 View 核心知識(shí)點(diǎn)
這部分主要是介紹自定義 View 的核心知識(shí)點(diǎn),上面提到過芥映,完全自定義 View 通常是繼承 View 洲尊,TextureView,SurfaceView屏轰,所以先來了解下這三者之間的區(qū)別所在颊郎。
1、 View霎苗、SurfaceView姆吭、TextureView 的區(qū)別
View
普通的 View,與宿主窗口共享同一個(gè)繪圖表面唁盏,UI 在主線程中繪制内狸,在有無硬件加速的情況下都能工作(沒有硬件加速的情況下,canvas 的有些方法會(huì)失效)
SurfaceView
繼承自 View厘擂,繪制和顯示效率高昆淡,因?yàn)閾碛歇?dú)立的繪圖表面,UI 在一個(gè)獨(dú)立的線程中進(jìn)行繪制刽严,不會(huì)占用主線程的資源昂灵。SurfaceView 的使用和普通的 View 不一樣,需要結(jié)合 SurfaceHodler 一起使用舞萄。因?yàn)楹退拗鞔翱诓皇枪蚕硗粋€(gè)繪圖表面的原因眨补,對(duì)其做動(dòng)畫操作可能會(huì)得不到想要的效果
TextureView
繼承自 View,與 SurfaceView 相比倒脓,TextureView 不會(huì)創(chuàng)建一個(gè)單獨(dú)的繪圖表面撑螺,這使得它可以像一般的 View 一樣執(zhí)行一些變換操作,比如移動(dòng)崎弃、動(dòng)畫等等甘晤,但 TextureView 必須在硬件加速開啟的窗口中才能正常工作;
2饲做、 幾個(gè)重要的函數(shù)
最后通過自定義 View 的流程圖來了解一下自定義 View 幾個(gè)重要的函數(shù)线婚。
(1)構(gòu)造函數(shù)
構(gòu)造函數(shù)是View的入口,可以用于初始化一些的內(nèi)容盆均,和獲取自定義屬性
View的構(gòu)造函數(shù)有四種重載
從上面的圖也可以看出酌伊,自定 View 的構(gòu)造函數(shù)的參數(shù)最多有四個(gè),而且有四個(gè)參數(shù)的構(gòu)造函數(shù)只能在 API 21 以上使用缀踪,因此四個(gè)參數(shù)的構(gòu)造函數(shù)先不考慮居砖,不過我們也需要了解這四個(gè)參數(shù)具體代表什么?
Context: View 中隨處都會(huì)用到
AttributeSet: XML 屬性(從 XML inflate 的時(shí)候使用)
int defStyleAttr: 應(yīng)用到 View 的默認(rèn)風(fēng)格(定義在主題中)
int defStyleRes: 如果沒有使用 defStyleAttr驴娃,應(yīng)用到 View 的默認(rèn)風(fēng)格
那么這里就有個(gè)問題了奏候,有四種構(gòu)造函數(shù),我們該怎么選擇呢唇敞?
比如上面圖片顯示的自定義的 MyView 蔗草,繼承 View 對(duì)象,如果我們想在普通的代碼中新建一個(gè) MyView疆柔,可以直接使用一個(gè)參數(shù)的構(gòu)造函數(shù)咒精,這也是大多數(shù)選擇使用的。
那么兩個(gè)參數(shù)的構(gòu)造函數(shù)什么時(shí)候使用呢旷档?比如有時(shí)候在 xml 中添加一個(gè)自定義 View 模叙,并且加了一些布局屬性,寬高屬性以及 margin 屬性鞋屈,這些屬性會(huì)存放在第二個(gè)構(gòu)造函數(shù)的 AttributeSet 參數(shù)里范咨。
有三個(gè)參數(shù)的構(gòu)造函數(shù)比第二個(gè)構(gòu)造函數(shù)多了一個(gè) int 型的值,名字叫 defStyleAttr 厂庇,從名稱上判斷渠啊,這是一個(gè)關(guān)于自定義屬性的參數(shù)。第三個(gè)構(gòu)造函數(shù)不會(huì)被系統(tǒng)默認(rèn)調(diào)用权旷,而是需要我們自己去顯式調(diào)用替蛉,比如在第二個(gè)構(gòu)造函數(shù)里調(diào)用調(diào)用第三個(gè)函數(shù),并將第三個(gè)參數(shù)設(shè)為0 拄氯。defStyleAttr 指定的是在Theme style 定義的一個(gè) attr躲查,它的類型是 reference 主要生效在 obtainStyledAttributes 方法里,obtainStyledAttributes 方法有四個(gè)參數(shù)坤邪,第三個(gè)參數(shù)是 defStyleAttr 熙含,第四個(gè)參數(shù)是自己指定的一個(gè) style ,當(dāng)且僅當(dāng) defStyleAttr 為 0 或者在 Theme 中找不到 defStyleAttr 指定的屬性時(shí)艇纺,第四個(gè)參數(shù)才會(huì)生效怎静,這些指的都是默認(rèn)屬性,當(dāng)在 xml 里面定義的黔衡,就以在 xml 文件里指定的為準(zhǔn)蚓聘,所以優(yōu)先級(jí)大概是:xml>style>defStyleAttr>defStyleRes>Theme 指定,當(dāng)defStyleAttr 為 0 時(shí)盟劫,就跳過 defStyleAttr 指定的 reference 夜牡,所以一般用 0 就能滿足一些基本開發(fā)。
(2)onMeasure(測量 View 大小)
這個(gè)函數(shù)有什么用呢塘装?將這個(gè)問題轉(zhuǎn)化一下急迂,就是問為什么要測量 View 的大小呢?
因?yàn)?View 的大小不僅由自身所決定蹦肴,同時(shí)也會(huì)受到父控件的影響僚碎,為了我們的控件能更好的適應(yīng)各種情況,一般會(huì)自己進(jìn)行測量
MeasureSpce 的 mode 有三種:EXACTLY, AT_MOST阴幌,UNSPECIFIED勺阐,除去 UNSPECIFIED 不談,其他兩種 mode:
當(dāng)父布局是 EXACTLY 時(shí)矛双,子控件確定大小或者 match_parent渊抽,mode 都是 EXACTLY,子控件是 wrap_content 時(shí)议忽,mode 為 AT_MOST懒闷;
當(dāng)父布局是 AT_MOST 時(shí),子控件確定大小徙瓶,mode 為 EXACTLY毛雇,子控件 wrap_content 或者 match_parent 時(shí),mode 為 AT_MOST侦镇。
所以在確定控件大小時(shí)灵疮,需要判斷 MeasureSpec 的 mode,不能直接用 MeasureSpec 的 size壳繁。在進(jìn)行一些邏輯處理以后震捣,調(diào)用 setMeasureDimension() 方法,將測量得到的寬高傳進(jìn)去供 layout 使用闹炉。
在實(shí)際運(yùn)用之中只需要記住有測量模式有三種蒿赢,用 MeasureSpec 的 getSize是獲取數(shù)值, getMode是獲取模式即可渣触。如果對(duì) View 的寬高進(jìn)行修改了羡棵,不要調(diào)用 super.onMeasure( widthMeasureSpec, heightMeasureSpec); 要調(diào)用 setMeasuredDimension( widthsize, heightsize); 這個(gè)函數(shù)。
(3)onSizeChanged(確定 View 大行嶙辍)
那么這個(gè)函數(shù)又是什么時(shí)候調(diào)用呢皂冰?
這個(gè)函數(shù)在視圖大小發(fā)生改變時(shí)調(diào)用。
那么問題又來了养篓,上面的 onMeasure 函數(shù)中不是說對(duì) View 的寬高進(jìn)行了修改后秃流,要調(diào)用 setMeasuredDimension 嗎?調(diào)用這個(gè)方法后柳弄,View 的大小基本已經(jīng)確定了啊舶胀,View 的視圖大小還會(huì)發(fā)生變化嗎?這是因?yàn)?View 的大小不僅僅只是由其本身來決定的,也受它的父控件影響嚣伐,所以在確定 View 大小的時(shí)候最好使用系統(tǒng)提供的 onSizeChanged 回調(diào)函數(shù)糖赔。
(4)onLayout(確定子 View 布局位置)
確定布局的函數(shù)是 onLayout ,它用于確定子 View 的位置纤控,在自定義 ViewGroup 中會(huì)用到挂捻,他調(diào)用的是子 View 的 layout 函數(shù)。比如船万,有時(shí)候我們自定義 View 的時(shí)候,需要獲取 View 的一些信息就需要用到這個(gè)函數(shù)骨田。當(dāng)然耿导,如果是單純的 View 就沒與必要重寫這個(gè)方法,為什么這么說呢态贤?
因?yàn)閱渭兊?View 舱呻,不是一個(gè) View 容器,沒有子 View 悠汽,而 onLayout 方法里主要是具體擺放子View 的位置箱吕,水平擺放或者垂直擺放,所以在單純的自定義 View 是不需要重寫 onLayout 方法柿冲。不過需要注意的一點(diǎn)是茬高,子 View 的 margin 屬性是否生效就要看 parent 是否在自身的 onLayout 方法進(jìn)行處理,而 View 的 padding 屬性是在 onDraw 方法中生效的
(5)onDraw(繪制內(nèi)容)
重頭戲假抄,onDraw怎栽,也就是是實(shí)際繪制的部分。
一般自定義控件耗費(fèi)心思最多的就是這個(gè)方法了宿饱,需要在這個(gè)方法里熏瞄,用 Paint 在 Canvas 上畫出你想要的圖案。如果是直接繼承的 View谬以,那么在重寫 onDraw 的方法是時(shí)候完全可以把 super.ondraw(canvas) 刪掉强饮,因?yàn)樗哪J(rèn)實(shí)現(xiàn)是空。