目錄
從0到1Android自定義View(二) 分類和核心函數(shù).png
一、自定義 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)頁(yè)實(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ù)。
自定義view的流程圖.jpg
(1)構(gòu)造函數(shù)
構(gòu)造函數(shù)是View的入口妇萄,可以用于初始化一些的內(nèi)容蜕企,和獲取自定義屬性
View的構(gòu)造函數(shù)有四種重載
自定義View的構(gòu)造函數(shù).png
從上面的圖也可以看出,自定 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ù)基茵,我們?cè)撛趺催x擇呢奋构?
比如上面圖片顯示的自定義的 MyView ,繼承 View 對(duì)象拱层,如果我們想在普通的代碼中新建一個(gè) MyView弥臼,可以直接使用一個(gè)參數(shù)的構(gòu)造函數(shù),這也是大多數(shù)選擇使用的根灯。
一個(gè)參數(shù)的構(gòu)造函數(shù).png
那么兩個(gè)參數(shù)的構(gòu)造函數(shù)什么時(shí)候使用呢径缅?比如有時(shí)候在 xml 中添加一個(gè)自定義 View 掺栅,并且加了一些布局屬性,寬高屬性以及 margin 屬性纳猪,這些屬性會(huì)存放在第二個(gè)構(gòu)造函數(shù)的 AttributeSet 參數(shù)里氧卧。
兩個(gè)參數(shù)的構(gòu)造函數(shù).png
有三個(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(測(cè)量 View 大欣搪臁)
這個(gè)函數(shù)有什么用呢妇押?將這個(gè)問題轉(zhuǎn)化一下,就是問為什么要測(cè)量 View 的大小呢姓迅?
因?yàn)?View 的大小不僅由自身所決定敲霍,同時(shí)也會(huì)受到父控件的影響,為了我們的控件能更好的適應(yīng)各種情況丁存,一般會(huì)自己進(jìn)行測(cè)量
onMeasure.png
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() 方法植捎,將測(cè)量得到的寬高傳進(jìn)去供 layout 使用衙解。
onMeasure流程.png
在實(shí)際運(yùn)用之中只需要記住有測(cè)量模式有三種,用 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ù)婶溯。
onSizeChanged.png
(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í)際繪制的部分。
onDraw.png
一般自定義控件耗費(fèi)心思最多的就是這個(gè)方法了遭庶,需要在這個(gè)方法里宁仔,用 Paint 在 Canvas 上畫出你想要的圖案。如果是直接繼承的 View峦睡,那么在重寫 onDraw 的方法是時(shí)候完全可以把 super.ondraw(canvas) 刪掉翎苫,因?yàn)樗哪J(rèn)實(shí)現(xiàn)是空。
注:非原創(chuàng)榨了,不裝逼煎谍,不虛假。