這個簡單,直接使用
drawArc()
即可實作
- 繪制向圓心收縮的動畫
這個一開始的時候想用
drawArc()
加上設定畫筆的寬度
strokeWidth
來實作,不過改變的寬度是往外擴張的,是以這個想法果斷放棄。
之後,我的想法是這樣的,看下圖
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiE0YpZlMkZXUYlFdKNjYtlTeNJTV6x0M5kWT2lkekxGbtZFbkdVW0xmMRNTVDNGcKhEZ6lzQkVnVXFWe5cFT2JFWkhWOp1UekJjYOZlMahWMXF2Xj1mY3VzUPVTRHpVMnpWTzE1VOxmRy0UMnpWW0FFVNNTWU5UN4k3YsR2VZRHbyg1aGJjYzJEWkZHOXFWdVhUY6VzVZBHctxkeWJjWoFzVhRXUXlld4d0YxkTeMZTTINGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
我就打算先繪制一個黃色的背景,然後在這個圖層上面繪制一個白色的圓,半徑不斷的縮小,直至為0,這就反過來得到了一個向中心收縮的動畫,這可以叫逆轉思維吧,最近看的一本書裡面說到有時候反過來思考也許會有不一樣的效果。
- 顯示勾出來
關于這個√,我在網上搜了一波,也沒有明确的指明怎麼畫法才是标準的,是以這裡可以随意發揮,自己覺得好看就行。這裡直接可以使用
drawLine()
可以一步搞定。
- 最後是圓環放大再回彈的效果
放大回彈可以使用
drawArc()
,配合改變畫筆的寬度來實作即可
3.具體實作
3.1 确定進度圓環和鈎的位置
經過上面分析,無論是選中狀态還是未選中狀态,進度圓環和鈎的位置是不變的,是以我們先來确定圓環的位置和鈎的位置
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
…
//設定圓圈的外切矩形,radius是圓的半徑,centerX,centerY是控件中心的坐标
mRectF.set(centerX - radius, centerY - radius, centerX + radius, centerY + radius);
//設定打鈎的幾個點坐标(具體坐标點的位置不用怎麼理會,自己定一個就好,沒有統一的标準)
//畫一個√,需要确定3個坐标點的位置
//是以這裡我先用一個float數組來記錄3個坐标點的位置,
//最後在onDraw()的時候使用canvas.drawLines(mPoints, mPaintTick)來畫出來
//其中這裡mPoint[0]~mPoint[3]是确定第一條線""的兩個坐标點位置
//mPoint[4]~mPoint[7]是确定第二條線"/"的兩個坐标點位置
mPoints[0] = centerX - tickRadius + tickRadiusOffset;
mPoints[1] = (float) centerY;
mPoints[2] = centerX - tickRadius / 2 + tickRadiusOffset;
mPoints[3] = centerY + tickRadius / 2;
mPoints[4] = centerX - tickRadius / 2 + tickRadiusOffset;
mPoints[5] = centerY + tickRadius / 2;
mPoints[6] = centerX + tickRadius * 2 / 4 + tickRadiusOffset;
mPoints[7] = centerY - tickRadius * 2 / 4;
}
3.2 定義變量,标記狀态
既然分選中狀态和未選中狀态,那個繪制過程中,就必須判斷目前究竟是繪制未選中的呢還是選中了的呢。
是以在這裡,我定義了一個變量
isChecked
//是否被點亮
private boolean isChecked = false;
//暴露外部接口,改變繪制狀态
public void setChecked(boolean checked) {
if (this.isCh 《Android學習筆記總結+最新移動架構視訊+大廠安卓面試真題+項目實戰源碼講義》無償開源 徽信搜尋公衆号【程式設計進階路】 ecked != checked) {
isChecked = checked;
reset();
}
}
3.3 繪制未選中狀态
繪制過程中那些畫筆就不詳細說了,一開始初始化畫筆最後繪制的時候調用即可
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (!isChecked) {
//繪制圓環,mRectF就是之前确定的外切矩形
//因為是靜态的,是以設定掃過的角度為360度
canvas.drawArc(mRectF, 90, 360, false, mPaintRing);
//根據之前定好的鈎的坐标位置,進行繪制
canvas.drawLines(mPoints, mPaintTick);
return;
}
}
3.4 繪制選中狀态
選中狀态是個動畫,是以我們這裡需要調用
postInvalidate()
不斷進行重繪,直到動畫執行完畢;另外,我這裡用計數器的方式來控制繪制的進度。
3.4.1 繪制圓環進度條
繪制進度圓環這裡,我們定義一個計數器
ringCounter
,峰值為360(也就是360度),每執行一次
onDraw()
方法,我們對
ringCounter
進行自加,進而模拟進度。
最後記得調用
postInvalidate()
進行重繪
//計數器
private int ringCounter = 0;
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (!isChecked) {
…
return;
}
//畫圓弧進度,每次繪制都自加12個機關,也就是圓弧又掃過了12度
//這裡的12個機關先寫死,後面我們可以做一個配置來實作自定義
ringCounter += 12;
if (ringCounter >= 360) {
ringCounter = 360;
}
canvas.drawArc(mRectF, 90, ringCounter, false, mPaintRing);
…
//強制重繪
postInvalidate();
}
這一步後效果圖如下
3.4.2 繪制向圓心收縮的動畫
圓心收縮的動畫在圓環進度達到100%的時候才進行,同理,也采用計數器
circleCounter
的方法來控制繪制的時間和速度
//計數器
private int circleCounter = 0;
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
…
//在圓環進度達到100%的時候才開始繪制
if (ringCounter == 360) {
//先繪制背景的圓
mPaintCircle.setColor(checkBaseColor);
canvas.drawCircle(centerX, centerY, radius, mPaintCircle);
//然後在背景圓的圖層上,再繪制白色的圓(半徑不斷縮小)
//半徑不斷縮小,背景就不斷露出來,達到向中心收縮的效果
mPaintCircle.setColor(checkTickColor);
//收縮的機關先試着設定為6,後面可以進行自己自定義
circleCounter += 6;
canvas.drawCircle(centerX, centerY, radius - circleCounter, mPaintCircle);
}
//必須重繪
postInvalidate();
}
這一步後效果圖如下
3.4.3 繪制鈎
當白色的圓半徑收縮到0後,就該繪制打鈎了。
繪制打鈎,這裡問題不大,因為在
onMeasure()
中已經将鈎的三個坐标點已經計算出來了,直接使用
drawLine()
即可畫出來。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
…
canvas.drawCircle(centerX, centerY, radius - circleCounter, mPaintCircle);
//當白色的圓半徑收縮到0後,
//也就是計數器circleCounter大于背景圓的半徑的時候,就該将鈎√顯示出來了
//這裡加40是為了加一個延遲時間,不那麼倉促的将鈎顯示出來
if (circleCounter >= radius + 40) {
//顯示打鈎(外加一個透明的漸變)
alphaCount += 20;
if (alphaCount >= 255) alphaCount = 255;
mPaintTick.setAlpha(alphaCount);
//最後就将之前在onMeasure中計算好的坐标傳進去,繪制鈎出來
canvas.drawLines(mPoints, mPaintTick);
}
postInvalidate();
}
這一步後效果圖如下
3.4.4 繪制放大再回彈的效果
放大再回彈的效果,開始的時機應該也是收縮動畫結束後開始,也就是說跟打鈎的動畫同時進行
因為這裡要放大并且回彈,是以這裡的計數器我設定成一個不為0的數值,先設定成45(随意,這不是标準),然後沒重繪一次,自減4個機關。
最後畫筆的寬度是關鍵的地方,畫筆的寬度根據
scaleCounter
的正負來決定是加還是減
//計數器
private int scaleCounter = 45;
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
…
if (circleCounter >= radius + 40) {
//顯示打鈎
…
//顯示放大并回彈的效果
scaleCounter -= 4;
if (scaleCounter <= -45) {
scaleCounter = -45;
}
//放大回彈,主要看畫筆的寬度
float strokeWith = mPaintRing.getStrokeWidth() +
(scaleCounter > 0 ? dp2px(mContext, 1) : -dp2px(mContext, 1));
mPaintRing.setStrokeWidth(strokeWith);
canvas.drawArc(mRectF, 90, 360, false, mPaintRing);
}
//動畫執行完畢,就補在需要重繪了
if (scaleCounter != -45) {
postInvalidate();
}
}
完成最後一步的最終效果圖
3.5 暴露外部接口
為了靈活的可以控制繪制的狀态,我們可以暴露一個接口給外部設定是否選中
public void setChecked(boolean checked) {
if (this.isChecked != checked) {
isChecked = checked;
reset();
}
}
private void reset() {
//畫筆重置
…
//計數器重置
ringCounter = 0;
circleCounter = 0;
scaleCounter = 45;
alphaCount = 0;
…
invalidate();
}
3.6 添加點選事件
控件到這裡已經基本做好了,但還不是特别的完善。
想想
checkbox
,它不需要暴露外部接口也能通過點選控件來實作選中還是取消選中,是以接下來要實作的就是為控件添加點選事件
先定義一個接口
OnCheckedChangeListener
,實作監聽此控件的監聽事件
private OnCheckedChangeListener mOnCheckedChangeListener;
public interface OnCheckedChangeListener {
void onCheckedChanged(TickView tickView, boolean isCheck);
}
public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
this.mOnCheckedChangeListener = listener;
}
接下來,初始化控件的點選事件
/**