Twitter 的 like 动画安卓版

3,433 阅读4分钟
原文链接: www.jcodecraeer.com

英文原文:Twitter's like animation in Android - alternative 

不久前Twitter展示了具有现代感的心形动画-作为star图标的替代。

Untitled.gif

虽然心形标志更普遍和昂贵,但是今天我们尝试复制新的动画,使用旧的星星图标。我们的效果如下(比gif图快一点点):

button_anim.gif

虽然实现这个动画最简单的方法是使用 Frame Animation,但是我们尝试用更灵活的方法来实现-手动绘制并用属性动画。这篇文章只是概要,没有深入的技术细节。

实现

我们将创建一个名叫LikeButtonView的view,它是一个由三个子view构成的FrameLayout- CircleView 显示星星图标下面的圆,ImageView (星星)以及代表按钮周围浮点的DotsView 。

CircleView

Circle animation

这个视图负责绘制星星图标下面的大圆。它本可以实现得更简单(通过xml),但是这里我们应该考虑按钮下面的背景颜色。

我们在canvas上绘制圆的实现:

Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    tempCanvas.drawColor(0xffffff, PorterDuff.Mode.CLEAR);
    tempCanvas.drawCircle(getWidth() / 2, getHeight() / 2, outerCircleRadiusProgress * maxCircleSize, circlePaint);
    tempCanvas.drawCircle(getWidth() / 2, getHeight() / 2, innerCircleRadiusProgress * maxCircleSize, maskPaint);
    canvas.drawBitmap(tempBitmap, 0, 0, null);
}

ondraw.java hosted with ❤ by GitHub

先使用CLEAR 模式绘制颜色以清除canvas。然后根据给定的进度(各自的进度是独立的)绘制内外圆。

内圆使用这样定义的mask paint :

maskPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));

maskPaint.java hosted with ❤ by GitHub

意味着内圆将在外圆内部创建一个透明的洞。

我们视图中使用了tempBitmap的tempCanvas定义如下:

Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    maxCircleSize = w / 2;
    tempBitmap = Bitmap.createBitmap(getWidth(), getWidth(), Bitmap.Config.ARGB_8888);
    tempCanvas = new Canvas(tempBitmap);
}

onSizeChanged.java hosted with ❤ by GitHub

我们需要完全透明,不然的话内圆就会显示窗口颜色。

对于那些眼睛机灵的人应该还注意到了另外一件事-我们的外圆颜色是基于当前进度而变化的。这是通过ArgbEvaluator 类来完成,该类可以基于一个给定的因子在两个颜色之间变换:

private void updateCircleColor() {
    float colorProgress = (float) Utils.clamp(outerCircleRadiusProgress, 0.5, 1);
    colorProgress = (float) Utils.mapValueFromRangeToRange(colorProgress, 0.5f, 1f, 0f, 1f);
    this.circlePaint.setColor((Integer) argbEvaluator.evaluate(colorProgress, START_COLOR, END_COLOR));
}

updateCircleColor.java hosted with ❤ by GitHub

CircleView代码的剩余部分就是一个实现的问题了。完整的源代码可以在这里找到: CircleView

DotsView

Dots animation

这个view将绘制浮动在星星图标周围的圆点。跟CircleView一样,它是使用onDraw()来做这件事的:

@Override
protected void onDraw(Canvas canvas) {
    drawOuterDotsFrame(canvas);
    drawInnerDotsFrame(canvas);
}

