之前對(duì)自定義View總是有很多的畏懼钧嘶,不知道從什么地方入手乐疆,近期感覺不能再這樣一直使用別人的代碼了筋夏,要試著自己去真正的了解一下Android的底層了扑眉。所以買來任教主的《Android開發(fā)藝術(shù)探索》脑奠,仔細(xì)一讀還真有收獲基公。
現(xiàn)在記錄下來,為跟我一樣的新手提供一些學(xué)習(xí)路上的幫助宋欺。
在我的理解轰豆,自定義View分為以下的幾類:
- 繼承View重寫onDraw方法,一般相對(duì)來說比較簡(jiǎn)單齿诞。
- 繼承ViewGroup派生出特殊的Layout酸休,這里可以做的更加復(fù)雜。
- 繼承自特定的View來實(shí)現(xiàn)的特定的功能
對(duì)于第一種祷杈,相對(duì)來說要比較簡(jiǎn)單斑司,一般就是畫個(gè)圓畫個(gè)方塊之類的,然后復(fù)雜一點(diǎn)可能就要加上動(dòng)畫但汞;第二種宿刮,可以實(shí)現(xiàn)更加復(fù)雜的動(dòng)畫效果,動(dòng)畫之間可能會(huì)有一些比較復(fù)雜的關(guān)聯(lián)之類的私蕾;最后一種僵缺,繼承自某一個(gè)View,例如繼承自一個(gè)Edittext
是目,我們可以自己加入一些對(duì)EditText中內(nèi)容的特殊處理谤饭。
今天只說最簡(jiǎn)單的一種:繼承View并實(shí)現(xiàn)動(dòng)畫,以一個(gè)加載等待控件為例,實(shí)現(xiàn)的效果如下
STEP 1 編寫一個(gè)類繼承自View懊纳,并重寫構(gòu)造方法
- 一般在構(gòu)造方法中揉抵,我們會(huì)抽取出一個(gè)init()方法來進(jìn)行對(duì)View的初始化工作,例如初始化畫筆Paint嗤疯、路徑Path等等冤今,總之哪些只有在第一次創(chuàng)建View的時(shí)候才會(huì)構(gòu)建的參數(shù)都要在這里來初始化。
- 一般會(huì)重寫三個(gè)構(gòu)造方法茂缚,這三個(gè)構(gòu)造方法有不同的參數(shù)戏罢,其實(shí)是代表了View不同的初始化方式屋谭,具體如下:
public LoadingPopPoint(Context context) { super(context); //這個(gè)是在java代碼中構(gòu)建的時(shí)候調(diào)用 }
public LoadingPopPoint(Context context, AttributeSet attrs) { super(context, attrs); //這個(gè)是在使用xml構(gòu)建的時(shí)候并且沒有指定style的時(shí)候調(diào)用 }
public LoadingPopPoint(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); //這是指定了style的時(shí)候調(diào)用的方法 }
一般都會(huì)在構(gòu)造方法中調(diào)用init來保證初始化的完成
STEP 2 重寫onDraw方法,在onDraw方法中使用canvas繪圖
使用在init方法中初始化的paint對(duì)象龟糕,來繪制圖形桐磁,其實(shí)動(dòng)畫的原理就和老式的電影放映是一樣的,一幀一幀地刷新就好讲岁,其實(shí)就是在onDraw方法中不停的隔一段時(shí)間就重畫一下我擂。這時(shí)就要postInvalidateDelayed(50)
這個(gè)方法登場(chǎng)了,這個(gè)方法會(huì)每隔指定的時(shí)間來調(diào)用View的invalidate()
方法缓艳,最終會(huì)重新調(diào)用onDraw
方法校摩,完成一個(gè)周期,所以如果想控制動(dòng)畫阶淘,我們就可以定義一個(gè)全局的progress
變量衙吩,在onDraw方法中不斷的遞增,然后繪制圖形的時(shí)候根據(jù)這個(gè)progress來做出相應(yīng)的調(diào)整溪窒,圖形不就動(dòng)起來了嗎坤塞?
好了,上代碼:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (int i = 0; i < pointNumber; i++) {
progress[i] = progress[i] + STEP;
radius = getRadiusByProgress(progress[i]);
canvas.drawCircle((float) (getWidth() / 2 + x), getHeight() / 2, radius, paints.get(i));
}
//redraw the view per 40 milliseconds ,and show the animation
postInvalidateDelayed(40);
}
這里的這個(gè)getRadiusByProgress
方法中做了一些數(shù)學(xué)的計(jì)算澈蚌,計(jì)算了應(yīng)該在什么地方畫圓尺锚,以及這個(gè)圓的半徑之類的信息,而這里正是這個(gè)View的核心部分惜浅,真心感覺數(shù)學(xué)很重要啊。伏嗜。坛悉。。這個(gè)算法想了好半天才想明白承绸。裸影。。
其實(shí)那些比較炫酷的View動(dòng)畫的實(shí)現(xiàn)军熏,后面都有很強(qiáng)大的算法在做支撐轩猩,總結(jié)起來其實(shí)就是:
一個(gè)好的View動(dòng)畫=強(qiáng)大的算法+對(duì)Android系統(tǒng)API的熟練應(yīng)用
個(gè)人感覺這個(gè)是新手入門的比較不錯(cuò)的例子,好了荡澎,代碼是最好的老師均践,有什么問題看代碼咯 GitHub地址