Perfect! Customize the View to realize the dynamic Gallery App Icon animation on dribble!

 

 

Original link: https://juejin.cn/post/7024883320269832205

I saw a very nice animation effect in dribbble before. I wanted it very much, so I imitated it. Also in order to practice custom controls, it has been a while, and now it's sorted out

 

 

dribbble address: https://dribbble.com/shots/4761564

thinking

Disassembly is still relatively simple. What needs to be drawn are:

  • Circular background
  • Sun (round)
  • Mountain (triangle)
  • Clouds (rounded rectangle + three circles)

Animation required:

  • Sun - rotation animation
  • Mountain - Pan up and down animation
  • Clouds - Pan left and right animation

There is no need to draw rounded outline, because the rounded corners of application icons of various mobile phone manufacturers are different, we can generate application icons in Android Studio. If necessary, you can also use shape to draw it yourself.

The difficulty is to animate the sun and draw clouds, because the sun rotation animation needs to calculate the coordinates of points on the rotating circle, and the shape of clouds is irregular.

draw

1. Round background

 

 

The white rounded outline here is drawn by shape, and the blue round background is also relatively simple. It is mainly to use canvas.drawCircle() in onDraw() method:

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // Cut the View into a circle, otherwise the painted mountains and clouds will appear outside the circular background
        mRoundPath.reset();
        mRoundPath.addCircle(mViewCircle, mViewCircle, mViewCircle, Path.Direction.CW);
        canvas.clipPath(mRoundPath);
        // Draw a circular background
        canvas.drawCircle(mViewCircle, mViewCircle, mViewCircle, mBackgroundPaint);
    }

mViewCircle here refers to the radius of view; mBackgroundPaint is the Paint used to Paint the background color.

mViewCircle get:

@Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        // Take the minimum value of width and height
        mParentWidth = mParentHeight = Math.min(getWidth(), getHeight());
        // Radius of View
        mViewCircle = mParentWidth >> 1;
    }

mBackgroundPaint background color just set a color:

mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBackgroundPaint.setColor(mBackgroundColor);

If the View is not cut into circles, the following situations will occur:

 

 

2. Draw the sun and animate the rotation

 

 

If you just draw the sun, determine the x,y coordinates and radius, and then add a color paint:

canvas.drawCircle((mParentWidth / 2) - getValue(90), (mParentHeight / 2) - getValue(80), sunWidth / 2, mSunPaint);

But we need to add animation. At this time, we need to understand:

/**
 * Transform the points in this path by matrix, and write the answer
 * into dst. If dst is null, then the the original path is modified.
 *
 * @param matrix The matrix to apply to the path
 * @param dst    The transformed path is written here. If dst is null,
 *               then the the original path is modified
 */
public void transform(Matrix matrix, Path dst) {
    long dstNative = 0;
    if (dst != null) {
        dst.isSimplePath = false;
        dstNative = dst.mNativePath;
    }
    nTransform(mNativePath, matrix.native_instance, dstNative);
}

This method can convert a path to matrix, that is, matrix conversion. Therefore, we can use the method matrix.postTranslate to realize translation animation, that is, create a circular animation, and set the animation value through postTranslate.

/**
 * Postconcats the matrix with the specified translation. M' = T(dx, dy) * M
 * dx,dy,Is the difference between the X and Y coordinate movements.
 */
public boolean postTranslate(float dx, float dy) {
    nPostTranslate(native_instance, dx, dy);
    return true;
}
Copy code
 Let's go first onSizeChanged()Where you get the starting point and the center of the sun x,y Coordinates, and then onDraw()Get the information when you want to rotate in real time x,y Coordinates, and finally get the corresponding difference.
onSizeChanged()Draw the sun and get the starting point of rotation x,y Coordinates:
private void drawSun() {
    // Diameter of sun graph
    int sunWidth = getValue(70);
    // Radius of sun graph
    int sunCircle = sunWidth / 2;
    // Sun animation radius = (Sun radius + 80(sun height from the center) + radius of the whole View + sun radius + 20(sun distance from the lowest edge of the whole View)) / 2
    mSunAnimCircle = (sunWidth + getValue(100) + mViewCircle) / 2;
    // Center x coordinate of sun animation
    mSunAnimX = mViewCircle;
    // Center y coordinate of sun animation = sun animation radius + (radius of the whole View - 80(sun height from the center) - Sun radius)
    mSunAnimY = mSunAnimCircle + (mViewCircle - getValue(80) - sunCircle);
    // Get the X and Y coordinates of the starting point of circular rotation animation, and the initial angle is - 120
    mSunAnimXY = getCircleXY(mSunAnimX, mSunAnimY, mSunAnimCircle, -120);
    // Draw sun
    mSunPath.addCircle(mSunAnimXY[0], mSunAnimXY[1], sunCircle, Path.Direction.CW);
}

