When I scanned Douyin in the subway, due to the poor network, it was very slow to load, so douyin would load a loading animation, which felt very interesting, so I analyzed it and wrote a Demo by myself to realize the effect.

rendering

Analysis of the animation

Analyzing the animation first, the initial state consists of two circular shapes that are tangent to each other. Break down the animation into two parts.

  1. From the blue ball on the left and the red ball on the right as the starting position, the blue ball moves to the right and the red ball moves to the left (while shrinking first, before restoring), and the part where the two balls meet becomes black.
  2. It’s the same as step 1, except that the balls change positions.

To draw

Mainly use Drawable animation to draw.

public class DouYinLoadingDrawable extends Drawable implements Animatable {}Copy the code

Start with three paints and three paths. To draw the pattern of two circles and overlapping parts in the animation.

private Paint leftBallPaint, rightBallPaint, coincideBallPaint;

private Path leftBallPath, rightBallPath, coincideBallPath;
Copy the code

Drawing is divided into two processes, blue ball left and blue ball right, creating enumerations to control the animation process.

 public enum Direction {
        LEFT,
        RIGHT
    }
Copy the code

Start drawing, in ondraw().

    @Override
    public void draw(@NonNull Canvas canvas) {
        if (mCurrentDirection == Direction.LEFT) {
            canvas.save();
            leftBallPath.reset();
            leftBallPath.addCircle(centerX - radius + translate, centerY, radius, Path.Direction.CCW);
            canvas.drawPath(leftBallPath, leftBallPaint);
            canvas.restore();


            canvas.save();
            rightBallPath.reset();
            rightBallPath.addCircle(centerX + radius - translate, centerY, radius * scale, Path.Direction.CCW);
            canvas.drawPath(rightBallPath, rightBallPaint);
            canvas.restore();

            canvas.save();
            coincideBallPath.reset();
            coincideBallPath.op(leftBallPath, rightBallPath, Path.Op.INTERSECT);
            canvas.drawPath(coincideBallPath, coincideBallPaint);
            canvas.restore();
        } else if (mCurrentDirection == Direction.RIGHT) {
            canvas.save();
            rightBallPath.reset();
            rightBallPath.addCircle(centerX - radius + translate, centerY, 20, Path.Direction.CCW);
            canvas.drawPath(rightBallPath, rightBallPaint);
            canvas.restore();

            canvas.save();
            leftBallPath.reset();
            leftBallPath.addCircle(centerX + radius - translate, centerY, 20* scale, Path.Direction.CCW); canvas.drawPath(leftBallPath, leftBallPaint); canvas.restore(); canvas.save(); coincideBallPath.reset(); coincideBallPath.op(leftBallPath, rightBallPath, Path.Op.INTERSECT); canvas.drawPath(coincideBallPath, coincideBallPaint); canvas.restore(); }}Copy the code

Part of the animation code writing, mainly using ValueAnimator

  • First start the left part of the animation, after the left part of the animation, the two balls exchange the left center of the circle, repeat the left part of the animation.
   private void leftAnimation(a) {
        // Pan animation
        final ValueAnimator translateAnimator = ValueAnimator.ofFloat(0.2 * radius);
        translateAnimator.setStartDelay(200);
        translateAnimator.setDuration(350);
        translateAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
        translateAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                translate = (float) translateAnimator.getAnimatedValue(); invalidateSelf(); }});// Zoom animation
        final ValueAnimator scaleAnimator = ValueAnimator.ofFloat(1.0.5 f.1);
        scaleAnimator.setStartDelay(200);
        scaleAnimator.setDuration(350);
        scaleAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
        scaleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                scale = (float) scaleAnimator.getAnimatedValue(); invalidateSelf(); }}); translateAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                // After each animation, change the Direction value to reverse the animation
                if (mCurrentDirection == Direction.LEFT)
                    mCurrentDirection = Direction.RIGHT;
                else
                    mCurrentDirection = Direction.LEFT;
                translate = 0; leftAnimation(); }}); animatorSet =new AnimatorSet();
        animatorSet.playTogether(translateAnimator, scaleAnimator);
        animatorSet.start();

    }
Copy the code

Animation using

Just set drawable in ImageView

DouYinLoadingDrawable douYinLoadingDrawable =new DouYinLoadingDrawable();
imageView.setImageDrawable(douYinLoadingDrawable);
douYinLoadingDrawable.start();
Copy the code

Making the address

Remember to light a little star

Address of previous articles

Android animation – Fake bouquet live loading animation

Android animation – copy 58 city loading animation