• articleWe took advantage of thatCustom clippingandThe customYou can play a simple example like this, and it works. In this lesson, we’re going to see if you can spice up Compose’s custom drawing.

Compose custom

  • Customization, the creativity of an application is often inseparable from people’s strange imagination and the ever-changing needs of users. Customization is what provides creativity on mobile. If you don’t have custom for Compose then you don’t have creativity. Custom unfamiliar to see my previous blog.

Android custom – Curve gradient fill 2.Android Custom – Gesture zoom line chart 3.Android Custom – gesture slide zoom gradient fill curve curve line chart 4. 5. Draw related API….

2. Start drawing

  • As I’ve written in the original, curves and gradient filling are actually very simple, simple mathematical addition, subtraction, multiplication and division plus a Seibel curve. Now let’s see how it works in Compose.

Draw components in 1.Compose

  • ComposeIn theCanvasAs a drawing component. The basic structure is as follows, as we saw last time:
Canvas(modifier = modifie.fillmaxWidth ().fillmaxHeight (),) {drawIntoCanvas {Canvas -> // internal provide size to obtain its own width and height}Copy the code

2. Align the coordinate system

  • The default coordinates Android,Flutter,SwiftUI are all in the top left corner, so let’s try the Compose coordinate. We draw a red circle at (100,100) with a radius of 100. If it appears on the top left side of the screen, it is consistent with the bottom.

 Canvas(
        modifier = Modifier
            .fillMaxWidth()
            .fillMaxHeight(),
    ) {

        drawIntoCanvas { canvas ->
          //
          val paint=Paint()
          paint.style=PaintingStyle.Fill
          paint.color=Color.Green
          canvas.drawCircle(Offset(100f.100f),100f,paint)

        }

    }
Copy the code

Ok coordinate system consistent, we start drawing.

  • The first step is to correct the coordinate system position from the front to the back.

Canvas.scale (1f,-1f)+ Canvas.translate (0f,-height) or Canvas.translate (0f,-height)+ Canvas.scale (1f,-1f) Done. Don’t understand to see my custom blog

If the following code is correct then the circle should be in the lower left corner

Canvas(
        modifier = Modifier
            .fillMaxWidth()
            .fillMaxHeight(),
    ) {

        drawIntoCanvas { canvas ->
            //
            val paint = Paint()
            paint.style = PaintingStyle.Fill
            paint.color = Color.Green
            // Change the axes
            canvas.translate(1f, -1f)
            canvas.drawCircle(Offset(100f.100f), 100f, paint)

        }

    }
Copy the code

After seeing the effect is not a little awkward, I actually suspect that there is something wrong with the simulator… Let’s look at the real thing, the huawei mate30 pro. Exactly. So pit a lot of, not careful on the doubt of life.

3. Draw a horizontal line parallel to the X-axis

  • First of all, the original image has Spaces on both the left and right sides, and the coordinate system can be left with room at the bottom and left for convenience.
@Preview(name = "canvas")
@Composable
fun MyCureView(mainActions: MainActions) {
    // Distance from the left screen
    val marginToLeft = 180f
    // Distance from bottom of the screen
    val marginToBootom = 240f
    

    Canvas(
        modifier = Modifier
            .fillMaxWidth()
            .fillMaxHeight(),
    ) {

        drawIntoCanvas { canvas ->
            val paint = Paint()
            paint.style = PaintingStyle.Fill
            paint.color = Color.Green
            canvas.translate(0f,size.height)
            canvas.scale(1f, -1f)

            canvas.translate(marginToLeft,marginToBootom)
            canvas.drawCircle(Offset(100f.100f), 100f, paint)

        }

    }

}

Copy the code

You can adjust later if you don’t have enough. Let’s draw a horizontal line.

 //2. Parallel the x axis
            val line_paint = Paint()
            line_paint.strokeWidth = 2f
            line_paint.style = PaintingStyle.Stroke
            line_paint.color = Color( 188.188.188.100)
            // The x-base is also left 80 to the right
            x_scaleWidth = (size.width - marginToLeft - 80f)
            val onePath=Path()
            onePath.lineTo(x_scaleWidth,0f)
            canvas.drawPath(onePath,line_paint)
Copy the code

We see a total of four lines going through.

private fun DrawScope.drawXLine(
    x_scaleWidth: Float,
    marginToLeft: Float,
    grid_width: Float,
    canvas: androidx.compose.ui.graphics.Canvas
) {
    var x_scaleWidth1 = x_scaleWidth
    var grid_width1 = grid_width
    val line_paint = Paint()
    line_paint.strokeWidth = 2f
    line_paint.style = PaintingStyle.Stroke
    line_paint.color = Color(188.188.188.100)
    // The x-base is also left 80 to the right
    x_scaleWidth1 = (size.width - marginToLeft - 80f)
    grid_width1 = x_scaleWidth1 / 6

    val onePath = Path()
    onePath.lineTo(x_scaleWidth1, 0f)
    canvas.drawPath(onePath, line_paint)

    canvas.save()
    // Draw the remaining parallel X axes by panning the canvas
    (0 until 3).forEach { index ->
        canvas.translate(0f, grid_width1 - 40f)
        canvas.drawPath(onePath, line_paint)
    }
    canvas.restore()
}
Copy the code

Of course, the phone looks very clear, but the picture is blurred. It’s too big to handle.

4. Draw text

  • I had a hard time drawing the text. The first is inimport androidx.compose.ui.graphics.*One kind of library for a long time. Because too naive to give up the search for a class. More than 40 categories let me look at it one by one, finally give up this method, and then a ton of operation in Google’s official website all kinds of gestures all speechless ah. In my last blog post, I had a friend reply to me in the middle of the night, everything is Google search engine, sent me screenshots and posted them in the comments section, boy, a Google search this morning was so good that only one Google expert wrote a case. Like no one has ever done before or since. On that one, I was happy like a child, picked up a meal CV, I am stupid, my compiler prompt can not parse tonativeCanvas“And then quieting down to analyze a wave of itimport androidx.compose.ui.graphics.*The location was found in the screenshot below. NativaCanvas is the conversion of graphics to native Canvas. But why can’t my project be referenced? And Google experts don’t offer a project. I ended up asking a couple of bigwigs,Finally I suspect the gradle version headIs it this thing that always goes wrong? Many of you who write Flutter and have not been exposed to Android have suffered a lot. To solve this problem, update Gradle to the latest version.

Gradle No problem version Gradle -7.0-milestone-2-bin.zip

⭐ ️ ⭐ ️ ⭐ ️ ⭐ ️ ⭐ ️ ⭐ ️ ⭐ ️ ⭐ ️ ⭐ ️ ⭐ ️ ⭐ ️ ⭐ ️ ⭐ ️ ⭐ ️

Before using the6.8The version is faulty. The latest distributionUrl = HTTPS \ :/ / services.gradle.org/distributions/gradle-7.0-milestone-2-bin.zip
Copy the code

⭐ ️ ⭐ ️ ⭐ ️ ⭐ ️ ⭐ ️ ⭐ ️ ⭐ ️ ⭐ ️ ⭐ ️ ⭐ ️ ⭐ ️ ⭐ ️ ⭐ ️ ⭐ ️

  • canvas.nativeCanvasTo get the native Canvas

Then draw the text

1. Text drawing via paint. GetTextBundl see my previous custom post without knowing it. X_scaleWidth / 6 3

fun DrawScope.drawTextDownX(
    x_scaleWidth: Float,
    marginToLeft: Float,
    grid_width: Float,
    canvas: androidx.compose.ui.graphics.Canvas,
    paint: Paint
) {
    var x_scaleWidth1 = x_scaleWidth
    var grid_width1 = grid_width
    x_scaleWidth1 = (size.width - marginToLeft - 80f)
    grid_width1 = x_scaleWidth1 / 6
    val text_paint = android.graphics.Paint()
    text_paint.strokeWidth = 2f
    text_paint.style = android.graphics.Paint.Style.STROKE
    text_paint.color = android.graphics.Color.argb(100.111.111.111)
    text_paint.textSize = 19f

    val rectText = Rect()
    canvas.save()
    // Rotate the text so that y downward is positive
    canvas.scale(1f, -1f)
    (0 until 7).forEach { index ->
        if (index > 0) {
            Log.e("weima?"."MyCureView: " + grid_width1)
            canvas.nativeCanvas.translate(grid_width1, 0f)}val strTx = "11.The ${11 + index}"
        text_paint.getTextBounds(strTx, 0, strTx.length, rectText)
        canvas.nativeCanvas.drawText(
            strTx,
            -rectText.width().toFloat() / 2,
            rectText.height().toFloat() * 2.5 f,
            text_paint
        )
    }
    canvas.restore()
}

Copy the code

Do the same for the text to the left of the Y axis.

private fun DrawScope.drawTextOfYLeft(
    x_scaleWidth: Float,
    marginToLeft: Float,
    grid_width: Float,
    canvas: androidx.compose.ui.graphics.Canvas
) {
    var x_scaleWidth1 = x_scaleWidth
    var grid_width1 = grid_width
    val text_paint = android.graphics.Paint()
    text_paint.strokeWidth = 2f
    text_paint.style = android.graphics.Paint.Style.STROKE
    text_paint.color = android.graphics.Color.argb(100.111.111.111)
    text_paint.textSize = 19f

    x_scaleWidth1 = (size.width - marginToLeft - 80f)
    grid_width1 = x_scaleWidth1 / 6

    val rectText = Rect()
    canvas.save()
    // Rotate the text so that y downward is positive
    (0 until 4).forEach { index ->
        if (index > 0) {
            canvas.translate(0f, grid_width1 - 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.nativeCanvas.drawText(
            strTx,
            -rectText.width().toFloat() - 42f,
            rectText.height().toFloat() / 2,
            text_paint
        )
        canvas.restore()
    }
    canvas.restore()
}
Copy the code

5. Draw the curve

When y1y_1y1< y2Y_2y2, as shown in figure 1. Figure out the control point x+40px in the lower part of the X-axis, and x-40px in the upper part of the X-axis. The Y-axis can also be adjusted to improve the smoothness of the lower control point Y-40x and the upper part y+40. =((x1x_1x1+ x2X_2x2)/2, (y1Y_1y1 + y2Y_2Y2)/2) 2. =((x1x_1x1+ x2Y_2Y2)/2 (y1y_1y1+ y2X_2x2)/2, (y1y_1y1+ y2X_2x2)/2, (y1y_1y1+ y2Y_2Y2)/2) Y2y_2y2 is shown in Figure 2. Figure out the midpoint coordinates of the upper X-axis +40px, and the lower x-40px. The Y-axis can also be adjusted, and the Y-axis can also be adjusted to improve the smoothness of the upper control point y+40x and the lower control point Y-40. =((x1x_1x1+ x2X_2x2)/2, (y1Y_1y1 + y2Y_2Y2)/2) 2. =((x1x_1x1+ x2Y_2Y2)/2 (y1y_1y1+ y2X_2x2)/2, (y1y_1y1+ y2X_2x2)/2, (y1y_1y2 + y2Y_2Y2)/2)

private fun DrawScope.drawCubtoCircle(
    x_scaleWidth: Float,
    marginToLeft: Float,
    grid_width: Float,
    dataList: ArrayList<Int>,
    canvas: androidx.compose.ui.graphics.Canvas
) {
    var x_scaleWidth1 = x_scaleWidth
    var grid_width1 = grid_width
    x_scaleWidth1 = (size.width - marginToLeft - 80f)
    grid_width1 = x_scaleWidth1 / 6
    val text_paint = android.graphics.Paint()
    text_paint.strokeWidth = 2f
    text_paint.style = android.graphics.Paint.Style.FILL
    text_paint.color = android.graphics.Color.argb(100.111.111.111)

    val caves_path = android.graphics.Path()
    //500= grid_widd-40 each unit length = pixel length
    val danweiY = (grid_width1 - 40) / 500
    val danweiX = (grid_width1)
    val linearGradient = LinearGradient(
        0f.1500 * danweiY,
        0f.0f,
        android.graphics.Color.argb(255.229.160.144),
        android.graphics.Color.argb(255.251.244.240),
        Shader.TileMode.CLAMP
    )
    text_paint.shader = linearGradient
    for (index in 0 until dataList.size - 1) {
        val xMoveDistance = 20
        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_width1 * index + grid_width1 * (1 + index)) / 2
            val centerY =
                (dataList[index].toFloat() * danweiY + dataList[index + 1].toFloat() * danweiY) / 2
            val controX0 = (grid_width1 * index + centerX) / 2
            val controY0 = (dataList[index].toFloat() * danweiY + centerY) / 2
            val controX1 = (centerX + grid_width1 * (1 + index)) / 2
            val controY1 = (centerY + dataList[index + 1].toFloat() * danweiY) / 2
            caves_path.cubicTo(
                controX0 + xMoveDistance,
                controY0 - yMoveDistance,
                controX1 - xMoveDistance,
                controY1 + yMoveDistance,
                grid_width1 * (1 + index),
                dataList[index + 1].toFloat() * danweiY
            )
        } else {
            val centerX = (grid_width1 * index + grid_width1 * (1 + index)) / 2
            val centerY =
                (dataList[index].toFloat() * danweiY + dataList[index + 1].toFloat() * danweiY) / 2
            val controX0 = (grid_width1 * index + centerX) / 2
            val controY0 = (dataList[index].toFloat() * danweiY + centerY) / 2
            val controX1 = (centerX + grid_width1 * (1 + index)) / 2
            val controY1 = (centerY + dataList[index + 1].toFloat() * danweiY) / 2
            caves_path.cubicTo(
                controX0 + xMoveDistance,
                controY0 + yMoveDistance,
                controX1 - xMoveDistance,
                controY1 - yMoveDistance,
                grid_width1 * (1 + index),
                dataList[index + 1].toFloat() * danweiY
            )

        }
    }
    canvas.nativeCanvas.drawCircle(0f.0f.10f, text_paint)
    // Draw a closed gradient curve
    canvas.nativeCanvas.drawPath(caves_path, text_paint)
    val line_paint = android.graphics.Paint()
    line_paint.strokeWidth = 3f
    line_paint.style = android.graphics.Paint.Style.STROKE
    line_paint.color = android.graphics.Color.argb(255.212.100.77)
    // Draw the outer ring red line
    canvas.nativeCanvas.drawPath(caves_path, line_paint)
    line_paint.style = android.graphics.Paint.Style.FILL
    / / circle.
    for (index in 0 until dataList.size) {
        canvas.nativeCanvas.drawCircle(
            grid_width1 * index,
            danweiY * dataList[index],
            8f,
            line_paint
        )
    }
}

Copy the code

So our biggest problem has been solved easily, right? Nothing more than simple addition, subtraction, multiplication and division, right? Next, we will decorate the beautiful curve. After learning the previous article, I think we can practice these skills and make perfect creation, right? Gradient fill, animation, click, ‘

6. Beautify curves

  • ⭐ ⭐ ⭐ ⭐ ⭐ ⭐ for custom drawing if you use the Compose Api poorly, it is perfectly acceptablecanvas.nativeCanvaswillCanvas native so you can switch between Canvas and Canvas⭐ ️ ⭐ ️ ⭐ ️ ⭐ ️ ⭐ ️ ⭐ ️ ⭐ ️ ⭐ ️
private fun DrawScope.drawResultBitifull(canvas: androidx.compose.ui.graphics.Canvas) {
    val text_paint = android.graphics.Paint()
    text_paint.strokeWidth = 2f
    text_paint.style = android.graphics.Paint.Style.FILL
    text_paint.color = android.graphics.Color.argb(255.0.0.0)
    text_paint.textSize = 66f
    val rectText = Rect()
    val rectTextYuan = Rect()

    canvas.save()
    canvas.scale(1f, -1f)
    canvas.translate((size.width / 2).toFloat() - 100, -500f)
    val text = "1347"
    val textyu = "Yuan"

    text_paint.getTextBounds(text, 0, text.length, rectText)

    canvas.nativeCanvas.drawText(
        text,
        -rectText.width().toFloat() - 42f,
        rectText.height().toFloat() / 2,
        text_paint
    )
    text_paint.color = android.graphics.Color.argb(111.111.111.111)
    text_paint.getTextBounds(textyu, 0, textyu.length, rectTextYuan)
    text_paint.textSize = 33f
    canvas.nativeCanvas.drawText(
        textyu,
        80 + -rectTextYuan.width().toFloat() - 42f,
        rectTextYuan.height().toFloat() / 2,
        text_paint
    )

    canvas.translate(0f.50f)
    canvas.nativeCanvas.drawText(
        "Compared to the day before yesterday",
        -rectTextYuan.width().toFloat() - 180f,
        rectTextYuan.height().toFloat() / 2,
        text_paint
    )
    canvas.translate(100f.0f)
    text_paint.color = android.graphics.Color.argb(255.223.129.120)
    canvas.nativeCanvas.drawText(
        "+ 971.99 (251.19%)",
        -rectTextYuan.width().toFloat() - 180f,
        rectTextYuan.height().toFloat() / 2,
        text_paint
    )
    canvas.translate(-100f.50f)
    text_paint.color = android.graphics.Color.argb(111.111.111.111)
    canvas.nativeCanvas.drawText(
        "Highest award for the dotted line.",
        -rectTextYuan.width().toFloat() - 180f,
        rectTextYuan.height().toFloat() / 2,
        text_paint
    )
    // There is no way to draw rich text on canvas. We can only measure and draw the text one by one. Don't be like me, take a good measure measure to improve their primary school calculations.

    canvas.restore()
}

//8. Draw a picture of the top winner every day... Pure fiction, right...
private fun drawHeaderToCanvas(canvas: Canvas,width:Float,marginToLeft:Float,dataList:List<Int>,imgList:ArrayList<ImageBitmap>) {
    val bitmap_paint = android.graphics.Paint()
    bitmap_paint.strokeWidth = 2f
    bitmap_paint.style = android.graphics.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 x_scaleWidth = (width - marginToLeft - 80f)
    val grid_width = x_scaleWidth / 6
    val danweiY = (grid_width - 40) / 500
    for (index in 0 until dataList.size) {
        val bitmap = imgList[index].asAndroidBitmap()
        canvas.save()
        canvas.translate(
            grid_width * index - bitmap.width /20,
            danweiY * dataList[index] + 20
        )
        // Here draw the picture to 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()


}

@SuppressLint("ObsoleteSdkInt")
fun drawTextButton(canvas: Canvas) {
    val line_paint = android.graphics.Paint()
    line_paint.strokeWidth = 2f
    line_paint.style = android.graphics.Paint.Style.STROKE
    line_paint.color = android.graphics.Color.argb(188.76.126.245)
    line_paint.textSize=32f
    val buttonPath = android.graphics.Path()
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
        buttonPath.addRoundRect(110f, -120f.270f, -180f.80f.80f, android.graphics.Path.Direction.CCW)
    }
    canvas.drawPath(buttonPath, line_paint)
    canvas.save()
    canvas.scale(1f, -1f)
    line_paint.style = android.graphics.Paint.Style.FILL
    canvas.drawText("First seven days.".140f.165f, line_paint)
    canvas.restore()

    canvas.save()
    canvas.translate(260f.0f)
    line_paint.style = android.graphics.Paint.Style.STROKE
    canvas.drawPath(buttonPath, line_paint)
    canvas.scale(1f, -1f)
    line_paint.style = android.graphics.Paint.Style.FILL
    canvas.drawText("The last seven days".140f.165f, line_paint)
    canvas.restore()

}

Copy the code

Third, summary

  • The customNot only does it provide a more convenient API, but also it can completely use the original API, so the native Android development is still very pleasant. In Compose, the customization point is totally unexpected, but for us, there are more choices. So customization is fine. And it’s absolutely as creative as anything, except that the API definition at the top level is different.