The slightly more difficult thing is to get the x,y coordinates on the circle   getCircleXY():

 

 

Known conditions: the coordinates of the center O (msuanimx, msuanimy), the radius is sunCircle, and the angle = -120 degrees

(the angle is relative to the horizontal line in the figure, clockwise is positive and counterclockwise is negative). To calculate the coordinates (x1,y1) of point p, there is the following formula:

x1 = x0 + r * cos(angle * PI / 180)
y1 = y0 + r * sin(angle * PI /180)

Where angle* PI/180 is the angle converted to radians.

/**
 * Find the point on the circle when sun rotates. The starting point is the rightmost point, clockwise.
 * x1   =   x0   +   r   *   cos(a   *   PI  /180  )
 * y1   =   y0   +   r   *   sin(a   *   PI  /180  )
 *
 * @param angle         angle
 * @param circleCenterX Center x coordinate
 * @param circleCenterY Center y coordinate
 * @param circleR       radius
 */
private int[] getCircleXY(int circleCenterX, int circleCenterY, int circleR, float angle) {
    int x = (int) (circleCenterX + circleR * Math.cos(angle * Math.PI / 180));
    int y = (int) (circleCenterY + circleR * Math.sin(angle * Math.PI / 180));
    return new int[]{x, y};
}

Then, in onDraw(), we can dynamically obtain the X and Y coordinates of other points on the circle to achieve the effect of rotation:

// x y coordinate
int[] circleXY = getCircleXY(mSunAnimX, mSunAnimY, mSunAnimCircle, mSunAnimatorValue);
mSunComputeMatrix.postTranslate(circleXY[0] - mSunAnimXY[0], circleXY[1] - mSunAnimXY[1]);
mSunPath.transform(mSunComputeMatrix, mSunComputePath);
canvas.drawPath(mSunComputePath, mSunPaint);
Copy code
mSunAnimatorValue Is the angle of change[-120,240]. This allows you to animate the rotation of the sun:
/**
 * sun Animation of
 */
private void setSunAnimator() {
    ValueAnimator mSunAnimator = ValueAnimator.ofFloat(-120, 240);
    mSunAnimator.setDuration(2700);
    mSunAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
    mSunAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            mSunAnimatorValue = (float) animation.getAnimatedValue();
            invalidate();
        }
    });
    mSunAnimator.start();
}
3. Mountain and pan up and down animation

 

 

After drawing the sun rotation animation above, this is relatively simple, because it only involves the change of the ordinate y, and x will not change. Careful observation will find that the Y coordinate will move up first and then quickly down.

Draw three mountains in onSizeChanged() and get the y coordinates to be translated: drawMou(mViewCircle, mViewCircle - getValue(10), getValue(10));

/**
 * Three mountains in the middle of the painting
 *
 * @param x Left coordinate of center point
 * @param y Right coordinate of center point
 */
private void drawMou(int x, int y, int down) {
    // How much does the Y coordinate of the left and right mountains move down relative to the center point
    int lrmYpoint = down + getValue(30);
    // How much does the X coordinate of the left and right mountains move left or right relative to the center point
    int lrdPoint = getValue(120);
    // What is the X distance between the left and right mountains
    int lrBanDis = getValue(140);
    // What is the X spacing of half of the middle mountain
    int lrBanGao = getValue(150);

    // Zuo Shan
    mLeftMountainPath.reset();
    // starting point
    mLeftMountainPath.moveTo(x - lrdPoint, y + lrmYpoint);
    mLeftMountainPath.lineTo(x - lrdPoint + lrBanDis, y + lrmYpoint + lrBanGao);
    mLeftMountainPath.lineTo(x - lrdPoint - lrBanDis, y + lrmYpoint + lrBanGao);
    // Make these points form a closed polygon
    mLeftMountainPath.close();

    // Right mountain
    mRightMountainPath.reset();
    mRightMountainPath.moveTo(x + lrdPoint + getValue(10), y + lrmYpoint);
    mRightMountainPath.lineTo(x + lrdPoint + getValue(10) + lrBanDis, y + lrmYpoint + lrBanGao);
    mRightMountainPath.lineTo(x + lrdPoint + getValue(10) - lrBanDis, y + lrmYpoint + lrBanGao);
    mRightMountainPath.close();

    // Zhongshan
    mMidMountainPath.reset();
    mMidMountainPath.moveTo(x, y + down);
    mMidMountainPath.lineTo(x + getValue(220), y + down + mParentHeight / 2 + mParentHeight / 14);
    mMidMountainPath.lineTo(x - getValue(220), y + down + mParentHeight / 2 + mParentHeight / 14);
    mMidMountainPath.close();

    // Moving distance of left and right mountains
    mMaxMouTranslationY = (y + down + mViewCircle) / 14;
}

