Android 自定义——点赞效果 (仿 Twitter)

阅读 3671
收藏 150
2017-06-01
原文链接:blog.csdn.net

前言

通过自定义控件,意欲模仿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;
    }

4.最终效果

总结

这里大致实现了Twitter的点赞效果。虽然是根据原效果图像帧比例来确定动画应分配时间的,放慢观察似乎还是不太理想。另有些状态确定不了是颜色渐变还是透明度变化,临界消失时缩放有没有伴随移动,这些都从简处理了。

需要强调一下的是这里用到了颜色渐变动画,而这个方法系统是API21才提供的, 这里直接拷贝系统源码的ArgbEvaluator到项目里了,其实就相当于属性动画自定义TypeEvaluator,既然源码里有,就不客气了。

源码:github.com/qkxyjren/Li…

欢迎指正

评论