前言
通過自定義控件,意欲模仿Twitter的點(diǎn)贊效果。
主要涉及:
1.三次貝塞爾曲線應(yīng)用;
2.屬性動畫的綜合應(yīng)用;
3.自定義View流程.
拆解原效果
我們先看一下Twitter上的原版效果是怎樣的.
放大后:

好吧!原速的看不太清楚,逐幀延遲后:

因?yàn)檫@個效果有需要使用多個動畫雜糅而成,為了更確切得出沒個子動畫階段所占比例還是用PS大法把它打開,根據(jù)該階段的幀數(shù)以及總幀數(shù)來確定動畫時長如何分配。

實(shí)現(xiàn)
1.動畫控制
這里使用ValueAnimator并設(shè)置插值器為LinearInterpolator來獲得隨時間正比例變化的逐漸增大的整數(shù)值。這個整數(shù)值在這里有三個作用。
1.每監(jiān)聽到一個整數(shù)值變化重繪一次View.
2.根據(jù)整數(shù)值的大小范圍來劃分所處的不同階段,這里共劃分為五個狀態(tài).
- 繪制心形并伴隨縮小和顏色漸變.
- 繪制圓并伴隨放大和顏色漸變.
- 繪制圓環(huán)并伴隨放大和顏色漸變.
- 圓環(huán)減消失、心形放大、周圍環(huán)繞十四圓點(diǎn).
- 環(huán)繞的十四圓點(diǎn)向外移動并縮小、透明度漸變、漸隱.
3.以整數(shù)值為基礎(chǔ)來實(shí)現(xiàn)其他動畫效果避免出現(xiàn)大量的ObjectAnimator.</li>
/**
* 展現(xiàn)View點(diǎn)擊后的變化效果
*/
private void startViewMotion() {
if (animatorTime != null && animatorTime.isRunning())
return;
resetState();
animatorTime = ValueAnimator.ofInt(0, 1200);
animatorTime.setDuration(mCycleTime);
animatorTime.setInterpolator(new LinearInterpolator());//需要隨時間勻速變化
animatorTime.start();
animatorTime.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int animatedValue = (int) animation.getAnimatedValue();
if (animatedValue == 0) {
if (animatorArgb == null || !animatorArgb.isRunning()) {
animatorArgb = ofArgb(mDefaultColor, 0Xfff74769, 0Xffde7bcc);
animatorArgb.setDuration(mCycleTime * 28 / 120);
animatorArgb.setInterpolator(new LinearInterpolator());
animatorArgb.start();
}
} else if (animatedValue <= 100) {
float percent = calcPercent(0f, 100f, animatedValue);
mCurrentRadius = (int) (mRadius - mRadius * percent);
if (animatorArgb != null && animatorArgb.isRunning())
mCurrentColor = (int) animatorArgb.getAnimatedValue();
mCurrentState = HEART_VIEW;
invalidate();
} else if (animatedValue <= 280) {
float percent = calcPercent(100f, 340f, animatedValue);//此階段未達(dá)到最大半徑
mCurrentRadius = (int) (2 * mRadius * percent);
if (animatorArgb != null && animatorArgb.isRunning())
mCurrentColor = (int) animatorArgb.getAnimatedValue();
mCurrentState = CIRCLE_VIEW;
invalidate();
} else if (animatedValue <= 340) {
float percent = calcPercent(100f, 340f, animatedValue);//半徑接上一階段增加,此階段外環(huán)半徑已經(jīng)最大值
mCurrentPercent = 1f - percent + 0.2f > 1f ? 1f : 1f - percent + 0.2f;//用于計(jì)算圓環(huán)寬度,最小0.2,與動畫進(jìn)度負(fù)相關(guān)
mCurrentRadius = (int) (2 * mRadius * percent);
if (animatorArgb != null && animatorArgb.isRunning())
mCurrentColor = (int) animatorArgb.getAnimatedValue();
mCurrentState = RING_VIEW;
invalidate();
} else if (animatedValue <= 480) {
float percent = calcPercent(340f, 480f, animatedValue);//內(nèi)環(huán)半徑增大直至消亡
mCurrentPercent = percent;
mCurrentRadius = (int) (2 * mRadius);//外環(huán)半徑不再改變
mCurrentState = RING_DOT__HEART_VIEW;
invalidate();
} else if (animatedValue <= 1200) {
float percent = calcPercent(480f, 1200f, animatedValue);
mCurrentPercent = percent;
mCurrentState = DOT__HEART_VIEW;
if (animatedValue == 1200) {
animatorTime.cancel();
animatorTime.removeAllListeners();
state = true;
}
invalidate();
}
}
});
}
2.圖形繪制
心形
這里使用貝塞爾曲線來繪制心形,通過四組控制點(diǎn)的改變來擬合心形。當(dāng)然項(xiàng)目中為了方便此處的繪制可以用圖片代替。
//繪制心形
private void drawHeart(Canvas canvas, int radius, int color) {
initControlPoints(radius);
mPaint.setColor(color);
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.FILL);
Path path = new Path();
path.moveTo(tPointB.x, tPointB.y);
path.cubicTo(tPointC.x, tPointC.y, rPointA.x, rPointA.y, rPointB.x, rPointB.y);
path.cubicTo(rPointC.x, rPointC.y, bPointC.x, bPointC.y, bPointB.x, bPointB.y);
path.cubicTo(bPointA.x, bPointA.y, lPointC.x, lPointC.y, lPointB.x, lPointB.y);
path.cubicTo(lPointA.x, lPointA.y, tPointA.x, tPointA.y, tPointB.x, tPointB.y);
canvas.drawPath(path, mPaint);
}
其他
還有一些 圓、圓點(diǎn)、圓環(huán)的繪制比較簡單這里不再列出,重點(diǎn)是這些圖形疊加交錯的動畫變化。
3.點(diǎn)擊事件
對外提供點(diǎn)擊事件監(jiān)聽,以便處理點(diǎn)贊與取消點(diǎn)贊的邏輯。
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
if (x + getLeft() < getRight() && y + getTop() < getBottom()) {//點(diǎn)擊在View區(qū)域內(nèi)
if (state) {
deselectLike();
} else {
startViewMotion();
}
if (mListener != null)
mListener.onClick(this);
}
break;
}
return true;
}
對外提供設(shè)置監(jiān)聽的方法
@Override
public void setOnClickListener(@Nullable OnClickListener l) {
mListener = l;
}
獲取是否已點(diǎn)贊的狀態(tài)
/**
* Indicates whether this LikeView is selected or not.
*
* @return true if the LikeView is selected now, false is deselected
*/
public boolean getState() {
return this.state;
}
4.最終效果

總結(jié)
這里大致實(shí)現(xiàn)了Twitter的點(diǎn)贊效果。雖然是根據(jù)原效果圖像幀比例來確定動畫應(yīng)分配時間的,放慢觀察似乎還是不太理想。另有些狀態(tài)確定不了是顏色漸變還是透明度變化,臨界消失時縮放有沒有伴隨移動,這些都從簡處理了。
需要強(qiáng)調(diào)一下的是這里用到了顏色漸變動畫,而這個方法系統(tǒng)是API21才提供的,
這里直接拷貝系統(tǒng)源碼的ArgbEvaluator到項(xiàng)目里了,其實(shí)就相當(dāng)于屬性動畫自定義TypeEvaluator,既然源碼里有,就不客氣了。
源碼:https://github.com/qkxyjren/LikeView
歡迎指正