Then we move according to the dynamic y coordinate in onDraw(), taking the mountain in the middle as an example:

// Middle mountain
mMidComputeMatrix.reset();
mMidComputePath.reset();
mMidComputeMatrix.postTranslate(0, mMaxMouTranslationY * mMidMouAnimatorValue);
mMidMountainPath.transform(mMidComputeMatrix, mMidComputePath);
canvas.drawPath(mMidComputePath, mMidMountainPaint);

Change the mMidMouAnimatorValue. Note that the y coordinate will rise a little before falling:

/**
 * Middle mountain animation
 */
private void setMidMouAnimator(final boolean isFirst) {
    ValueAnimator mMidMouAnimator;
    if (isFirst) {
        mMidMouAnimator = ValueAnimator.ofFloat(0, -1, 10);
        mMidMouAnimator.setStartDelay(200);
        mMidMouAnimator.setDuration(1000);
    } else {
        mMidMouAnimator = ValueAnimator.ofFloat(10, 0);
        mMidMouAnimator.setStartDelay(0);
        mMidMouAnimator.setDuration(600);
    }
    mMidMouAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
    mMidMouAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            mMidMouAnimatorValue = (float) animation.getAnimatedValue();
            invalidate();
        }
    });
    mMidMouAnimator.start();
}
4. Clouds and pan left and right animation

 

 

This animation is very similar to the mountain animation, except that the change from y coordinate to x coordinate is changed, but drawing clouds is a little troublesome:

For more information, see here: Android custom view rain animation - draw cloud. Generally speaking, it is composed of four views, the rectangle at the bottom (because the whole moves down, there is basically no rectangle here), and the three circles above the rectangle.

// Draw rounded rectangle
path.addRoundRect(RectF rect, float rx, float ry, Direction dir)
// Draw circle
path.addCircle(float x, float y, float radius, Direction dir)

Then, after obtaining the x coordinate, move dynamically according to the incremental value mcloud animatorvalue:

mCloudComputeMatrix.postTranslate(mMaxCloudTranslationX * mCloudAnimatorValue, 0);
mCloudPath.transform(mCloudComputeMatrix, mCloudComputePath);
canvas.drawPath(mCloudComputePath, mCloudPaint);

Then we combine the rotation animation of the sun, the up and down translation animation of three mountains and the left and right translation animation of clouds to get a complete coherent animation.

last

For extensibility, we add some properties to the View to customize the color:

<declare-styleable name="SceneryView">
    <!--The color of sun-->
    <attr name="sun_color" format="color" />
    <!--The color of the cloud-->
    <attr name="cloud_color" format="color" />
    <!--The color of the left mountain-->
    <attr name="left_mountain_color" format="color" />
    <!--The color of the right mountain-->
    <attr name="right_mountain_color" format="color" />
    <!--The color of the middle mountain-->
    <attr name="mid_mountain_color" format="color" />
    <!--The color of the background-->
    <attr name="background_color" format="color" />
</declare-styleable>

The main difficulty here is the understanding and use of animation:

matrix.postTranslate(dx, dy);
path.transform(matrix, momputePath);
canvas.drawPath(momputePath, mPaint);

We achieve the dynamic effect by dynamically changing the values of dx and dy, and then draw triangles, circles, rounded rectangles and their coordinate positions.

The above source code can be obtained here: https://github.com/youlookwhat/SceneryView/blobhttps://github.com/youlookwhat/SceneryView/blob/master/library/src/main/java/me/jingbin/scenery/SceneryView.java

end of document

Your favorite collection is my greatest encouragement!
Welcome to follow my brief book, share Android dry goods and exchange Android technology.
If you have any opinions on the article or any technical problems, please leave a message in the comment area for discussion!

 

Posted on Mon, 06 Dec 2021 23:13:51 -0500 by jasonbullard