private void drawOuterDotsFrame(Canvas canvas) {
    for (int i = 0; i < dots_count; i++) {         int cx =" (int) (centerX + currentRadius1 * Math.cos(i * OUTER_DOTS_POSITION_ANGLE * Math.PI / 180));"         int cy =" (int) (centerY + currentRadius1 * Math.sin(i * OUTER_DOTS_POSITION_ANGLE * Math.PI / 180));"         canvas.drawcircle(cx, cy, currentdotsize1, circlepaints[i % circlepaints.length]);=""     }="" }="" private void drawinnerdotsframe(canvas canvas) {=""     for (int i =" 0; i < DOTS_COUNT; i++) {"         canvas.drawcircle(cx, cy, currentdotsize2, circlepaints[(i + 1) % circlepaints.length]);="" }<="" pre="">

onDraw2.java hosted with ❤ by GitHub

圆点是基于currentProgress绘制的,背后是数学逻辑,老实说从安卓sdk的角度来看这里没有什么有趣的地方,倒是有两个跟数学相关的东西:

  • 圆点分布在一个圆上-它们的位置决定于:

    int cX = (int) (centerX + currentRadius1 * Math.cos(i * OUTER_DOTS_POSITION_ANGLE * Math.PI / 180));

    int cY = (int) (centerY + currentRadius1 * Math.sin(i * OUTER_DOTS_POSITION_ANGLE * Math.PI / 180));

    意味着:在每个OUTER_DOTS_POSITION_ANGLE上设置圆点 (51 度).

  • 每个圆点都有它自己的颜色动画:

    private void updateDotsPaints() {
        if (currentProgress < 0.5f) {         float progress =" (float) Utils.mapValueFromRangeToRange(currentProgress, 0f, 0.5f, 0, 1f);"         circlepaints[0].setcolor((integer) argbevaluator.evaluate(progress, color_1, color_2));=""         circlepaints[1].setcolor((integer) argbevaluator.evaluate(progress, color_2, color_3));=""         circlepaints[2].setcolor((integer) argbevaluator.evaluate(progress, color_3, color_4));=""         circlepaints[3].setcolor((integer) argbevaluator.evaluate(progress, color_4, color_1));=""     } else {=""         circlepaints[0].setcolor((integer) argbevaluator.evaluate(progress, color_2, color_3));=""         circlepaints[1].setcolor((integer) argbevaluator.evaluate(progress, color_3, color_4));=""         circlepaints[2].setcolor((integer) argbevaluator.evaluate(progress, color_4, color_1));=""         circlepaints[3].setcolor((integer) argbevaluator.evaluate(progress, color_1, color_2));=""     }="" }<="" pre="">

    这意味着圆点颜色在3个区间形式的值之间动画。我们再一次使用ArgbEvaluator 让它平滑。其余就很简单了。这个类的完整代码在这里:DotsView

LikeButtonView

最终的ViewGroup是由CircleView, ImageView 以及DotsView组成的。



    

    

    

likebutton.xml hosted with ❤ by GitHub

我们使用 Merge 标签帮助消除多余的ViewGroup。LikeButtonView本身就是一个FrameLayout,因此没有必要出现两次。

我们最终的动画是由更小的动画组成的,通过AnimatorSet一起播放:

@Override
public void onClick(View v) {
    //...

    animatorSet = new AnimatorSet();

    ObjectAnimator outerCircleAnimator = ObjectAnimator.ofFloat(vCircle, CircleView.OUTER_CIRCLE_RADIUS_PROGRESS, 0.1f, 1f);
    outerCircleAnimator.setDuration(250);
    outerCircleAnimator.setInterpolator(DECCELERATE_INTERPOLATOR);

    ObjectAnimator innerCircleAnimator = ObjectAnimator.ofFloat(vCircle, CircleView.INNER_CIRCLE_RADIUS_PROGRESS, 0.1f, 1f);
    innerCircleAnimator.setDuration(200);
    innerCircleAnimator.setStartDelay(200);
    innerCircleAnimator.setInterpolator(DECCELERATE_INTERPOLATOR);

    ObjectAnimator starScaleYAnimator = ObjectAnimator.ofFloat(ivStar, ImageView.SCALE_Y, 0.2f, 1f);
    starScaleYAnimator.setDuration(350);
    starScaleYAnimator.setStartDelay(250);
    starScaleYAnimator.setInterpolator(OVERSHOOT_INTERPOLATOR);

    ObjectAnimator starScaleXAnimator = ObjectAnimator.ofFloat(ivStar, ImageView.SCALE_X, 0.2f, 1f);
    starScaleXAnimator.setDuration(350);
    starScaleXAnimator.setStartDelay(250);
    starScaleXAnimator.setInterpolator(OVERSHOOT_INTERPOLATOR);

    ObjectAnimator dotsAnimator = ObjectAnimator.ofFloat(vDotsView, DotsView.DOTS_PROGRESS, 0, 1f);
    dotsAnimator.setDuration(900);
    dotsAnimator.setStartDelay(50);
    dotsAnimator.setInterpolator(ACCELERATE_DECELERATE_INTERPOLATOR);

    animatorSet.playTogether(
            outerCircleAnimator,
            innerCircleAnimator,
            starScaleYAnimator,
            starScaleXAnimator,
            dotsAnimator
    );

    //...

    animatorSet.start();
}

全是关于恰当的时间和插值器。

Touch animation

我们的LikeButtonView还会响应触摸事件(缩放动画):

@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            ivStar.animate().scaleX(0.7f).scaleY(0.7f).setDuration(150).setInterpolator(DECCELERATE_INTERPOLATOR);
            setPressed(true);
            break;

        case MotionEvent.ACTION_MOVE:
            float x = event.getX();
            float y = event.getY();
            boolean isInside = (x > 0 && x < getwidth() && y > 0 && y < getheight());             if (ispressed() !=" isInside) {"                 setpressed(isinside);=""             }=""             break;=""         case motionevent.action_up:=""             ivstar.animate().scalex(1).scaley(1).setinterpolator(deccelerate_interpolator);=""             if (ispressed()) {=""                 performclick();=""                 setpressed(false);=""     }=""     return true;="" }<="" pre="">

以上就是全部。就如你看到的,这里没有神秘的东西,但是最终的效果却很赞。那么现在该干什么呢?让我们用它来美化自己的app吧。

源码

所描述的项目的完整源代码可以在github的repository上获取。