preface

Through the custom control, intended to imitate Twitter like effect. It mainly involves: 1. Application of cubic Bezier curve; 2. Comprehensive application of attribute animation; 3. Customize the View flow.

Disassemble the original effect

Let’s take a look at what the original Twitter look like.

Enlarged:

All right! The original speed is not very clear, after frame by frame delay:

Because this effect needs to be mixed with multiple animations, in order to get a more accurate proportion of each sub-animation stage, it is better to use photoshop to open it, and determine how to allocate the animation time according to the number of frames and the total number of frames in this stage.

implementation

1. Animation control

Here we use ValueAnimator and set the interpolator to LinearInterpolator to get progressively larger integer values proportional to time. This integer value has three functions here.

  1. Redraw the View every time it hears an integer change. There are five states.
    • Draw a heart with shrinking and color gradient.
    • Draw a circle with enlargement and color gradient.
    • Draw a circle with enlargement and color gradient.
    • The circle disappears, the heart enlarges, and fourteen dots surround it.
    • The fourteen circles move outward and shrink, their opacity gradually fade out.
  2. The different stages are divided according to the size range of integer values.
  3. Implement other animation effects based on integer values to avoid numerous ObjectAnimators.
 /** * display the effect of View click */
    private void startViewMotion() {
        if(animatorTime ! =null && animatorTime.isRunning())
            return;
        resetState();
        animatorTime = ValueAnimator.ofInt(0.1200);
        animatorTime.setDuration(mCycleTime);
        animatorTime.setInterpolator(new LinearInterpolator());// Need constant velocity with time
        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(newLinearInterpolator()); 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);// The maximum radius is not reached at this stage
                    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);// The radius increases at the next stage, when the outer ring radius has reached its maximum value
                    mCurrentPercent = 1f - percent + 0.2f > 1f ? 1f : 1f - percent + 0.2f;// It is used to calculate the width of the circle. The minimum is 0.2, which is negatively correlated with the animation progress
                    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);// The radius of the inner ring increases until it dies
                    mCurrentPercent = percent;
                    mCurrentRadius = (int) (2 * mRadius);// The radius of the outer ring does not change
                    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(); }}}); }Copy the code

2. Graph drawing

heart

Here bezier curves are used to draw hearts, which are fitted by changing four sets of control points. Of course, you can use images in your project to make it easier to draw here.

    // Draw a heart
    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);
    }Copy the code

other

There are also some circles, dots and circles that are easy to draw, which are not listed here. The emphasis is on the animation changes of the overlapping of these figures.

3. Click events

Provides external click event listener to handle like and unlike logic.

  @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()) {// Click in the View area
                    if (state) {
                        deselectLike();
                    } else {
                        startViewMotion();
                    }
                    if(mListener ! =null)
                        mListener.onClick(this);
                }
                break;
        }
        return true;
    }
Copy the code

Provides methods for setting listening externally


    @Override
    public void setOnClickListener(@Nullable OnClickListener l) {
        mListener = l;
    }Copy the code

4. The end result

conclusion

This is roughly Twitter like. Although the animation should be allocated according to the scale of the original effect image frame, slowing down the observation is still not ideal. Other states that can’t be determined whether the color gradient or opacity changes, or whether scaling is accompanied by movement when critical disappearance, are simplified.

It is important to note that the color gradient animation is used, and the method system is provided by API21. The ArgbEvaluator is copied directly from the system source code into the project, which is equivalent to the property animation custom TypeEvaluator.

Source: github.com/qkxyjren/Li…

Welcome to point out mistakes