Android自定義View

最近做項(xiàng)目碰到一個(gè)需求,需要實(shí)現(xiàn)如下圖的一個(gè)自定義的view。


icon.jpg

如上圖,還需實(shí)現(xiàn)一個(gè)動(dòng)畫效果,中間的圓形image保持不動(dòng),外面的兩個(gè)圓弧與圓點(diǎn)沿圓心轉(zhuǎn)動(dòng),當(dāng)觸摸view時(shí),整個(gè)view等比例放大,轉(zhuǎn)動(dòng)速度變快,觸摸取消時(shí)恢復(fù)原樣。
自己本身對(duì)Android自定義View的實(shí)現(xiàn)并不是很熟練,在這個(gè)View上卡住了。在后面師傅實(shí)現(xiàn)了這個(gè)View的所有功能后,自己將代碼仔細(xì)學(xué)習(xí)了一遍,有種豁然開朗的感覺。
首先可以看到,我們這個(gè)View應(yīng)該是直接繼承View,看上去貌似是一個(gè)組合的view,由中間的ImageView加上周邊旋轉(zhuǎn)的圓弧組成,然而仔細(xì)考慮后發(fā)現(xiàn),如果采用組合View的寫法,可能會(huì)有縮放比例方面的問題,所以我們選擇了直接繼承View來寫。
首先我們分析一下這個(gè)View,它由中間的圓形View,兩個(gè)圓環(huán),兩個(gè)圓點(diǎn)組成,我們可以記為mAvatar,mGrayRingInner,mGrayRingOuter,mGreenCircle,mRedCircle。其實(shí)思路很簡單,我們先在自定義View的構(gòu)造函數(shù)里面確定各個(gè)部分的長寬參數(shù)、轉(zhuǎn)動(dòng)速度以及觸摸事件,然后在重寫onDraw()函數(shù)進(jìn)行各個(gè)部分的繪制。
那么我們的實(shí)現(xiàn)過程有下列兩個(gè)部分:

  • 構(gòu)造函數(shù)中確定各數(shù)值
  • onDraw()中進(jìn)行繪制

GradientDrawable

在進(jìn)行這兩部分工作之前,我們先介紹一個(gè)GradientDrawable這個(gè)類。
GradientDrawable是Drawable類的直接子類,而Drawable類又是個(gè)什么類呢?

官方文檔是這樣形容的

A Drawable is a general abstraction for "something that can be drawn." Most often you will deal with Drawable as the type of resource retrieved for drawing things to the screen; the Drawable class provides a generic API for dealing with an underlying visual resource that may take a variety of forms. Unlike a View, a Drawable does not have any facility to receive events or otherwise interact with the user.

意思是,Drawable首先是一個(gè)abstract類,它表示“可以被繪制的一些事情”。經(jīng)常我們會(huì)認(rèn)為Drawable是能夠繪制到屏幕上的資源。但是Drawable跟View不一樣,它無法獲取事件,同時(shí)也無法與用戶發(fā)生任何的交互。
那么,既然GradientDrawable是Drawable的直接子類,那么GradientDrawable也能夠被繪制。我們同樣看一下它的官方介紹。

A Drawable with a color gradient for buttons, backgrounds, etc.

可以看出來GradientDrawable是一般被用來繪制按鈕、背景等。GradientDrawable可以繪制如下形狀。
int LINE Shape is a line
int LINEAR_GRADIENT Gradient is linear (default.)
int OVAL Shape is an ellipse
int RADIAL_GRADIENT Gradient is circular.
int RECTANGLE Shape is a rectangle, possibly with rounded corners
int RING Shape is a ring.
int SWEEP_GRADIENT Gradient is a sweep.
那么,我們?nèi)绾问褂肎radientDrawable呢?一般來講,我們通過四個(gè)函數(shù)實(shí)現(xiàn)對(duì)GradientDrawable的使用。

  1. setShape() 設(shè)置形狀,可以設(shè)置如上述七種參數(shù)
  2. setColor() 設(shè)置GradientDrawable的顏色
  3. setBound() 設(shè)置GradientDrawable的邊界,其實(shí)可以理解為我們這個(gè)Drawable的大小。
  4. GradientDrawable.draw(canvas) 最后是將其繪制到畫布canvas上去。

