1. Introduction

When BROWSING GitHub the day before yesterday, I found a project that imitates the Loading effect of Gif. I feel the effect is very good and quite creative, as follows:

GitHub has several projects that do this, but few are fully implemented and some are buggy, so it took two days to implement it.

The effect is as follows:

GitHub project is here at LeavesLoading

2. The analysis

Implementation requirements:

  • leaves
    • Randomly generated
    • The oscillation trajectory is a sine function and has random amplitude
    • Flapping is accompanied by self-rotation, which is more consistent with physical laws
    • The progress bar seems to blend in
  • fan
    • Can rotate
    • Loading == 100% displays an animation
  • details
    • Fan and leaf adaptive View size
    • Leaves cannot visually float out of RountRect boundaries

3. Core implementation

3.1 Random generation of leaves

The essence is to generate a certain number of leaves in advance, and the amplitude, phase and rotation direction of these leaves are random when they float, and the drift is periodic, that is, when the leaves float to the left, they return to the right again.

The Leaf class:

    private class Leaf{
        float x,y;/ / coordinates
        AmplitudeType type;// Leaf flutter amplitude
        int rotateAngle;// Rotate the Angle
        RotateDir rotateDir;// Rotation direction
        long startTime;// Start time
        int n;// Initial phase
    }
Copy the code

Leaf generation method:

    Leaf generateLeaf(a){
        Leaf leaf = new Leaf();
        // Random amplitude
        int randomType = mRandom.nextInt(3);
        switch (randomType){
          case 0:
            / / small amplitude
            leaf.type = AmplitudeType.LITTLE;
            break;
          case 1:
            // Medium amplitude
            leaf.type = AmplitudeType.MIDDLE;
            break;
          default:
            / / large amplitude
            leaf.type = AmplitudeType.BIG;
            break;
        }
        // Rotate the direction randomly
        int dir = mRandom.nextInt(2);
        switch (dir){
          case 0:
            / / counterclockwise
            leaf.rotateDir = RotateDir.ANTICLOCKWISE;
            break;
          default:
            / / clockwise
            leaf.rotateDir = RotateDir.CLOCKWISE;
            break;
        }
        // Random starting Angle
        leaf.rotateAngle = mRandom.nextInt(360);
        leaf.n = mRandom.nextInt(20);
        mAddTime += mRandom.nextInt((int)mLeafFloatTime);
        leaf.startTime = System.currentTimeMillis() + mAddTime;
        return leaf;
    }
Copy the code

3.2 Leaf flapping trajectory is a sine function

Determine the coordinates of Leaf at a time (x, y) :

    /** * get the (x,y) position of the leaf *@paramLeaf *@paramCurrentTime currentTime */
    private void getLeafLocation(Leaf leaf,long currentTime){
        long intervalTime = currentTime - leaf.startTime;// Float duration
        if (intervalTime <= 0) {// The Leaf is not ready to float
          return;
        }else if (intervalTime > mLeafFloatTime){
          // Leaf flapping time is greater than the specified flapping time, that is, Leaf flapping to the left, should return to the right
          leaf.startTime = currentTime + new Random().nextInt((int)mLeafFloatTime);
        }
        // Calculate the movement factor
        float fraction = (float) intervalTime / mLeafFloatTime;
        leaf.x = (1-fraction)*mProgressLen;
        leaf.y = getLeafLocationY(leaf);

        if (leaf.x <= mYellowOvalHeight / 4) {// The leaf floats to the far left and may exceed the RoundRect boundary, so special treatment is made in advance
          leaf.startTime = currentTime + new Random().nextInt((int)mLeafFloatTime); leaf.x = mProgressLen; leaf.y = getLeafLocationY(leaf); }}Copy the code

In order to make the floating trajectory of Leaf a sine function, the key is to determine the Y-axis coordinates of Leaf:

   /** * get the Y coordinate of the leaf *@paramLeaf *@returnThe calculated Y-axis coordinates of the leaf */
    private float getLeafLocationY(Leaf leaf){
        float w = (float) (Math.PI * 2 / mProgressLen);/ / angular frequency
        float A;// Calculate the amplitude value
        switch (leaf.type){
            case LITTLE:
                A = mLeafLen/3;
                break;
            case MIDDLE:
                A = mLeafLen*2/3;
                break;
            default:
                A = mLeafLen;
                break;
        }
        // (mheight-mleaflen)/2 is to center the Y axis of Leaf
        return (float) (A * Math.sin(w * leaf.x + leaf.n)+(mHeight-mLeafLen)/2);
    }
Copy the code

3.3 The leaf rotates when it flutters

This involves the drawing of Leaf. In fact, the Leaf and fan in Gif can be directly drawn using Canves, but there are two problems:

  1. Difficult to draw: it takes a lot of effort to draw a satisfactory figure and rotate, scale and translate it.
  2. Low flexibility: you have to redesign the drawing process if you want to change styles.

