Original link: https://juejin.cn/post/702488...
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/47...
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 center point) + Radius of the entire View + Sun radius + 20(sun distance from the lowest edge of the entire View)) / two 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 entire View) - 80(sun height from center point) - 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) { // Zuo Zuo Shan How much does the Y coordinate move down relative to the center point int lrmYpoint = down + getValue(30); // Zuo Zuo Shan How much does the X coordinate move left or right relative to the center point int lrdPoint = getValue(120); // Zuo Zuo Shan What is the X spacing of half of the mountain int lrBanDis = getValue(140); // Middle mountain What is the X spacing of half of the 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/youlookwha...
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!