- articleWe took advantage of that
Custom clipping
andThe custom
You 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
Compose
In theCanvas
As 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 in
import 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 head
Is 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.nativeCanvas
To 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 acceptable
canvas.nativeCanvas
willCanvas 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 custom
Not 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.