Therefore, the method of canves.drawbitmap () is used to draw, and the existing picture is directly used as the leaf and fan. Meanwhile, an overloaded method of canves.drawbitmap () is used to easily achieve rotation, scaling and translation:

void drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) ;
Copy the code

It is the Matrix Matrix that encapsulates postScale(), postTranslate, postRotate() and other methods, which can help us quickly rotate, scale, translate and perform other operations on bitmaps. Remember to use it in conjunction with Canves’ save() and restore(), otherwise the desired effect will not be achieved.

For those of you who are not familiar with this area, see HenCoder’s custom View tutorial 1-4.

Method of drawing Leaf:

  private void drawLeaves(Canvas canvas){
      long currentTime = System.currentTimeMillis();
      for (Leaf leaf : mLeafList) {
          if(currentTime > leaf.startTime && leaf.startTime ! =0) {// Get the current coordinates of LEAF
            getLeafLocation(leaf,currentTime);
            canvas.save();
            Matrix matrix = new Matrix();
            // Zoom ADAPTS to View size
            float scaleX = (float) mLeafLen / mLeafBitmapWidth;
            float scaleY = (float) mLeafLen / mLeafBitmapHeight;
            matrix.postScale(scaleX,scaleY);
            / / displacement
            float transX = leaf.x;
            float transY = leaf.y;
            matrix.postTranslate(transX,transY);
            / / rotation
            // Compute the rotation factor
            float rotateFraction = ((currentTime - leaf.startTime) % mLeafRotateTime)
              /(float)mLeafRotateTime;
            float rotate;
            switch (leaf.rotateDir){
              case CLOCKWISE:
                / / clockwise
                rotate = rotateFraction * 360 + leaf.rotateAngle;
                break;
              default:
                / / counterclockwise
                rotate = -rotateFraction * 360 + leaf.rotateAngle;
                break;
            }
            // Rotate the center of Leaf
            matrix.postRotate(rotate,transX + mLeafLen / 2,transY + mLeafLen / 2); canvas.drawBitmap(mLeafBitmap,matrix,mBitmapPaint); canvas.restore(); }}Copy the code

3.4 Loading == 100% Animation

Add a judgment field isLoadingCompleted and select the corresponding draw strategy in onDraw().

IsLoadingCompleted is set according to progress in setProgress() :

   /** * Set the progress (automatic refresh) *@param progress 0-100
     */
    public void setProgress(int progress){
        if (progress < 0){
            mProgress = 0;
        }else if (progress > 100){
            mProgress = 100;
        }else {
            mProgress = progress;
        }
        if (progress == 100){
            isLoadingCompleted = true;
        }else {
            isLoadingCompleted = false;
        }
        // 255 is opaque
        mCompletedFanPaint.setAlpha(255);
        postInvalidate();
    }
Copy the code

Leavesload.ondraw ()

   @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas); .if (isLoadingCompleted){
            // Draw the finished effect
            drawCompleted(canvas);
        }else {
            // Draw the fan leaf
            drawFan(canvas,mFanLen,mBitmapPaint);
        }
        / / refresh
        postInvalidate();
    }
Copy the code

DrawCompleted () :

    private void drawCompleted(Canvas canvas) {
        // Each time the fan is drawn, the opacity decreases by 10
        int alpha = mCompletedFanPaint.getAlpha() - 10;
        if (alpha <= 0){
            alpha = 0;
        }
        mCompletedFanPaint.setAlpha(alpha);
        // Text transparency is just the opposite of fan
        mCompletedTextPaint.setAlpha(255-alpha);
        // Calculate the transparency factor
        float fraction = alpha / 255f;
        // The leaf size and the text size also change inversely
        float fanLen = fraction * mFanLen;
        float textSize = (1 - fraction) * mCompletedTextSize;
        mCompletedTextPaint.setTextSize(textSize);
         // Measure the space taken up by text
        Rect bounds = new Rect();
        mCompletedTextPaint.getTextBounds(
                LOADING_COMPLETED,
                0,
                LOADING_COMPLETED.length(),
                bounds);
      	DrawLeaf () = drawLeaf()
        drawFan(canvas, (int) fanLen, mCompletedFanPaint);
        / / draw text
        canvas.drawText(
                LOADING_COMPLETED,
                0,
                LOADING_COMPLETED.length(),
                mFanCx-bounds.width()/2f,
                mFanCy+bounds.height()/2f,
                mCompletedTextPaint);
    }
Copy the code

Process: calculate fan and text transparency -> calculate fan and text size and text space -> draw.

End of 4.

If there are any mistakes in this article, please comment in the comments section.

If you think LeavesLoading is of any help to you, I hope you can get your Star on GitHub!

Thanks:

  • This article from GAStudio provides the core code reference