? 前言:自打?qū)W安卓第一天起就聽過自定義View,記得當(dāng)時實習(xí)的項目里要弄一個隨機(jī)驗證碼当编,當(dāng)時百度了一份代碼丛楚,打開發(fā)現(xiàn)就一個文件族壳,叫一個xxxCodeView,放進(jìn)布局跑起來就出現(xiàn)了一個隨機(jī)的驗證碼框子趣些!當(dāng)時連Canvas仿荆,Paint都沒摸過的我覺得這份代碼的作者好牛逼啊,廖廖十來行代碼就搞定坏平。想起來真是記憶猶新赖歌!還好在后來的工作學(xué)習(xí)中時不時的從一些公眾號上有讀到一些自定義View的相關(guān)知識,慢慢的積累功茴,然后自己也會照著敲一敲。
不同的是我不喜歡一碼不差的對著源碼敲孽亲,讀懂源碼后我習(xí)慣給自己提出一點點和源碼不同的需求坎穿,然后結(jié)合自己對源碼的理解一點一點的去實現(xiàn)這個需求~ 這算是我學(xué)習(xí)自定義View的一點點小心得,在這里分享給大家先返劲!
言歸正傳玲昧,這里結(jié)合本人平時從公眾號上看來的幾個小demo稍稍改裝下做演示,簡單的介紹一下常見的幾種自定義View以及自定義View的大致方法步驟篮绿!水平有限孵延,有些地方裝逼了那也是點到即止,切勿拆穿亲配,謝謝尘应。
一惶凝、自定義View的大致步驟
? 1.新建自定義View的文件,繼承View或繼承一些系統(tǒng)自帶組件(TextView犬钢,LinearLayout)苍鲜。
? 2.復(fù)寫一些類的構(gòu)造方法,在里面獲取這個自定義View的自定義屬性(如果有)玷犹。
? 3.重寫view的onMeasure方法(這里基本上就是針對MeasureSpec.EXACTLY或者M(jìn)easureSpec.UNSPECIFIED做一些處理混滔,大多數(shù)時候代碼都是相同的,當(dāng)然具體需求具體再改歹颓,標(biāo)準(zhǔn)代碼文末放公眾號文章的鏈接坯屿,可以自己去下源碼看)
4.重寫onDraw(這個最重要了,直接決定你的自定義View呈現(xiàn)出什么東東巍扛,什么樣子)
5.其他需要重寫的方法總結(jié):
a.涉及到觸碰交互的話重寫onTouchEvent等方法(事件分發(fā)機(jī)制领跛,,电湘,這是個大課題)
b.需要刷新或者重新繪制View的時候調(diào)用postInvalidate()和Invalidate(),前者在子線程中調(diào)用~
二隔节、幾個簡單的例子,幾行關(guān)鍵代碼
示例一:進(jìn)度(時鐘寂呛,計數(shù))View
如圖所示怎诫,中間顯示計數(shù)(或者倒計時),白色被黃色覆蓋代表進(jìn)度的遞增(這里可以是顏色覆蓋贷痪,也可以是白色慢慢消失)幻妓。可以根據(jù)具體需求做修改劫拢。
實現(xiàn)思路分析:這是一個比較簡單的自定義View肉津,幾乎就是在一個Canvas上用畫筆畫N條線,自定義好線的長寬舱沧。值得一提的是將這個作為demo是為了介紹這類用Paint在Canvas上draw東西的自定義View有一個很重要的方法就是Canvas的rotate方法妹沙,圖中有54條線,不可能draw的時候找到每一條的坐標(biāo)然后去draw(當(dāng)然如果你變態(tài)到一定要去算其實是可以算:我是sin你是cos~咋倆求tan~咳咳咳熟吏!咋們是在說三角函數(shù)對吧>嗵恰)。這里我的做法是通過中心點center和View的半徑以及線的長度算出
數(shù)字正上方第一條線的起始點(x.y)?
PositionX =center; ? ? PositionY =center-out_radius; ?
然后畫出第一條線 canvas.drawLine(textPositionX, textPositionY, textPositionX, textPositionY -hightOfLine,out_paint); ?
//不要罵人牵寺,下面有注釋 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
每畫好一條線之后旋轉(zhuǎn)一定角度繼續(xù)畫悍引,另外View下方有90度的區(qū)域是沒有線的,所以旋轉(zhuǎn)的時候注意避開135~225這90度
至于根據(jù)業(yè)務(wù)邏輯動態(tài)變化顏色下圖有說明帽氓,只需要給個公開的方法設(shè)置象征進(jìn)度的progressDegree~然后調(diào)用postInvalidate()(我外部是在子線程中改變進(jìn)度趣斤,所以這里用postInvalidate,與大致步驟的第五點b 首尾呼應(yīng)一下有木有~)
核心代碼:
示例二:刮刮樂+卡片 組合View?
(其實就是抄了公眾號上的兩個demo強(qiáng)行一起用做示例撐場面而已~~這么坦誠~老Fe扎Zn不黎休?)
如圖浓领,這里展示的就是刮刮卡效果和特殊形狀的view(當(dāng)然也可以圖片實現(xiàn)玉凯,不過沒有直接自定義view來的靈活)
實現(xiàn)思路分析: 首先來說說這個刮刮卡效果
灰色的背景,手指觸摸滑動的地方變成透明镊逝,從而顯示出下方的圖片(或者其他View)壮啊。 其實說白了,就是在一張純灰色的bitmap上撑蒜,新建Paint并且設(shè)置成透明顏色歹啼,然后畫出手指的移動軌跡Path,用這個軌跡path去覆蓋掉bitmap上的灰色
即:透明色覆蓋掉灰色? ? 這里就涉及到Paint一個很重要的方法setXfermode(newPorterDuffXfermode(PorterDuff.Mode.SRC_IN));
關(guān)于setXfermode的參數(shù)有個經(jīng)典的圖片幫助理解座菠,這里我用的是PorterDuff.Mode.SRC_IN狸眼,也就是取 “bitmap上原色塊(純灰)和新色塊(手指劃過的地方的透明色)的交集的新色塊”? 這句話可能比較繞口,但實際你看看下面鏈接里的效果對比圖你就能理解了(看不懂浴滴?多看幾次)
http://blog.csdn.net/edisonlg/article/details/7084977
經(jīng)過分析拓萌,這個自定義View的主要成員也就有了
一個Bitmap,上面繪制灰色底色升略,和手指劃過的透明色規(guī)矩
一個Canvas微王,用來裝載這個Bitmap
一個Paint,手指的畫筆品嚣,設(shè)置覆蓋模式(取交集炕倘,顯示頂層)
一個Path,記錄手指滑動的軌跡
每次手指觸到屏幕時:
記錄下x翰撑,y罩旋,連入path,并調(diào)用invalidate刷新界面(即調(diào)用onDraw重繪)
這時候與Bitmap關(guān)聯(lián)的canvas會將透明軌跡畫到bitmap上眶诈,同時抹去軌跡下面的灰色涨醋,然后把繪制好的bitmap通過系統(tǒng)提供的canvas來展示,值得注意的是ondraw里的canvas和我們自定義的canvas有些不同逝撬,這個系統(tǒng)提供的canvas僅用來顯示最終成像的bitmap浴骂,如果直接用這個canvas來畫灰色背景再畫手指軌跡的話最終得不到想要的效果(原因目前沒搞懂,之后發(fā)現(xiàn)了再補上)
擦出功能核心的代碼就這么多了宪潮,最后還有一點就是當(dāng)擦除的面積到達(dá)一定比例后純灰層就直接移除了靠闭,這個粗一看實現(xiàn)起來完全沒有思路,坎炼,這個擦去的面積咋算呢?(同心理陰影面積一樣不好算~)拦键,但是公眾號demo里給了個解決方法挺不錯的谣光,這里直接展示出來
該方法大致的過程就是獲取bitmap的像素數(shù)據(jù),存入int數(shù)組芬为,對每個像素遍歷萄金,如果數(shù)值為0表示這個像素被擦除了蟀悦,最后累計為0的像素數(shù)目,算出百分比氧敢,超過60%就設(shè)置isCoplete為ture日戈,這樣在ondraw的時候就不會去繪制純灰和軌跡了,整個view透明孙乖,放在下面的圖片就顯示出來啦 浙炼。 當(dāng)然整個方法可以會比較耗時,手指拖動不停計算唯袄,放在子線程中異步計算比較安全~嗯弯屈,必須的
(這里我大膽的猜測一下,人臉識別算法里應(yīng)該會涉及到這個恋拷,分析像素资厉,哈哈)
刮刮樂效果到此就分析完了,當(dāng)然技術(shù)有限蔬顾,大部分東西照搬過來有些原理還是不太清楚宴偿,這里留下了兩個問題。
1.示例中這個展示效果的bitmap必須用自己new出來Canvas去綁定诀豁,最后給ondraw中系統(tǒng)提供的Canvas去展示窄刘,why?
2.實際上當(dāng)我的畫筆Paint顏色設(shè)置TRANSPARENT色不論SRC_IN還是DST_IN都實現(xiàn)了擦出的效果且叁,why都哭??逞带?
但是如果設(shè)置成別的顏色就只有SRC_IN才有效果(符合SRC_IN的效果:先畫灰層欺矫,再畫軌跡,取軌跡顏色)展氓。
卡片view效果:
篇幅問題穆趴,,這個小view很簡單遇汞,簡單說一下
1.繼承LinearLayout(或者其他)
2.在view的top和bottom畫上一排的小半圓(小三角未妹,小扇形...想畫啥都行,甚至是bitmap)
重點:確定半圓半徑空入,兩個半圓間間距(view的寬度已知络它,這樣就算出了半圓的個數(shù)circleNum)
核心代碼:同樣在ondraw中
根據(jù)小半圓的個數(shù)循環(huán)上下邊畫圓,值得注意的是啟始的x是寬度減去circleNum個圓直徑再減去circleNum-1個間距后剩下的不足一個直接的長度的一半歪赢,化戳,也就是這一段
示例三:百分比原盤
效果描述:1.外部傳入各個類別的名字,和該類別的數(shù)值 view自動計算百分比然后畫出各色扇形~
? ? ? ? ? ? ? ? ? ? 并且扇形的最外部中心出標(biāo)出名字和百分比(自定義屬性埋凯,Canvas的rotate等)
? ? ? ? ? ? ? ? ? ? 2.整體view可以拖拽轉(zhuǎn)動(觸碰事件的處理点楼,向量的計算)
? ? ? ? ? ? ? ? ? ? 3.慣性轉(zhuǎn)動(屬性動畫扫尖,差值器等)*ps: ?這里我的實現(xiàn)不太好,有bug掠廓,如果你有更好的實現(xiàn)思路可以留言哈
這個示例算是一個綜合點的自定義view换怖,包含的了前兩個簡單自定義view的大部分內(nèi)容還有前面未涉及的觸碰事件處理,廢話不多說直接上思路代碼蟀瞧。
1.實現(xiàn)基本的外形需求
數(shù)據(jù)部分很簡單沉颂,自定義一個ViewItem類,包含名字name和數(shù)值value字段黄橘,你可以再加上顏色等其他屬性
自定義View內(nèi)部存一個List<ViewItem>?datas,用來接受外部的賦值然后初始化view兆览,重點來看看如何畫這個扇形
關(guān)鍵代碼:
文字部分:
畫文字一樣是通過循環(huán)的方式一 一添加,不同的是drawText需要傳入文字的啟始位置x和底邊y塞关,所以同demo1抬探,如果每個類別都精確的去計算的話會灰常痛苦,這里的思路一樣是轉(zhuǎn)動canvas畫布帆赢,每次寫文字都寫在圓心上方半徑高度處小压,這里為了便于理解循環(huán)寫文字時canvas轉(zhuǎn)動的角度的計算,下圖給出了第一次寫 旅游(10.81%) 這段文字canvas轉(zhuǎn)動的角度a和第二次寫 ?美食(28.83%)時轉(zhuǎn)動的角度B的示意圖椰于,怠益,結(jié)合著理解下循環(huán)中計算角度的方法吧~(我知道圖里畫的是阿爾法和貝塔~~只怪win10自帶輸入法不給力啦)
到這里,效果的第一部分核心就已經(jīng)實現(xiàn)了瘾婿,onDraw里就三個方法~就得到了一個不能轉(zhuǎn)~沒鳥用的百分比原盤view
drawOutCircle(canvas);
drawInnerArc(canvas);
drawTypeText(canvas);
2.實現(xiàn)整體view拖拽轉(zhuǎn)動蜻牢,讓這個圓盤看起來不那么沒鳥用~
思考一下,要實現(xiàn)手指放在圓盤上(上下上下左右BABA)拖動的時候圓盤跟著轉(zhuǎn)動
最起碼的是不是應(yīng)該要識別一下手指拖動的方向呢偏陪?
圓盤轉(zhuǎn)動只可能是順時針或者逆時針抢呆,但是對應(yīng)不同的拖動位置和拖動方向組合有很多種,最簡單的笛谦,都是向下拖動 如果拖動的位置在圓盤左側(cè)就應(yīng)該逆時針轉(zhuǎn)抱虐,在右側(cè)就是順時針了。 那么如何來處理這個方向問題呢饥脑?正如前面寫到了恳邀,用向量!T詈洹谣沸!
***以下代碼純屬抄襲,如有雷同~~~要么我抄了你笋颤,要么我們一起抄了別人的***
這里我想起來以前做圖片旋轉(zhuǎn)的時候抄過的一段代碼(圖片被手指拖著繞著中心點旋轉(zhuǎn))
這段代碼在當(dāng)然在onTouch事件里面乳附,用到了向量交叉相乘,PointF等知識點,许溅,有點不可描述(都快還給數(shù)學(xué)老師了),大致的作用就是通過計算邊a,b,c算出邊a轉(zhuǎn)到邊c的角度newDegree秉版,這個角度就是我們要轉(zhuǎn)的角度贤重,角度的正負(fù)包含了方向。
這里在手指move的過程中清焕,這段代碼會不停的跑并蝗,所以其實每次得到的newDegree都很小,有個全局的記錄轉(zhuǎn)動度數(shù)mDegree秸妥,把newDegree累加進(jìn)去
正如最后兩行滚停,拿到了轉(zhuǎn)動角度,累加計算當(dāng)前角度粥惧,通過接口傳給外面~然后在外面調(diào)用實例的setRotation()方法
my_custom.setRotation(mDegree)來同步的實現(xiàn)view的轉(zhuǎn)動键畴。
3.手指松開后,慣性轉(zhuǎn)動效果
由于篇幅和時間(2017年4月28日00:00:24)的原因(其實主要是目前的實現(xiàn)效果自己都不能接受)暫時不寫了突雪,之后有機(jī)會再來補充起惕。