curve
It’s not often used in development, but learning how to draw curves can make your software much bettercreative
andEndless charm
.
Other apis are the basis for drawing, and I think curves are the soul of painting. Take off with it. In this lesson we are going to learn about curves and the application of curves and so on.
I. Curve recognition and understanding
Curve common API 1. First-order curve 2. Second-order curve 3Copy the code
We in junior high school learning to learn a variety of straight lines, circles, ellipses, is xuan… Coordinate system equations for curves and so on, and then let’s review our equations for lines and curves and so on.
- So the first step we’re going to do is we’re going to define a class and we’re going to create a coordinate system, and we’re going to rotate the screen in landscape
package com.example.android_draw.view
import android.content.Context
import android.graphics.Canvas
import android.util.AttributeSet
import android.view.View
/ * * * * ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ * │ ┌ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┐ │ x │ │ Esc │! 1 2 │ │ @ # % 3 $4 │ │ │ 5 6 7 │ │ & * ^ 8 │ │ │ (9) 0 _ + = │ │ - | \ │ ` ~ │ │ * │ ├ ─ ─ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ─ ─ ┤ │ x │ │ Tab │ │ │ │ │ Q W E R T I │ │ │ Y U │ │ │ P O {[│}] │ BS │ │ x │ ├ ─ ─ ─ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ─ ─ ─ ─ ─ ┤ │ x │ │ Ctrl │ │ │ │ │ │ F G D S A H │ │ │ K J L │ :; │ │ "' Enter │ │ X │ ├ ─ ─ ─ ─ ─ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ─ ─ ─ ┬ ─ ─ ─ ┤ │ X │ │ Shift │ │ X │ │ Z C V │ │ │ N M B , │ │ < >. │? / │ Shift │ Fn │ │ x │ └ ─ ─ ─ ─ ─ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ─ ─ ┬ ┴ ─ ─ ─ ┴ ─ ─ ─ ┴ ─ ─ ─ ┴ ─ ─ ─ ┴ ─ ─ ─ ┴ ─ ─ ┬ ┴ ─ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ┘ │ x │ │ Fn Alt │ │ Space Alt │ │ Win │ HHKB │ x │ └ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ┘ │ * └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ Copyright (c) 2015 Bohai Xinneng All Rights Reserved@authorFeiWang * Version: 1.5 * Created date: 2/8/21 * Description: Android_Draw * E-mail: 1276998208@qq.com
* CSDN:https://blog.csdn.net/m0_37667770/article
* GitHub:https://github.com/luhenchang
*/
class LHC_Cubic_View @JvmOverloads constructor( context: Context? , attrs: AttributeSet? =null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
init{}override fun onDraw(canvas: Canvas?). {
super.onDraw(canvas)
}
}
Copy the code
In order to facilitate observation and rendering, grid and coordinate axes are drawn. I believe that I have mastered the transformation operation of canvas in the last article. I will not explain the code of grid axis. Look at the picture.
1. The equation is mapped to the coordinate system
Remember we learned in junior high that Y of x is equal to ax plus b. So let’s look at this equation mapped to the coordinate system. I’m going to define a function y equals 2x minus 80 and I’m going to get a set of points, and for the sake of effect we’re going to draw x even, and then we’re going to draw points. The code is as follows:
private var number=0.420.
// The equation of the line is y=2x-80
private fun drawzxLine(canvas: Canvas) {
pointList= ArrayList()
// Draw y=10x+20
val gPaint = getPaint()
number.forEach { t ->
val point=PointF()
if (t%2= =0) {// Draw even points on the X-axis
point.x = t.toFloat()
point.y = 2f * t - 80
pointList.add(point)
canvas.drawPoint(point.x, point.y, gPaint)
}
}
}
Copy the code
1. The equations of the same circle and ellipse can be mapped to the coordinate system in this way.
2.The curve represented is a circle with center O(a, b) and radius R.
3. For example: (x – 10)2+(y-10)2= 1602Let’s map it to the coordinate system.
- I should be able to solve the equation. Let’s convert to the familiar equation:
(x-10)2+(y-10)2=1602 1. (y-10)2=1602-(x-10)2 2 The equation is as follows: There are positive and negative values after the square root
- Y = SQRT (160.0 pow (2.0). The toFloat () – ((point. X – 10). ToDouble ()). The pow (2.0)). ToFloat () + 10
- Y = – SQRT (160.0 pow (2.0). The toFloat () – ((pointDown. X – 10). ToDouble ()). The pow (2.0)). ToFloat () + 10
// Draw the circle
number.forEach { t ->
val point = PointF()
val pointDown = PointF()
//(x-10)2+ (y-10)2 =1602
point.x = t.toFloat()
pointDown.x = t.toFloat()
// I don't need to tell you.
point.y =
sqrt(160.0.pow(2.0).toFloat() - ((point.x - 10).toDouble()).pow(2.0)).toFloat() + 10
pointDown.y = -sqrt(
160.0.pow(2.0).toFloat() - ((pointDown.x - 10).toDouble()).pow(2.0)
).toFloat() + 10
canvas.drawPoint(point.x, point.y, gPaint)
canvas.drawPoint(pointDown.x, pointDown.y, gPaint)
}
Copy the code
2. Bezier curve
From the above we can see that any function can be mapped to coordinate system drawing one by one, and of course there are equations for Bezier curves. There are the following:
Linear Bezier curve
- Given points P0, P1, the linear Bezier curve is just a straight line between two points. The line is given by the following formula:
Quadratic Bezier curve
- The path of the quadratic Bezier curve is traced by B (t) of the given points P0, P1, and P2:
Bessel curve to the third power
The points P0, P1, P2, and P3 define a cubic Bezier curve in the plane or in three dimensions. It starts at P0 going to P1, and it goes from P2 to P3. It usually doesn’t go through P1 or P2; The formula is as follows:
Of course, the Native layer of Android terminal has encapsulated methods, quadratic Bezier curve and cubic Bezier curve, and known functions can be encapsulated.
Second and third order bezier curves are provided on the Android side:public void quadTo(float x1, float y1, float x2, float y2)
publicVoid rQuadTo(float dx1, float dy1, float dx2, float dy2)public void cubicTo(float x1, float y1, float x2, float y2,float x3, float y3)
public void rCubicTo(float x1, float y1, float x2, float y2,float x3, float y3)
Copy the code
Next, we draw a second-order curve, and the control point can move the screen with the gesture and press down. It is very clear in the previous section of the broken line about the mapping between the gesture coordinate system and the screen coordinate system, which will not be explained here.
- quadTo(float x1, float y1, float x2, float y2)
// Record the moving canvas coordinates, not gesture coordinates, which are converted to canvas coordinates for refreshing
private var moveX: Float = 160f
private var moveY: Float = 160f
private fun drawQuz(canvas: Canvas) {
controllRect = Rect(
(moveX - 30f).toInt(),
(moveY + 30f).toInt(),
(moveX + 30).toInt(),
(moveY - 30f).toInt()
)
val quePath = Path()
canvas.drawCircle(0f.0f.10f, getPaintCir(Paint.Style.FILL))
canvas.drawCircle(320f.0f.10f, getPaintCir(Paint.Style.FILL))
// The first point and control point are connected to the last point chain. For the sake of observation
val lineLeft = Path()
lineLeft.moveTo(0f.0f)
lineLeft.lineTo(moveX, moveY)
lineLeft.lineTo(320f.0f)
canvas.drawPath(lineLeft, getPaint(Paint.Style.STROKE))
// Draw a circle at p0. I'm going to draw a control point circle at the second p1, and then I'm going to draw a control point circle at the end.
canvas.drawCircle(moveX, moveY, 10f, getPaintCir(Paint.Style.FILL))
quePath.quadTo(moveX, moveY, 320f.0f)
canvas.drawPath(quePath, getPaint(Paint.Style.STROKE))
}
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
ACTION_DOWN,
ACTION_MOVE -> {
// Within the range near the control point, move
Log.e("x="."onTouchEvent: (x,y)"+(event.x - width / 2).toInt()+":"+(-(event.y - height / 2)).toInt())
// Convert gesture coordinates to screen coordinates
moveX = event.x - width / 2
moveY = -(event.y - height / 2)
invalidate()
}
}
return true
}
Copy the code
In the figure above, you can drag the control point, and the curve between the beginning and the end deforms with the control point. The protrusion near the radian of the control point is inclined to the other side. It is only necessary to have a preliminary understanding of this rule, and the control point is constantly adjusted in practice to meet our needs. But in the figure above we see that the radians are not circular enough, and we can adjust the radians well in the third order function. Now let’s look at third-order functions
The third order curve
- public void cubicTo(float x1, float y1, float x2, float y2,float x3, float y3)
And again we’re going to plot the third order curve in the coordinate system. In order to get a good view of the effect we are going to do fine control this time, we can drag any control point we want and look at our third order curve. In the previous section, the contains method of Rect with the gesture can be used for local clicking, of course, dragging is also no problem. As shown in the figure below: We only need to draw the distance shape near the control point to wrap the control point, and the control point and corresponding distance shape can be refreshed by gesture sliding.
private fun drawCubic(canvas: Canvas) {
val cubicPath=Path()
cubicPath.moveTo(0f.0f)
cubicLeftRect= Rect(
(moveCubiX - 30f).toInt(),
(moveCubiY - 30f).toInt(),
(moveCubiX + 30).toInt(),
(moveCubiY + 30f).toInt()
)
cubicRightRect=Rect(
(moveCubiXX - 30f).toInt(),
(moveCubiYY - 30f).toInt(),
(moveCubiXX + 30).toInt(),
(moveCubiYY + 30f).toInt()
)
val lineLeft = Path()
lineLeft.moveTo(0f.0f)
lineLeft.lineTo(moveCubiX, moveCubiY)
lineLeft.lineTo(moveCubiXX, moveCubiYY)
lineLeft.lineTo(320f.0f)
canvas.drawPath(lineLeft, getPaint(Paint.Style.STROKE,Color.GRAY))
//canvas.drawRect(cubicLeftRect, getPaint(Paint.Style.FILL,Color.RED))
//canvas.drawRect(cubicRightRect, getPaint(Paint.Style.FILL,Color.RED))
canvas.drawCircle(moveCubiX, moveCubiY, 10f, getPaintCir(Paint.Style.FILL))
canvas.drawCircle(moveCubiXX, moveCubiYY, 10f, getPaintCir(Paint.Style.FILL))
cubicPath.cubicTo(moveCubiX,moveCubiY,moveCubiXX,moveCubiYY,320f.0f)
canvas.drawPath(cubicPath, getPaint(Paint.Style.STROKE,Color.RED))
}
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
ACTION_DOWN,
ACTION_MOVE -> {
// Within the range near the control point, move
Log.e(
"x="."onTouchEvent: (x,y)" + (event.x - width / 2).toInt() + ":" + (-(event.y - height / 2)).toInt()
)
// Second order curve
if (controllRect.contains((event.x - width / 2).toInt(),(-(event.y - height / 2)).toInt())) {
Log.e("Click here"."To" )
moveX = event.x - width / 2
moveY = -(event.y - height / 2)
invalidate()
// Control point 1 of third order curve
}else if(cubicLeftRect.contains((event.x - width / 2).toInt(),(-(event.y - height / 2)).toInt())){
moveCubiX= event.x - width / 2
moveCubiY= -(event.y - height / 2)
invalidate()
// Third order curve control point 2
}else if(cubicRightRect.contains((event.x - width / 2).toInt(),(-(event.y - height / 2)).toInt())){
moveCubiXX= event.x - width / 2
moveCubiYY= -(event.y - height / 2)
invalidate()
}
}
}
return true
}
Copy the code
Now I think we have a pretty good idea of what second and third order curves are controlling in the general direction of radians. You think this is the end of it. Now let’s begin the formal application of the entry curve.
Curve – Simple application
In qq group no one asked how to do the effect. I think this should be pretty easy for you. We are going to use the above learning curve to write.
First prepare 1. A picture 2. Draw a rectangular box 3. Drag curve 5. Change hue saturation brightness, etc
1. Understanding of image hue saturation, etc
Android does not provide an API for manipulating the Bitmap directly to change hue saturation, so we need to draw the Bitmap onto the canvas. So let’s start writing code. First create class LHC_Image_View and add attributes to attrs.
attribute
<declare-styleable name="LHC_Image_View">
<attr name="defaultImag" format="reference" />
</declare-styleable>
Copy the code
Get the property image and draw it to the canvas, where we need to know a little bit about the color API. ColorMatrix image is composed of countless pixels, each pixel with RGBA four values to describe, specifically by a 4*5 matrix to control the display of each pixel, point RGBA and together constitute the appearance of the bitmap expression form, so change the control pixel RGBA value, you can change the effect of bitmap display
The 'ColorMatrix', represented by a 4x5 matrix, is used to convert the color and alpha components of the bitmap. [a, b, c, d, e, f, g, h, I, j, k, l, m, n, o, p, q, r, s, t] calculation is as follows: r = a * r g + c + b * * * a + b + d e; G = f*R + g*G + h*B + i*A + j; B = k*R + l*G + m*B + n*A + o; A = p*R + q*G + r*B + s*A + t; Common default standard matrix [1 0 0 0 0 R=225 R1=225 + 0+ 0+ 0+ 0+ offset =225 0 1 0 0 x G=225 = G1=225 + 0+ 0+ 0+ offset =225 = RGBA=[225,225,225,225] 0 0 1 0 0 B=225 B1=225 + 0+ 0+ 0+ offset =225 0 0 0 1 0] A=225 A1=225 + 0+ 0+ 0+ offset =225 I want to lower the green and blue, so we can change the matrix 2 rows, 2 columns, 1 to 0.5,3 rows,3 columns, 1 to 0.5 and so on. Public ColorMatrix() {reset(); } /** * Create a new colormatrix initialized with the specified array of values. */ public ColorMatrix(float[] src) { System.arraycopy(src, 0, mArray, 0, 20); } /** * Create a new colormatrix initialized with the specified colormatrix. */ public ColorMatrix(ColorMatrix src) { System.arraycopy(src.mArray, 0, mArray, 0, 20); }Copy the code
Common use method in ColorMatrix :setRotate(int axis, PI / 180d here in order to control the matrix array developers easy to write to use the positive remainder function, -1 to 1 to 0-360 degrees, of course, this is my guess, after all, -1 to 1 decimal is not easy to grasp. For your convenience, please attach a positive image:
From the graph we can see that 0 degrees and 90 degrees are 0 and 1 respectively, so next we draw a picture set to int=0 for red.. Degrees =90 degrees
[ 1 0 0 0 0 R 0 1 0 0 0 x G 0 0 1 0 0 B =R G B A 0 0 0 1 0 ] A [ 1 0 0 0 0 R 0 0 1 0 0 x G =R B -G A 0 -1 0 0 0 B 0 0 0 So the red stays the same, the green becomes blue, the blue becomes negative, and we know that red and blue become yellow. So let's verify our results.Copy the code
public void setRotate(int axis, float degrees) {
reset();
double radians = degrees * Math.PI / 180d;
float cosine = (float) Math.cos(radians);
float sine = (float) Math.sin(radians);
switch (axis) {
// Rotation around the red color
case 0:
mArray[6] = mArray[12] = cosine;
mArray[7] = sine;
mArray[11] = -sine;
break;
// Rotation around the green color
case 1:
mArray[0] = mArray[12] = cosine;
mArray[2] = -sine;
mArray[10] = sine;
break;
// Rotation around the blue color
case 2:
mArray[0] = mArray[6] = cosine;
mArray[1] = sine;
mArray[5] = -sine;
break;
default:
thrownew RuntimeException(); }}Copy the code
Verification results:
class LHC_Image_View @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val MIDDLE_VALUE=127
private var mdrawable: Drawable?
lateinit var bitmap:Bitmap
init {
val array: TypedArray = context.obtainStyledAttributes(attrs, R.styleable.LHC_Image_View)
mdrawable = array.getDrawable(R.styleable.LHC_Image_View_defaultImag)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
if(mdrawable! =null){ bitmap =mdrawable!! .toBitmap(width,height, Bitmap.Config.ARGB_8888) }else{
return
}
// instantiate a brush
val mPaint = Paint()
mPaint.strokeWidth=10f
// Instantiate the color matrix that handles the hue
val colorMatrix = ColorMatrix()
// Get the hue calculation formula
val gress=90f
// 0 is red
colorMatrix.setRotate(0, gress)
// Set the color to the brush
mPaint.colorFilter = ColorMatrixColorFilter(colorMatrix)
// Then we use the adjusted color brush to paint the original image BMP onto the new bitmap
canvas.drawBitmap(bitmap, 0f.0f, mPaint)
}
}
Copy the code
Verification succeeded. So let’s go ahead and plot our curves, and get a little off topic.
2. Draw a rectangle
This one I think is super easy.
private fun drawGrid(canvas: Canvas) {
//1. The lower left corner is the dot on the screen
canvas.translate(0f, height.toFloat())
canvas.scale(1f, -1f)
canvas.save()
val xpath=Path()
xpath.moveTo(0f.0f)
xpath.lineTo(width.toFloat(), 0f)
val paint=Paint()
paint.color=Color.GRAY
paint.strokeWidth=2f
paint.style= Paint.Style.STROKE
for (index in 0 until 6){
canvas.translate(0f.160f)
canvas.drawPath(xpath,paint)
}
canvas.restore()
val ypath=Path()
ypath.moveTo(0f.0f)
ypath.lineTo(0f, width.toFloat()-120)
val painty=Paint()
painty.color=Color.GRAY
painty.strokeWidth=2f
painty.style= Paint.Style.STROKE
canvas.save()
for (index in 0 until 6){
canvas.translate(160f.0f)
canvas.drawPath(ypath,paint)
}
canvas.restore()
}
Copy the code
3. Lines and curves with a gesture
Draw a line and a curve and a circle with a gesture. Also talk about a pepper, the above all learned, RIGHT? I think the little friends who work harder can only think of can not do it. If you haven’t — and I believe you haven’t read my article carefully — here’s the code:
private fun drawLineAndCubit(canvas: Canvas) {
val paint=Paint()
paint.color=Color.GRAY
paint.strokeWidth=2f
paint.style= Paint.Style.STROKE
/ / slash
val xpath=Path()
xpath.moveTo(10f.10f)
xpath.lineTo(width.toFloat(), width.toFloat()-120)
canvas.drawPath(xpath,paint)
// Start and finish circles
paint.style= Paint.Style.FILL
paint.color=Color.RED
canvas.drawCircle(15f.15f.15f,paint)
canvas.drawCircle(width.toFloat()-15, width.toFloat()-120-15.15f,paint)
val cubicPath=Path()
cubicPath.moveTo(0f.0f)
paint.style= Paint.Style.STROKE
paint.strokeWidth=5f
cubicPath.quadTo(moveX, moveY,width.toFloat()-15, width.toFloat()-120-15)
// Draw a curve
canvas.drawPath(cubicPath,paint)
}
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN,
MotionEvent.ACTION_MOVE -> {
// Within the range near the control point, move
moveX= event.x
moveY= -(event.y - height)
invalidate()
}
}
return true
}
Copy the code
Three, the use of curve, texture, cutting
Many floating spheres such as energy or progress spheres have bezier curves. Let’s do a simple application, write a small animation of fish swimming. This process we need is simple math calculation, canvas cropping, drawing pictures to canvas and other basic common operations. The following small case is analyzed step by step with the combination of curve animation and related API.
- Draw the analysis
1. Wave drawing -> Curve drawing 2. Moving wave -> Canvas translation 3. Always moving animation 4. fish swimming with waves -> Drawing of pictures on canvasCopy the code
1. Wave drawing
In the image above we can see the curved waves in constant motion. So how are waves painted? The Bezier curve would have done the following.
Code:
// Draw waves
private fun drawWave(canvas: Canvas) {
val wavePath=Path()
wavePath.moveTo(0f, -waveWidth * 6)
wavePath.lineTo(0f.0f)
wavePath.quadTo(waveWidth, waveHeight, waveWidth * 2.0f)
wavePath.quadTo(waveWidth * 3, -waveHeight, waveWidth * 4.0f)
wavePath.quadTo(waveWidth * 5, waveHeight, waveWidth * 6.0f)
wavePath.quadTo(waveWidth * 7, -waveHeight, waveWidth * 8.0f)
wavePath.lineTo(waveWidth * 8, -waveWidth * 6)
canvas.drawPath(wavePath, getPaintBefor(Paint.Style.FILL))
Copy the code
2. Moving waves
The drawing of waves is only filled with colors after the curve is drawn. The movement of the curve can be accomplished simply by moving the canvas.
// The waves move with the pan of the canvas.
canvas.translate(animal.animatedValue as Float.0f)
Copy the code
The effect of an ocean wave moving in a round or rectangular or a container of various shapes. All we have to do is cut the canvas to the desired shape and let the waves watch over the cut area.
Code:
private fun clipCanvas(canvas: Canvas,type:Int) {
if (type==0) {
val rect = Rect(
(waveWidth * 4).toInt(),
160,
(waveWidth * 8).toInt(),
(-waveWidth * 4).toInt()
)
canvas.clipRect(rect)
}else if (type==1) {
val circlePath = Path()
circlePath.addCircle(480f.0f.160f, Path.Direction.CCW)
canvas.drawCircle(480f.0f.160f, getPaint(Paint.Style.STROKE))
canvas.clipPath(circlePath)
}else if (type==2) {val rundRect = Path()
rundRect.addRoundRect(waveWidth * 4,waveWidth*3,waveWidth* 8,-waveWidth*3.60f.60f,Path.Direction.CCW)
//canvas.drawPath(rundRect,getPaint(Paint.Style.STROKE))
canvas.clipPath(rundRect)
}
}
Copy the code
But setting the value of the animation is a simple math calculation.
The value of animation only needs to be shifted by a whole wavelength = waveWidth * 4
init {
animal=ObjectAnimator.ofFloat(0f, waveWidth * 4)
animal.duration = 2000
animal.repeatCount = ValueAnimator.INFINITE;
animal.interpolator = LinearInterpolator()
animal.addUpdateListener {
invalidate()
}
}
Copy the code
3. Fish swimming with the waves
Canvas pan. If you place a position map on the canvas, the picture will move as the canvas moves.
// You can read the related API to draw an image to the canvas coordinate system (left,top) position.
drawBitmap(@NonNull Bitmap bitmap, float left, float top, @Nullable Paint paint)
Copy the code
Material can be downloaded in alibaba picture material library.
val arrList= arrayListOf( R.drawable.yu, R.drawable.ziyuan, R.drawable.jingyu)
private fun drawFish(canvas: Canvas) {
val bmp = BitmapFactory.decodeResource(resources, arrList[0])
canvas.drawBitmap(bmp, waveWidth * 3, -waveHeight * 2.2 f, getPaint(Paint.Style.FILL))
val bmpzy = BitmapFactory.decodeResource(resources,arrList[1])
canvas.drawBitmap(bmpzy, waveWidth, -waveHeight *3f, getPaint(Paint.Style.FILL))
val bmpjy= BitmapFactory.decodeResource(resources,arrList[2])
canvas.drawBitmap(bmpjy, waveWidth* 6, -waveHeight * 3.5 f, getPaint(Paint.Style.FILL))
}
Copy the code
End result:
Code:
package com.example.android_draw.view
import android.animation.ObjectAnimator
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.MotionEvent.ACTION_DOWN
import android.view.MotionEvent.ACTION_MOVE
import android.view.View
import android.view.animation.LinearInterpolator
import com.example.android_draw.R
import kotlin.math.max
import kotlin.random.Random
/ * * * * ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ * │ ┌ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┬ ─ ─ ─ ┐ │ x │ │ Esc │! 1 2 │ │ @ # % 3 $4 │ │ │ 5 6 7 │ │ & * ^ 8 │ │ │ (9) 0 _ + = │ │ - | \ │ ` ~ │ │ * │ ├ ─ ─ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ─ ─ ┤ │ x │ │ Tab │ │ │ │ │ Q W E R T I │ │ │ Y U │ │ │ P O {[│}] │ BS │ │ x │ ├ ─ ─ ─ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ─ ─ ─ ─ ─ ┤ │ x │ │ Ctrl │ │ │ │ │ │ F G D S A H │ │ │ K J L │ :; │ │ "' Enter │ │ X │ ├ ─ ─ ─ ─ ─ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ┬ ─ ┴ ─ ─ ─ ─ ┬ ─ ─ ─ ┤ │ X │ │ Shift │ │ X │ │ Z C V │ │ │ N M B , │ │ < >. │? / │ Shift │ Fn │ │ x │ └ ─ ─ ─ ─ ─ ┬ ─ ─ ┴ ┬ ─ ─ ┴ ─ ─ ┬ ┴ ─ ─ ─ ┴ ─ ─ ─ ┴ ─ ─ ─ ┴ ─ ─ ─ ┴ ─ ─ ─ ┴ ─ ─ ┬ ┴ ─ ─ ─ ┴ ┬ ─ ─ ┴ ┬ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ┘ │ x │ │ Fn Alt │ │ Space Alt │ │ Win │ HHKB │ x │ └ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ┘ │ * └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ Copyright (c) 2015 Bohai Xinneng All Rights Reserved@authorFeiWang * Version: 1.5 * Created date: 2/8/21 * Description: Android_Draw * E-mail: 1276998208@qq.com
* CSDN:https://blog.csdn.net/m0_37667770/article
* GitHub:https://github.com/luhenchang
*/
@Suppress("DEPRECATION")
class LHC_wave_View @JvmOverloads constructor( context: Context? , attrs: AttributeSet? =null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private var moveX: Float = 0f
private var moveY: Float = 0f
private var hCount: Int = 0
private var wCount: Int = 0
private lateinit var pointList: ArrayList<PointF>
// Width of grid
var gridWidth = 80
/ / half wavelength
val waveWidth=80f
val waveHeight=30f
lateinit var animal:ValueAnimator
init {
animal=ObjectAnimator.ofFloat(0f, waveWidth * 4)
animal.duration = 2000
animal.repeatCount = ValueAnimator.INFINITE;
animal.interpolator = LinearInterpolator()
animal.addUpdateListener {
invalidate()
}
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// Draw grid lines
drawGridLine(canvas)
// Draw text on the x and y axes
drawTextXAndY(canvas)
// Crop the canvas
clipCanvas(canvas,2)
// Draw waves
drawWave(canvas)
/ / to draw
drawFish(canvas)
}
val arrList= arrayListOf( R.drawable.yu, R.drawable.ziyuan, R.drawable.jingyu)
private fun drawFish(canvas: Canvas) {
val bmp = BitmapFactory.decodeResource(resources, arrList[0])
canvas.drawBitmap(bmp, waveWidth * 3, -waveHeight * 2.2 f, getPaint(Paint.Style.FILL))
val bmpzy = BitmapFactory.decodeResource(resources,arrList[1])
canvas.drawBitmap(bmpzy, waveWidth, -waveHeight *3f, getPaint(Paint.Style.FILL))
val bmpjy= BitmapFactory.decodeResource(resources,arrList[2])
canvas.drawBitmap(bmpjy, waveWidth* 6, -waveHeight * 3.5 f, getPaint(Paint.Style.FILL))
}
private fun clipCanvas(canvas: Canvas,type:Int) {
if (type==0) {
val rect = Rect(
(waveWidth * 4).toInt(),
160,
(waveWidth * 8).toInt(),
(-waveWidth * 4).toInt()
)
canvas.clipRect(rect)
}else if (type==1) {
val circlePath = Path()
circlePath.addCircle(480f.0f.160f, Path.Direction.CCW)
canvas.drawCircle(480f.0f.160f, getPaint(Paint.Style.STROKE))
canvas.clipPath(circlePath)
}else if (type==2) {val rundRect = Path()
rundRect.addRoundRect(waveWidth * 4,waveWidth*3,waveWidth* 8,-waveWidth*3.60f.60f,Path.Direction.CCW)
//canvas.drawPath(rundRect,getPaint(Paint.Style.STROKE))
canvas.clipPath(rundRect)
}
}
// Draw waves
private fun drawWave(canvas: Canvas) {
canvas.translate(animal.animatedValue as Float.0f) // Inner wave
val wavePath=Path()
wavePath.moveTo(0f, -waveWidth * 6)
wavePath.lineTo(0f.0f)
wavePath.quadTo(waveWidth, waveHeight, waveWidth * 2.0f)
wavePath.quadTo(waveWidth * 3, -waveHeight, waveWidth * 4.0f)
wavePath.quadTo(waveWidth * 5, waveHeight, waveWidth * 6.0f)
wavePath.quadTo(waveWidth * 7, -waveHeight, waveWidth * 8.0f)
wavePath.lineTo(waveWidth * 8, -waveWidth * 6)
canvas.drawPath(wavePath, getPaintBefor(Paint.Style.FILL))
canvas.translate(animal.animatedValue as Float.0f) // Inner wave
val wavePath_out=Path()
wavePath_out.moveTo(-waveWidth * 7, -waveWidth * 6)
wavePath_out.lineTo(-waveWidth * 7.0f)
wavePath_out.quadTo(-waveWidth * 7, waveHeight, -waveWidth * 6.0f)
wavePath_out.quadTo(-waveWidth * 5, -waveHeight, -waveWidth * 4.0f)
wavePath_out.quadTo(-waveWidth * 3, waveHeight, -waveWidth * 2.0f)
wavePath_out.quadTo(-waveWidth, -waveHeight, 0f.0f)
wavePath_out.quadTo(waveWidth, waveHeight, waveWidth * 2.0f)
wavePath_out.quadTo(waveWidth * 3, -waveHeight, waveWidth * 4.0f)
wavePath_out.quadTo(waveWidth * 5, waveHeight, waveWidth * 6.0f)
wavePath_out.quadTo(waveWidth * 7, -waveHeight, waveWidth * 8.0f)
wavePath_out.lineTo(waveWidth * 8, -waveWidth * 6)
canvas.drawPath(wavePath_out, getPaint(Paint.Style.FILL))
}
private fun getPaintIn(style: Paint.Style): Paint {
val gPaint = Paint()
gPaint.color = Color.BLUE
gPaint.strokeWidth = 2f
gPaint.isAntiAlias = true
gPaint.style = style
gPaint.textSize = 26f
gPaint.color = Color.argb(255.75.151.79)
var linearGradient = LinearGradient(
waveWidth * 4, -waveWidth * 8,
waveWidth * 4.80f,
Color.argb(255.47.26.253),
Color.argb(255.24.220.253),
Shader.TileMode.CLAMP
)
gPaint.shader=linearGradient
return gPaint
}
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
ACTION_DOWN,
ACTION_MOVE -> {
animal.start()
// Within the range near the control point, move
Log.e(
"x="."onTouchEvent: (x,y)" + (event.x - width / 2).toInt() + ":" + (-(event.y - height / 2)).toInt()
)
moveX = event.x - width / 2
moveY = -(event.y - height / 2)
invalidate()
}
}
return true
}
private fun drawTextXAndY(canvas: Canvas) {
val gPaint = Paint()
gPaint.color = Color.BLUE
gPaint.strokeWidth = 2f
gPaint.isAntiAlias = true
gPaint.style = Paint.Style.STROKE
gPaint.textSize = 26f
gPaint.color = Color.argb(255.75.151.79)
canvas.scale(-1f.1f)
canvas.save()
canvas.scale(1f, -1f)
// X-axis square text
for (index in 1 until wCount / 2) {
val rectText = Rect()
canvas.translate(160f.0f)
gPaint.getTextBounds(
(80 * index * 2).toString(),
0,
(80 * index * 2).toString().length,
rectText
)
canvas.drawText(
(80 * index * 2).toString(),
-(rectText.width() / 2).toFloat(), rectText.height().toFloat() * 2f, gPaint
)
}
canvas.restore()
canvas.save()
// Draw text in the negative direction of the x axis
canvas.scale(1f, -1f)
for (index in 1 until wCount / 2) {
val rectText = Rect()
canvas.translate(-160f.0f)
gPaint.getTextBounds(
"-The ${(80 * index * 2)}".0,
(80 * index * 2).toString().length,
rectText
)
canvas.drawText(
"-The ${(80 * index * 2)}",
-(rectText.width() / 2).toFloat(), rectText.height().toFloat() * 2f, gPaint
)
}
canvas.restore()
canvas.save()
// Draw text in the negative direction of the x axis
canvas.scale(1f, -1f)
canvas.translate(20f.0f)
// Negative y direction
for (index in 1 until hCount / 2) {
val rectText = Rect()
canvas.translate(0f.160f)
gPaint.getTextBounds(
"-The ${(80 * index * 2)}".0,
(80 * index * 2).toString().length,
rectText
)
canvas.drawText(
"-The ${(80 * index * 2)}".0f, rectText.height().toFloat(), gPaint
)
}
canvas.restore()
canvas.save()
canvas.scale(1f.1f)
canvas.translate(20f.0f)
// Positive y direction
for (index in 1 until hCount / 2) {
val rectText = Rect()
canvas.translate(0f.160f)
canvas.save()
canvas.scale(1f, -1f)
gPaint.getTextBounds(
"The ${(80 * index * 2)}".0,
(80 * index * 2).toString().length,
rectText
)
canvas.drawText(
"The ${(80 * index * 2)}".0f, rectText.height().toFloat(), gPaint
)
canvas.restore()
}
canvas.restore()
}
private fun drawGridLine(canvas: Canvas) {
// Initialize a brush
val gPaint = Paint()
gPaint.color = Color.BLUE
gPaint.strokeWidth = 2f
gPaint.isAntiAlias = true
gPaint.style = Paint.Style.FILL
gPaint.shader = RadialGradient(
0f.0f,
max(width, height) / 2f,
Color.BLUE,
Color.YELLOW,
Shader.TileMode.CLAMP
)
// The screen width and height are known in onDraw
val screenWidth = width
val screenHeight = height
// The number of wide cells
wCount = screenWidth / gridWidth
// The number of high cells
hCount = screenHeight / gridWidth
//1. Move the coordinate point to the midpoint of the screen
canvas.translate((screenWidth / 2).toFloat(), (screenHeight / 2).toFloat())
// The direction of the overall coordinate system changes clockwise
//2. Change the positive direction above the y axis.
canvas.scale(1f, -1f)
// Draw the x and y axes
canvas.drawLine(-screenWidth / 2f.0f, screenWidth / 2f.0f, gPaint)
canvas.drawLine(0f, -screenHeight / 2f.0f, screenHeight / 2f, gPaint)
gPaint.color = Color.argb(61.111.111.111)
drawGridCode(canvas, screenWidth, gPaint, hCount, screenHeight, wCount)
//2. Change the positive direction below the y axis.
canvas.scale(1f, -1f)
drawGridCode(canvas, screenWidth, gPaint, hCount, screenHeight, wCount)
//3. Change the positive left direction of the X axis.
canvas.scale(-1f.1f)
drawGridCode(canvas, screenWidth, gPaint, hCount, screenHeight, wCount)
//4. Change x to positive
canvas.scale(1f, -1f)
drawGridCode(canvas, screenWidth, gPaint, hCount, screenHeight, wCount)
}
private fun drawGridCode(
canvas: Canvas,
screenWidth: Int,
gPaint: Paint,
hCount: Int,
screenHeight: Int,
wCount: Int
) {
// Save the snapshot of the center of the screen into the stack. Convenient for later operation.
canvas.save()
// draw a horizontal line starting at the point (0,0)
//canvas.drawLine(0f, 0f, (screenWidth / 2).toFloat(), 0f, gPaint)
//3. Draw a line parallel to the X-axis that completes the first quadrant
for (index in 0 until hCount) {
// The coordinate system dot is continuously shifted upwards to the height of gridWidth
canvas.translate(0f, gridWidth.toFloat())
// Draw a straight line at the translated dot
canvas.drawLine(0f.0f, (screenWidth / 2).toFloat(), 0f, gPaint)
}
// Restore to the snapshot state. The dot is in the center
canvas.restore()
canvas.save()
//4. Draw parallel to y axis
//canvas.drawLine(0f, 0f, 0f, screenHeight / 2f, gPaint)
for (index in 0 until wCount) {
// The coordinate system dot is continuously shifted upwards to the height of gridWidth
canvas.translate(gridWidth.toFloat(), 0f)
// Draw a straight line at the translated dot
canvas.drawLine(0f.0f.0f, screenHeight / 2f, gPaint)
}
// Restore to the snapshot state. The dot is in the center
canvas.restore()
}
private fun getPaintBefor(style: Paint.Style): Paint {
val gPaint = Paint()
gPaint.color = Color.BLUE
gPaint.strokeWidth = 2f
gPaint.isAntiAlias = true
gPaint.style = style
gPaint.textSize = 26f
gPaint.color = Color.argb(255.75.151.79)
var linearGradient = LinearGradient(
waveWidth * 4, -waveWidth * 8,
waveWidth * 4.80f,
Color.argb(155.27.134.244),
Color.argb(195.24.220.253),
Shader.TileMode.CLAMP
)
gPaint.shader=linearGradient
return gPaint
}
private fun getPaint(style: Paint.Style): Paint {
val gPaint = Paint()
gPaint.color = Color.BLUE
gPaint.strokeWidth = 2f
gPaint.isAntiAlias = true
gPaint.style = style
gPaint.textSize = 26f
gPaint.color = Color.argb(255.75.151.79)
var linearGradient = LinearGradient(
waveWidth * 4, -waveWidth * 2,
waveWidth * 4.80f,
Color.argb(255.27.134.244),
Color.argb(255.24.220.253),
Shader.TileMode.CLAMP
)
gPaint.shader=linearGradient
return gPaint
}
}
Copy the code
4. Curve charts
Charts are the most common and simplest,No more than draw API
,Simple math
,gestures
Can. Next, we will draw a common curve chart in the market.
1. Transform the coordinates
First we transform the left side to our usual coordinate system: leave the left and bottom margin of the text out. I’m going to do the following transformation and if you don’t understand it just look at the coordinate system transformation that we showed you in the last video on polylines.
Scale (1f, -1f) // Coordinate shift leftWidth to the right, flatten down height-bootomheight, where upward is positive. So translation down is negative canvas. Translate (leftWidth, -(bottom-height))Copy the code
// Create a new class LHC_curve_View
class LHC_curve_View @JvmOverloads constructor( context: Context? , attrs: AttributeSet? =null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
// The distance to the left
val leftWidth = 180f
// The height from the bottom
val bottomHeight = 240f
// Width of x axis
var x_scaleWidth=0f
// The width and height of the square
private var grid_width=0f
init{}override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
//2
translateCanvas(canvas)
}
// Pan the canvas
private fun translateCanvas(canvas: Canvas) {
// The y direction is negative
canvas.scale(1f, -1f)
// Frame shift leftWidth to the right, flatten down height-bootomheight, where up is positive. So the shift down is negative
canvas.translate(leftWidth, -(height - bottomHeight))
}
}
Copy the code
2. Draw lines parallel to the X-axis
So let’s draw lines parallel to the X-axis, we’ve switched coordinates up here and now let’s draw lines parallel to the X-axis
// Draw lines parallel to the x axis
private fun drawLine(canvas: Canvas) {
//canvas.drawColor(Color.argb(22, 255, 255, 111))
val line_paint = Paint()
line_paint.strokeWidth = 2f
line_paint.style = Paint.Style.STROKE
line_paint.color = Color.argb(100.188.188.188)
x_scaleWidth = (width - leftWidth - 80f)
grid_width = x_scaleWidth / 6
// Draw the bottom line
val x_path = Path()
x_path.moveTo(0f.0f)
x_path.lineTo(x_scaleWidth, 0f)
canvas.drawPath(x_path, line_paint)
canvas.save()
// Draw the remaining parallel x axes by translating the canvas
(0 until 3).forEach { index ->
canvas.translate(0f, grid_width-40f)
canvas.drawPath(x_path, line_paint)
}
canvas.restore()
}
Copy the code
The effect is as follows:
3. Draw text
- The text below the X axis is nothing more than the measurement of the text and the transformation of the canvas.
1.Paint's getTextBounds are measured in conjunction with Rect 2. Use the canvas save and restore to transform the canvas at willCopy the code
// The text below the horizontal x axis
private fun drawDownOfXLineText(canvas: Canvas) {
val text_paint = Paint()
text_paint.strokeWidth = 2f
text_paint.style = Paint.Style.STROKE
text_paint.color = Color.argb(100.111.111.111)
text_paint.textSize=24f
val rectText=Rect()
canvas.save()
// Rotate the text so that the coordinate system y downward is positive
canvas.scale(1f, -1f)
(0 until 7).forEach { index ->
if(index>0) {
canvas.translate(grid_width, 0f)}val strTx= "11.The ${11+index}"
text_paint.getTextBounds(strTx,0,strTx.length,rectText)
canvas.drawText(strTx, -rectText.width().toFloat()/2, rectText.height().toFloat()*2.5 f,text_paint)
}
canvas.restore()
}
private fun drawLeftOfYLineText(canvas: Canvas) {
val text_paint = Paint()
text_paint.strokeWidth = 2f
text_paint.style = Paint.Style.STROKE
text_paint.color = Color.argb(100.111.111.111)
text_paint.textSize=24f
val rectText=Rect()
canvas.save()
// Rotate the text so that the coordinate system y downward is positive
(0 until 4).forEach { index ->
if(index>0) {
canvas.translate(0f, grid_width-40f)}var strTx=""
if(index==0){
strTx="${index}"
}else if(index==1){
strTx="The ${500}"
}else if(index==2){
strTx="1k"
}else{
strTx="1.5 k"
}
canvas.save()
canvas.scale(1f, -1f)
text_paint.getTextBounds(strTx,0,strTx.length,rectText)
canvas.drawText(strTx, -rectText.width().toFloat()-42f, rectText.height().toFloat()/2,text_paint)
canvas.restore()
}
canvas.restore()
}
Copy the code
The effect
3. Curve drawing
- Second order curve drawing, we will find the lack of control points. Now let’s calculate the control points
The control points controX=(x1x_1X1 + X2X_2X2)/2 and controY=(y1y_1Y1 + Y2y_2y2)/2. Now let’s plot the curve. X-axis grid_width=1 day time. It’s not hard to figure out x for the intermediate control point. Y = grid_width – 40 500 f.
The code is as follows:
private fun drawCaves(canvas: Canvas) {
val text_paint = Paint()
text_paint.strokeWidth = 2f
text_paint.style = Paint.Style.STROKE
text_paint.color = Color.argb(100.111.111.111)
val caves_path=Path()
//500=grid_width-40 Each unit of length = pixel length
val danweiY=(grid_width-40) /500
val danweiX=(grid_width)
for (index in 0 until dataList.size-1) {if (dataList[index]==dataList[index+1]){
caves_path.quadTo(
(grid_width * index + grid_width * (1 + index)) / 2.0f,
grid_width * (index + 1),
(dataList[index + 1].toFloat()) * danweiY
)
}else {
caves_path.quadTo(
(grid_width * index + grid_width * (1 + index)) / 2,
(dataList[index].toFloat() * danweiY + dataList[index + 1].toFloat() * danweiY) / 2 +100,
grid_width * (index + 1),
(dataList[index + 1].toFloat()) * danweiY
)
}
}
canvas.drawCircle(0f.0f.10f,text_paint)
canvas.drawPath(caves_path,text_paint)
}
Copy the code
But we’ll see that there are no radians at either focus. It’s hard, and then we go on to analyze and solve it to perfection. As for the end result, we can be even cooler.
4. The rescue of the third-order curve
When y1y_1y1< y2y_2y2, as shown in the figure 1 above, find the coordinates of the midpoint: control point x+40px in the lower part of the X-axis and x-40px in the upper part, and adjust the Y-axis to make the smoothness of control point Y-40x in the lower part and y+40 in the upper part. 1. Obtain the coordinates of the midpoint (X_ in X, Y_ in Y) =((x1x_1x1+ x2x_2X2)/2, (y1y_1y1+ y2y_2y2)/2) 2. The coordinates between x1x_1x1 and X_ in X =(x1x_1x1+ y2y_2y2)/2 The coordinates between x_ in x and X2X_2X2 =((x_ in x + x_ in y + y_ in y)/2, (y_ in y + y2y_2y2)/2) when y1y_1y1> Y2y_2y2 is shown in figure 2. Figure out the coordinates of the midpoint: the upper part of the X-axis +40px, the lower part x-40px, the Y-axis can also be adjusted, and the Y-axis can also be adjusted to make the smoothness of the upper part of the control point Y +40x, the lower part y-40. 1. Obtain the coordinates of the midpoint (X_ in X, Y_ in Y) =((x1x_1x1+ x2x_2X2)/2, (y1y_1y1+ y2y_2y2)/2) 2. The coordinates between x1x_1x1 and X_ in X =(x1x_1x1+ y2y_2y2)/2 The coordinates between x_ in x and X2X_2X2 =(x_ in x + x_ in y + y_ in y)/2, (y_ in y + y2y2)/2)
Next we heap code:
// Draw a curve
private fun drawCaves(canvas: Canvas) {
val text_paint = Paint()
text_paint.strokeWidth = 2f
text_paint.style = Paint.Style.STROKE
text_paint.color = Color.argb(100.111.111.111)
val caves_path = Path()
//500=grid_width-40 Each unit of length = pixel length
val danweiY = (grid_width - 40) / 500
val danweiX = (grid_width)
for (index in 0 until dataList.size - 1) {
// The display of the second-order curve is awkward
// if (dataList[index]==dataList[index+1]){
// caves_path.quadTo(
// (grid_width * index + grid_width * (1 + index)) / 2,
// 0f,
// grid_width * (index + 1),
// (dataList[index + 1].toFloat()) * danweiY
/ /)
// }else {
// caves_path.quadTo(
// (grid_width * index + grid_width * (1 + index)) / 2,
// (dataList[index].toFloat() * danweiY + dataList[index + 1].toFloat() * danweiY) / 2 +100,
// grid_width * (index + 1),
// (dataList[index + 1].toFloat()) * danweiY
/ /)
/ /}
// Third order curve to display
val xMoveDistance=40
val yMoveDistance=40
if (dataList[index] == dataList[index + 1]) {
caves_path.lineTo(danweiX*(index+1),0f)}else if(dataList[index] < dataList[index + 1]) {/ / y1 < y2
val centerX=(grid_width * index + grid_width * (1 + index)) / 2
val centerY=(dataList[index].toFloat() * danweiY + dataList[index + 1].toFloat() * danweiY) / 2
val controX0=(grid_width * index+centerX)/2
val controY0=(dataList[index].toFloat() * danweiY+centerY)/2
val controX1=(centerX+ grid_width * (1 + index))/2
val controY1=(centerY+dataList[index+1].toFloat() * danweiY)/2caves_path.cubicTo(controX0+xMoveDistance,controY0-yMoveDistance,controX1-xMoveDistance,controY1+yMoveDistance,grid_widt h * (1 + index),dataList[index + 1].toFloat() * danweiY)
}else{
val centerX=(grid_width * index + grid_width * (1 + index)) / 2
val centerY=(dataList[index].toFloat() * danweiY + dataList[index + 1].toFloat() * danweiY) / 2
val controX0=(grid_width * index+centerX)/2
val controY0=(dataList[index].toFloat() * danweiY+centerY)/2
val controX1=(centerX+ grid_width * (1 + index))/2
val controY1=(centerY+dataList[index+1].toFloat() * danweiY)/2caves_path.cubicTo(controX0+xMoveDistance,controY0+yMoveDistance,controX1-xMoveDistance,controY1-yMoveDistance,grid_widt h * (1 + index),dataList[index + 1].toFloat() * danweiY)
}
}
canvas.drawCircle(0f.0f.10f, text_paint)
canvas.drawPath(caves_path, text_paint)
}
Copy the code
Let’s see what happens
- Is there a feeling, no more than the control point for adjustment, we see other people’s more sharp, we for
xMoveDistance
.yMoveDistance
To adjust
val xMoveDistance=20
val yMoveDistance=40
So far our biggest difficulty is simply solved, right? Nothing more than simple addition, subtraction, multiplication and division, right? Next, we are going to decorate beautiful curves. After learning from the previous article, I think we are all familiar with these skills, right? Gradient fill, animation, click, gesture, texture.
6. Beautify curves
- I remember said to want to draw more SAO than it, I think we should be able to do, much and SAO not necessarily beautiful, but everywhere is technology, that comes down us in much and SAO aspect proceed with, as to the result we dare not say more beautiful than UI design right.
1. Embellish your skillsThe gradient
- We noticed a gradual fading of the design from top to bottom. Let’s choose a linear gradient from 0,y to 0,0. Heart no number of traversal for maximum. My data is written down for convenience.
val linearGradient = LinearGradient(
0f.1500 * danweiY,
0f.0f,
Color.argb(255.229.160.144),
Color.argb(255.251.244.240),
Shader.TileMode.CLAMP
)
text_paint.shader = linearGradient
Copy the code
2. Embellish your skillsSurrounded by the Path
- We notice that the UI has a red line around it. So let’s just take the curve and set the brush. I need to add a circle to each vertex.
// Draw a closed gradient curve
canvas.drawPath(caves_path, text_paint)
val line_paint = Paint()
line_paint.strokeWidth = 3f
line_paint.style = Paint.Style.STROKE
line_paint.color = Color.argb(255.212.100.77)
// Draw the outer ring red line
canvas.drawPath(caves_path, line_paint)
line_paint.style = Paint.Style.FILL
/ / circle.
for (index in 0 until dataList.size ) {
canvas.drawCircle(grid_width*index,danweiY*dataList[index],6f,line_paint)
}
Copy the code
Look at the results:Rendering text
// Draw the first and last 7 days text button
private fun drawTextButton(canvas: Canvas) {
val line_paint = Paint()
line_paint.strokeWidth = 2f
line_paint.style = Paint.Style.STROKE
line_paint.color = Color.argb(188.76.126.245)
line_paint.textSize=38f
val buttonPath = Path()
buttonPath.addRoundRect(100f, -120f.320f, -200f.80f.80f, Path.Direction.CCW)
canvas.drawPath(buttonPath,line_paint)
canvas.save()
canvas.scale(1f, -1f)
canvas.drawText("The first seven days".140f.175f,line_paint)
canvas.restore()
canvas.save()
canvas.translate(260f.0f)
canvas.drawPath(buttonPath,line_paint)
canvas.scale(1f, -1f)
canvas.drawText("The last seven days".140f.175f,line_paint)
canvas.restore()
}
Copy the code
The effect is as follows:
3. Embellish your skillsmap
Stickers have been used in blogs before, but I won’t go into them here. Add a clipping and you’re done.
// Draw the avatar at the top
private fun drawHeaderToCanvas(canvas: Canvas) {
val bitmap_paint = Paint()
bitmap_paint.strokeWidth = 2f
bitmap_paint.style = Paint.Style.STROKE
bitmap_paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
bitmap_paint.isAntiAlias =true
canvas.save()
val srcRect1=Rect(0.0.80.80)
val dstRect1=Rect(0.0.40.40)
val danweiY = (grid_width - 40) / 500
for (index in 0 until dataList.size) {
val mdrawable = ContextCompat.getDrawable(context, imgList[index])
val bitmap = getBitmap(bitmap_paint, mdrawable!!)
canvas.save()
canvas.translate(
grid_width * index - bitmap.width / 4,
danweiY * dataList[index] + 20
)
// Here draw the image onto the canvas
val circlePath = Path()
circlePath.addCircle(20f.20f.20f, Path.Direction.CCW)
canvas.clipPath(circlePath)
canvas.drawBitmap(bitmap, srcRect1, dstRect1, bitmap_paint)
canvas.restore()
}
canvas.restore()
}
Copy the code
I think I’ve reached the UI design at this point. We added an extra picture. I won’t bore you with something as simple as words.
4. Ocd
In order to achieve the same effect, their obsessive-compulsive disorder no way to draw the final effect. Those dead words and distances are not the quality of a professional developer. I hope you measure and calculate as you write. I’m writing dead data here for speed. Don’t imitate.
private fun drawTopTextToCanvas(canvas: Canvas) {
val text_paint = Paint()
text_paint.strokeWidth = 2f
text_paint.style = Paint.Style.FILL
text_paint.color = Color.argb(255.0.0.0)
text_paint.textSize =66f
val rectText = Rect()
val rectTextYuan = Rect()
canvas.save()
canvas.scale(1f, -1f)
canvas.translate((width/2).toFloat()-100, -500f)
val text="1347"
val textyu="Yuan"
text_paint.getTextBounds(text, 0,text.length, rectText)
canvas.drawText(
text,
-rectText.width().toFloat() - 42f,
rectText.height().toFloat() / 2,
text_paint
)
text_paint.color = Color.argb(111.111.111.111)
text_paint.getTextBounds(textyu, 0,textyu.length, rectTextYuan)
text_paint.textSize =33f
canvas.drawText(
textyu,
80+ -rectTextYuan.width().toFloat() - 42f,
rectTextYuan.height().toFloat() / 2,
text_paint
)
canvas.translate(0f.50f)
canvas.drawText(
"From the day before yesterday",
-rectTextYuan.width().toFloat() - 180f,
rectTextYuan.height().toFloat() / 2,
text_paint
)
canvas.translate(100f.0f)
text_paint.color = Color.argb(255.223.129.120)
canvas.drawText(
"+ 971.99 (251.19%)",
-rectTextYuan.width().toFloat() - 180f,
rectTextYuan.height().toFloat() / 2,
text_paint
)
canvas.translate(-100f.50f)
text_paint.color = Color.argb(111.111.111.111)
canvas.drawText(
"The highest prize is awarded for the part of the dotted line.",
-rectTextYuan.width().toFloat() - 180f,
rectTextYuan.height().toFloat() / 2,
text_paint
)
// I haven't found a way to draw rich text on canvas yet. We can only measure and draw the text one by one. Don't learn from me, good measurement measurement to improve their primary school calculations.
// val textSpanned1 = SpannableString("Hello World");
// textSpanned1.setSpan(ForegroundColorSpan(Color.RED), 0, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
// text_paint.reset()
// text_paint.textSize=44f
// canvas.drawText(textSpanned1,0,10,0f,0f,text_paint)
canvas.restore()
}
Copy the code
7. Embellish your skillsgestures
addanimation
If you don’t understand gestures, sliding, zooming, and animating, take a look at the previous articles.
Curve – the game
I’ll write it tomorrow sleep