構(gòu)造函數(shù)中的數(shù)值確定

在設(shè)計(jì)中,我們整個(gè)View的大小為95dp*95dp,mOuterRing的直徑大小為85p,mInnerRing的直徑為75dp,mAvatar直徑為65dp。這部分直接看代碼吧。

mViewSize = 95dp;
mGrayRingInner = new GradientDrawable();
mGrayRingInner.setShape(GradientDrawable.OVAL);
mGrayRingInner.setStroke(2, getResources().getColor(R.color.divider));

mGrayRingOuter = new GradientDrawable();
mGrayRingOuter.setShape(GradientDrawable.OVAL);
mGrayRingOuter.setStroke(2, getResources().getColor(R.color.divider));

mGreenCircle = new GradientDrawable();
mGreenCircle.setShape(GradientDrawable.OVAL);
mGreenCircle.setColor(getResources().getColor(R.color.green);
 
mRedCircle = new GradientDrawable();
mRedCircle.setShape(GradientDrawable.OVAL);
mRedCircle.setColor(getResources().getColor(R.color.red));
initDrawableBounds(); // 設(shè)置各個(gè)部分的大小
setOnTouchListener(new OnTouchListener() {
     @Override
     public boolean OnTouch(View v, MotionEvent event) {
     }
});

onDraw()中的繪制

首先我們將不轉(zhuǎn)動(dòng)的部分繪制上去,包括mAvatar, mGrayRingInner和mGrayRingOuter。調(diào)用GradientDrawable的draw(canvas)。

mAvatar.draw(canvas);
mGrayRingInner.draw(canvas);
mGrayRingOuter.draw(canvas);

剩下的兩部分要實(shí)現(xiàn)旋轉(zhuǎn)的動(dòng)畫,我們可以通過不斷繪制畫布,每一次繪制mGreenCircle與mRedCircle進(jìn)行相應(yīng)的坐標(biāo)變換,這樣就可以實(shí)現(xiàn)旋轉(zhuǎn)的動(dòng)畫效果。但是,同時(shí)我們需要保證mAvatar、mGrayRingInner和mGrayRingOuter不旋轉(zhuǎn),則需要調(diào)用canvas的save()和restore()函數(shù)。我們先看看這兩個(gè)函數(shù)的官方說明。

save(). Saves the current matrix and clip onto a private stack.

保存當(dāng)前的matrix放置到一個(gè)私有的棧中去。

restore() .This call balances a previous call to save(), and is used to remove all modifications to the matrix/clip state since the last save call.

save()和restore()成對(duì)出現(xiàn)時(shí),可以有這種效果:保存當(dāng)前的畫布狀態(tài),然后你可以進(jìn)行其他的繪制操作,當(dāng)調(diào)用restore()的時(shí)候,移除掉這些修改(新的繪制還存在)。
所以,我們在繪制mGreenCircle和mRedCircle時(shí),先調(diào)用save(),再繪制圓點(diǎn),再restore()。

canvas.save();
canvas.translate(x, y);
mGreenCircle.draw(canvas);
canvas.restore();
// x y 為計(jì)算出來的坐標(biāo),上面的與下面的并不相同,省去了相關(guān)代碼
canvas.save();
canvas.translate(x, y);
mRedCircle.draw(canvas);
canvas.restore();

因?yàn)橐獙?shí)現(xiàn)不斷的繪制,那么在onDraw()中加上invalidate()。為了保證畫布不會(huì)多次繪制出現(xiàn)重疊的圖案,需要在上述draw操作的外層再增加一層save()和restore()代碼。

@Override
protected void onDraw(Canvas canvas) {
     super.onDraw(canvas);
     canvas.save();
        ...
        ...
        ...
     canvas.restore();

     invalidate();
}

這樣便實(shí)現(xiàn)了我們需求的自定義View。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容