前言
通过自定义控件,意欲模仿Twitter的点赞效果。
主要涉及:
1.三次贝塞尔曲线应用;
2.属性动画的综合应用;
3.自定义View流程.
拆解原效果
我们先看一下Twitter上的原版效果是怎样的.
放大后:
好吧!原速的看不太清楚,逐帧延迟后:
因为这个效果有需要使用多个动画杂糅而成,为了更确切得出没个子动画阶段所占比例还是用PS大法把它打开,根据该阶段的帧数以及总帧数来确定动画时长如何分配。
实现
1.动画控制
这里使用ValueAnimator并设置插值器为LinearInterpolator来获得随时间正比例变化的逐渐增大的整数值。这个整数值在这里有三个作用。
1.每监听到一个整数值变化重绘一次View.
2.根据整数值的大小范围来划分所处的不同阶段,这里共划分为五个状态.
- 绘制心形并伴随缩小和颜色渐变.
- 绘制圆并伴随放大和颜色渐变.
- 绘制圆环并伴随放大和颜色渐变.
- 圆环减消失、心形放大、周围环绕十四圆点.
- 环绕的十四圆点向外移动并缩小、透明度渐变、渐隐.
3.以整数值为基础来实现其他动画效果避免出现大量的ObjectAnimator.
/**
* 展现View点击后的变化效果
*/
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);//此阶段未达到最大半径
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);//半径接上一阶段增加,此阶段外环半径已经最大值
mCurrentPercent = 1f - percent + 0.2f > 1f ? 1f : 1f - percent + 0.2f;//用于计算圆环宽度,最小0.2,与动画进度负相关
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);//内环半径增大直至消亡
mCurrentPercent = percent;
mCurrentRadius = (int) (2 * mRadius);//外环半径不再改变
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.图形绘制
心形
这里使用贝塞尔曲线来绘制心形,通过四组控制点的改变来拟合心形。当然项目中为了方便此处的绘制可以用图片代替。
//绘制心形
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);
}
其他
还有一些 圆、圆点、圆环的绘制比较简单这里不再列出,重点是这些图形叠加交错的动画变化。
3.点击事件
对外提供点击事件监听,以便处理点赞与取消点赞的逻辑。
@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()) {//点击在View区域内
if (state) {
deselectLike();
} else {
startViewMotion();
}
if (mListener != null)
mListener.onClick(this);
}
break;
}
return true;
}
对外提供设置监听的方法
@Override
public void setOnClickListener(@Nullable OnClickListener l) {
mListener = l;
}
获取是否已点赞的状态
/**
* 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.最终效果
总结
这里大致实现了Twitter的点赞效果。虽然是根据原效果图像帧比例来确定动画应分配时间的,放慢观察似乎还是不太理想。另有些状态确定不了是颜色渐变还是透明度变化,临界消失时缩放有没有伴随移动,这些都从简处理了。
需要强调一下的是这里用到了颜色渐变动画,而这个方法系统是API21才提供的,
这里直接拷贝系统源码的ArgbEvaluator到项目里了,其实就相当于属性动画自定义TypeEvaluator,既然源码里有,就不客气了。
源码:https://github.com/qkxyjren/LikeView
欢迎指正