Flutter- first encounter

  • From June 2018 to June 2018, I entered into Flutter, so I took out beautiful interfaces such as Meituan and Zain, felt a wave of Flutter UI and drawing, and felt the strength of Flutter when I wrote a Demo for three days. At that time, I wrote relevant Demo and uploaded it to Github hastily. Unconsciously, many stars on Github were very happy and decided to record basic teaching videos. I also received many thank-you messages and technical exchanges on site B. I still remember that my brother, whom I had never met in 2018, gave me 2018 MacBook Pro for no reason because of my enthusiasm. With this feeling and gratitude, I also began to write a lot of articles and dependency libraries for beginners to learn together.

Flutter open source libraries and articles

Flutter_excle — GitHub — flutter_time_axis — GitHub — flutter_excle — CSDN Flutter3D Flutter_pickter_plugin supports the selection of images, files and videos, etc. – library – GitHub Flutter time and date formatting, etc. Plugin – library WGS84 with geodey 2000 coordinate conversion (Java,C#,Dart) the support libraries for Flutter have been released and of course there are many more in CSDN

B station teaching link

               

Android custom drawing related articles

Android custom – art is to draw Android- custom – curve line chart fill gradient, gesture sliding, gesture zooming and so on Android custom – any region clickable line chart Android custom – gesture zooming line chart Android Custom – Gesture Slide Zoom Gradient fill curve Broken line chart Android- Custom expandable ViewGroup Android Custom – Curve gradient fill Jetpack-compose Jetpack-compose – Custom draw And of course there’s a lot more at CSDN

IOS related Articles

SwiftUI learning Notes basic UI SwiftUI learning notes – [list] SwiftUI learning notes [PATH drawing] SwiftUI learning notes [Sqlite] SwiftUI learning notes [XML parsing] of course there are many in CSDN

As long as the article doesn't stop on the way to programming... I hope to bring you convenience, your praise is my greatest happiness

2. Surprises brought by Flutter UI

  • The surprises that Flutter brings are more than a few: stateful thermal reloading enables quick rerendering of the interface, an expressive and extremely flexible UI. Its own rendering engine, stripped of its native base components as a mapping, has an absolute advantage over frameworks like Uni and RN in terms of performance. Scroll, navigation, ICONS and fonts for a full native performance experience on iOS and Android with support for Web,Desktop,Embedded.. , etc. In 18 years, I learned about Flutter and felt the plasticity and creativity of THE UI. It was completely original and more flexible. Of course, there were many pages written at that time.Custom clippinganddrawLet the UI diagram of the application break the ceiling, andgesturesandanimationThe addition of more let your application, pleasing to the eye, transcendent. So what about Compose? Today’s focus is on exploring the capabilities of Compose on the UI.

               

If you’ve ever had a problem with Flutter drawing and clipping or anything else, I’m sure this man can tell you all about it.

Compose UI- Powerful and simple

  • We learned the first two chaptersBasic UI writingandCustom drawing clippingThe application of. In this lesson we will combine basic UI with custom plus simplegestures,animationIn order to learn about Compose’s creativity, this section will be interspersed with a discussion of custom curves, which seem to be almost inseparable from curves. Let’s take a look at the following, okay?

               

UI is not just a simple layout of official components. Good UI requires careful customization and dynamic interaction. The diagram above does not represent beauty, but it contains many interactive and dynamic effects. We start from the interactive dynamic possibilities of components. Of course, you think what kind of UI is the most difficult to express your opinion, I will return you a lang Lang. Technical exchange group QQ skirt 730772561.

1, rotate, zoom, background blur

  • How about rotation scaling of components with Flutter?TransformorRotationTransitionSuch container parts are wrapped and need to set up animation control AnimationController and so on. For Compose, how do we rotate and scale the component?ModifierIt not only solves the problem that too many parameters can be unified configuration, but also provides extremely powerfultailoring,transform,Pointer to the gestures,decorationAnd other methods, greatly improve the convenience and creativity.

1, Modifier. The rotate (degrees: Float)

  • Any component can be used through the ModifierRotating [Modifier. The rotate (degrees: Float)],[Modifier. Offset (x: Dp = 0.dp, y: Dp = 0.dp)],Zoom [Modifier. Scale (scale: Float)]And so on.

               

Observe this part of the dynamic effects 1. The rotation of the middle picture. 2, circular picture arc zooming. 3. Background blur and zoom. 4. Curve of motion.

2. Animation creation and use

  • When we mouse click on the screen, the image in the middle rotates at any Angle. We already know that rotation goes throughModifier.rotateTo set it, just by clicking on the screenPerform the animationContinue to update the Angle value. So simple animation creation needs to be mastered.

The animation store can be viewed by clicking on the source code. The values during the animation execution are stored in the mutableStateOf, so it has a state and changes when the state goes down. You can refresh the UI by looking at previous chapters or the official website

// Animate the process of numerical memory
val animatedDegree = remember { Animatable(0f) }
Copy the code

AnimateTo (targetValue: T,animationSpec: animationSpec = defaultSpringSpec…) Start animation, you can set animation target value and animation size speed etc…

animatedOffset.animateTo(targetValue,animationSpec = spring(stiffness = StiffnessLow))
Copy the code

fun Modifier.pointerInput(key1: Any? , block: suspend PointerInputScope.() -> Unit): To process pointer input in the modified element area. PointerInput can call PointerInputScope -awaitpointereventscope to install a pointerInput handler, This handler causes WaitPOInterEventScope-AWaitPoInterEvent to receive and use pointer input events. By PointerInputScope. The extension function defined as performing at a higher level on AwaitPointerEventScope gesture detection. We get the screen click coordinates as our Angle via awaitPointerEventScope.

 @Compose
 fun LoginPage(a){
 //1. Animate the process of numerical memory
 val animatedDegree = remember { Animatable(0f) }
 Box() {
        // Blur the background at the bottom
        Image(
            // Get the fuzzy bitmap
            bitmap = BitmapBlur.doBlur(getBitmap(resource =R.drawable.head_god).asAndroidBitmap(),
            animatedRound.value.toInt()+20.false).asImageBitmap(),
            contentDescription = "",
            contentScale = ContentScale.FillHeight,
            modifier = Modifier
                .fillMaxWidth()
                .fillMaxHeight()
                .scale(animatedScale.value, animatedScale.value),
        )
        // Circle image and circle circle
        Column(verticalArrangement = Arrangement.Center, modifier = Modifier.pointerInput(Unit) {
            coroutineScope {
                while (true) {
                    //2. Handle pointer events by awaitPointerEventScope
                    val offset = awaitPointerEventScope {
                        // Get the position coordinates of the first press
                        awaitFirstDown().position

                    }
                    / / ctrip
                    launch {
                            //3. Set the target value of animated to the x value in the pressed screen coordinate system, and set the animation format to be gentle and not stilted. Start performing the animation.
                            animatedDegree.animateTo(
                            offset.x,
                            animationSpec = spring(stiffness = StiffnessLow)
                        )

                    }

                }
            }
        }) {
          Image( bitmap = getBitmap(R.drawable.head_god),
                        contentDescription = "w",
                        contentScale = ContentScale.FillBounds,
                        modifier = Modifier
                            .height(80.dp)
                            .width(80.dp)
                            .background(color = Color(0XFF0DBEBF), shape = CircleShape)
                            .padding(3.dp)
                            .clip(
                                CircleShape
                            )
                            .shadow(elevation = 150.dp, clip = true)
                            .rotate(//4. Set the Angle to initialize the animation value to target X and the new UI
                                animatedDegreen.value
                            )
                    )
        }
   }     
Copy the code

3, translucent ringThe zoomandcoloranimation

  • By the same tokenval animatedScale = remember { Animatable(1f) }To create a shrink-size memory, animate it to match the new image size and color by clicking on itval animatedColor = remember { Animatable(Color(206, 199, 250, 121)) }Click to set the target zoom value and color execution. AnimalTo can perform continuous animation and send up to the new store state to the new UI. Circular cutting related to lookPrevious articles
  Box(contentAlignment = Alignment.Center,
      modifier = Modifier.padding(0.dp).clip(CicleImageShape())
                        .background(animatedColor.value)
                        .width((130 * animatedScale.value).dp)
                        .height((130 * animatedScale.value).dp)
                ) {
                    Image(
                        bitmap = getBitmap(R.drawable.head_god),
                        contentDescription = "",
                        contentScale = ContentScale.FillBounds,
                        modifier = Modifier
                            .height(80.dp)
                            .width(80.dp)
                            .background(color = Color(0XFF0DBEBF), shape = CircleShape)
                            .padding(3.dp)
                            .clip(
                                CircleShape
                            )
                            .shadow(elevation = 150.dp, clip = true)
                            .rotate(
                                animatedOffset.value
                            )
                    )
                }
Copy the code

4. Image Gaussian blur acquisition

Bitmap and ImageBitmap can convert each other: Bitmap asImageBitmap () : ImageBitmap and fun ImageBitmap. AsAndroidBitmap () : Bitmap

object BitmapBlur {
    fun doBlur(sentBitmap: Bitmap, radiu: Int = 1.canReuseInBitmap: Boolean): Bitmap {
        var radius: Int = radiu
        val bitmap: Bitmap = if (canReuseInBitmap) {
            sentBitmap
        } else {
            sentBitmap.copy(sentBitmap.config, true)}if (radius < 1) {
            radius = 0
        }
        val w = bitmap.width
        val h = bitmap.height
        val pix = IntArray(w * h)
        bitmap.getPixels(pix, 0, w, 0.0, w, h)
        val wm = w - 1
        val hm = h - 1
        val wh = w * h
        val div = radius + radius + 1
        val r = IntArray(wh)
        val g = IntArray(wh)
        val b = IntArray(wh)
        var rsum: Int
        var gsum: Int
        var bsum: Int
        var x: Int
        var y: Int
        var i: Int
        var p: Int
        var yp: Int
        var yi: Int
        var yw: Int
        val vmin = IntArray(Math.max(w, h))
        var divsum = div + 1 shr 1
        divsum *= divsum
        val dv = IntArray(256 * divsum)
        i = 0
        while (i < 256 * divsum) {
            dv[i] = i / divsum
            i++
        }
        yi = 0
        yw = yi
        val stack = Array(div) {
            IntArray(
                3)}var stackpointer: Int
        var stackstart: Int
        var sir: IntArray
        var rbs: Int
        val r1 = radius + 1
        var routsum: Int
        var goutsum: Int
        var boutsum: Int
        var rinsum: Int
        var ginsum: Int
        var binsum: Int
        y = 0
        while (y < h) {
            bsum = 0
            gsum = bsum
            rsum = gsum
            boutsum = rsum
            goutsum = boutsum
            routsum = goutsum
            binsum = routsum
            ginsum = binsum
            rinsum = ginsum
            i = -radius
            while (i <= radius) {
                p = pix[yi + Math.min(wm, Math.max(i, 0))]
                sir = stack[i + radius]
                sir[0] = p and 0xff0000 shr 16
                sir[1] = p and 0x00ff00 shr 8
                sir[2] = p and 0x0000ff
                rbs = r1 - Math.abs(i)
                rsum += sir[0] * rbs
                gsum += sir[1] * rbs
                bsum += sir[2] * rbs
                if (i > 0) {
                    rinsum += sir[0]
                    ginsum += sir[1]
                    binsum += sir[2]}else {
                    routsum += sir[0]
                    goutsum += sir[1]
                    boutsum += sir[2]
                }
                i++
            }
            stackpointer = radius
            x = 0
            while (x < w) {
                r[yi] = dv[rsum]
                g[yi] = dv[gsum]
                b[yi] = dv[bsum]
                rsum -= routsum
                gsum -= goutsum
                bsum -= boutsum
                stackstart = stackpointer - radius + div
                sir = stack[stackstart % div]
                routsum -= sir[0]
                goutsum -= sir[1]
                boutsum -= sir[2]
                if (y == 0) {
                    vmin[x] = Math.min(x + radius + 1, wm)
                }
                p = pix[yw + vmin[x]]
                sir[0] = p and 0xff0000 shr 16
                sir[1] = p and 0x00ff00 shr 8
                sir[2] = p and 0x0000ff
                rinsum += sir[0]
                ginsum += sir[1]
                binsum += sir[2]
                rsum += rinsum
                gsum += ginsum
                bsum += binsum
                stackpointer = (stackpointer + 1) % div
                sir = stack[stackpointer % div]
                routsum += sir[0]
                goutsum += sir[1]
                boutsum += sir[2]
                rinsum -= sir[0]
                ginsum -= sir[1]
                binsum -= sir[2]
                yi++
                x++
            }
            yw += w
            y++
        }
        x = 0
        while (x < w) {
            bsum = 0
            gsum = bsum
            rsum = gsum
            boutsum = rsum
            goutsum = boutsum
            routsum = goutsum
            binsum = routsum
            ginsum = binsum
            rinsum = ginsum
            yp = -radius * w
            i = -radius
            while (i <= radius) {
                yi = Math.max(0, yp) + x
                sir = stack[i + radius]
                sir[0] = r[yi]
                sir[1] = g[yi]
                sir[2] = b[yi]
                rbs = r1 - Math.abs(i)
                rsum += r[yi] * rbs
                gsum += g[yi] * rbs
                bsum += b[yi] * rbs
                if (i > 0) {
                    rinsum += sir[0]
                    ginsum += sir[1]
                    binsum += sir[2]}else {
                    routsum += sir[0]
                    goutsum += sir[1]
                    boutsum += sir[2]}if (i < hm) {
                    yp += w
                }
                i++
            }
            yi = x
            stackpointer = radius
            y = 0
            while (y < h) {

                // Preserve alpha channel: ( 0xff000000 & pix[yi] )
                pix[yi] =
                    -0x1000000 and pix[yi] or (dv[rsum] shl 16) or (dv[gsum] shl 8) or dv[bsum]
                rsum -= routsum
                gsum -= goutsum
                bsum -= boutsum
                stackstart = stackpointer - radius + div
                sir = stack[stackstart % div]
                routsum -= sir[0]
                goutsum -= sir[1]
                boutsum -= sir[2]
                if (x == 0) {
                    vmin[y] = Math.min(y + r1, hm) * w
                }
                p = x + vmin[y]
                sir[0] = r[p]
                sir[1] = g[p]
                sir[2] = b[p]
                rinsum += sir[0]
                ginsum += sir[1]
                binsum += sir[2]
                rsum += rinsum
                gsum += ginsum
                bsum += binsum
                stackpointer = (stackpointer + 1) % div
                sir = stack[stackpointer]
                routsum += sir[0]
                goutsum += sir[1]
                boutsum += sir[2]
                rinsum -= sir[0]
                ginsum -= sir[1]
                binsum -= sir[2]
                yi += w
                y++
            }
            x++
        }
        bitmap.setPixels(pix, 0, w, 0.0, w, h)
        return bitmap
    }
}
Copy the code

5. Curve of motion

  • The radian motion of the curve can be changed according to the position change of the control point. I talked about this in the custom drawing section.
1. Bezier curve

Any function can be mapped to a coordinate system drawing, 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. Next we begin the formal entry curve animation.

               

  • Cut radians as in the picture above can be passedThe second order curveDraw the path for clipping.The intermediate point is the control pointWhen clicking on the screen, the value of the x axis of the screen obtained by clicking on the event can be used as the target value of the control point to perform the animation.

 Image(bitmap = BitmapBlur.doBlur(getBitmap(resource =R.drawable.head_god).asAndroidBitmap(),
                        animatedBitmap.value.toInt(),false).asImageBitmap(),
                    contentDescription = "",
                    contentScale = ContentScale.FillWidth,
                    modifier = Modifier
                        .fillMaxWidth()
                        .height(230.dp)
                        .clip(// The second order curve is clipped.
                            QureytoImageShapes(160f, animatedOffsetX.value)
                        )
                        .scale(animatedScale.value, animatedScale.value)// Zoom the header background image
                )
                
@Stable
class QureytoImageShapes(var hudu: Float = 100f.var controller:Float=0f) : Shape {

    override fun createOutline( size: Size, layoutDirection: LayoutDirection, density: Density ): Outline {
        val path = Path()
        path.moveTo(0f.0f)
        path.lineTo(0f, size.height - hudu)
        // Default initialization selects the middle value as the control point coordinate x
        if(controller==0f){
            controller =size.width / 2f
        }
        path.quadraticBezierTo(controller, size.height, size.width, size.height - hudu)
        path.lineTo(size.width, 0f)
        path.close()
        return Outline.Generic(path)
    }
}                
Copy the code

2, text color and dynamic effect

  • In the image above, we can see that the text has color motion, and the text below has a certain bending effect when clicked.





Text color dynamic effect or

We move the gradient and color the text as we move it to create this effect

Sections before we already know that path, the canvas can undertake translation transform, actually our that LinearGradient also can set the transformations such as moving through that LinearGradient. SetLocalMatrix (transMatrix)

The text works with the path

  • Custom text can be drawn along the Path. To make the text move we can make the Path move to make the text move.
    val animatedOffset = remember { Animatable(0f)}... Click to trigger animalTo and set the target value to screen coordinate X. Of course the target value itself can define other... Box(contentAlignment = align.center, modifier = modifier.fillMaxWidth().padding(top =20.dp)
            ) {
                Column(horizontalAlignment = Alignment.CenterHorizontally) {
                    androidx.compose.foundation.Canvas(
                        modifier = Modifier
                            .fillMaxWidth()
                            .draggable(state = DraggableState {

                            }, orientation = Orientation.Horizontal, onDragStarted = {
                            }, onDragStopped = {
                             
                            }),
                    ) {
                        drawIntoCanvas { canvas ->
                            val paint = Paint()
                            paint.style = PaintingStyle.Fill
                            paint.color = Color.Green
                            val text_paint = android.graphics.Paint()
                            text_paint.strokeWidth = 2f
                            text_paint.style = android.graphics.Paint.Style.FILL
                            text_paint.color = android.graphics.Color.BLACK
                            text_paint.textSize = 52f


                            // Measure text width
                            val rect = android.graphics.Rect()
                            text_paint.getTextBounds("ComposeUnit landing".0.6, rect)
                            val colors = intArrayOf(
                                android.graphics.Color.BLACK,
                                android.graphics.Color.argb(
                                    250.121,
                                    animatedOffset.value.toInt(),
                                    206
                                ),
                                android.graphics.Color.argb(250.121.206, animatedOffset.value.toInt())
                            )
                            val positions = floatArrayOf(0.2 f.11f.0.2 f)


                            // Let the gradient change to feel the text flash
                            val transMatrix = android.graphics.Matrix()
                            transMatrix.postTranslate(
                                -rect.width() + rect.width() * 2 * (animatedScale.value * 1.5 f),
                                0f
                            )
                            // Set the gradient
                            val linearGradient = android.graphics.LinearGradient(
                                0f.0f,
                                rect.width().toFloat(),
                                0f,
                                colors,
                                positions,
                                android.graphics.Shader.TileMode.CLAMP
                            )
                            // Set the matrix transformation
                            linearGradient.setLocalMatrix(transMatrix)

                            text_paint.shader = linearGradient
                            //1
                            canvas.nativeCanvas.drawText(
                                "ComposeUnit landing",
                                size.width / 3.5 f,
                                size.height / 2.5 f,
                                text_paint
                            )
                            val secontextPath = android.graphics.Path()


                            val rect1 = android.graphics.Rect()
                            text_paint.getTextBounds("More exciting, more experience ~".0.6, rect1)
                            secontextPath.moveTo(340f.100f)
                            / / 0-110
                            if (animatedOffset.value == 0f) {
                                secontextPath.quadTo(350f.10f.710f.100f)}// Set the control point of the curve path to control the dynamic effect of the text following the path by clicking the X-axis coordinate
                            secontextPath.quadTo(animatedOffset.value, 10f.710f.100f)

                            text_paint.textSize = 32f
                            text_paint.letterSpacing = 0.3 f
                            //canvas.nativeCanvas.drawPath(secontextPath,text_paint)
                            canvas.nativeCanvas.drawTextOnPath(
                                "More exciting, more experience ~",
                                secontextPath,
                                0f.0f,
                                text_paint
                            )
                        }

                    }
                }
            }
Copy the code

3, haven’t seen the input box?

  • The picture above should be our common input field, right? How to make the input box different fancy, it is absolutely inseparable from the drawing. As shown below, when we click on the screen, the boundary of the input box changes significantly in radians.Modifier.borderaddCustom drawingCan be completed.

  Box(
                contentAlignment = Alignment.Center, modifier = Modifier
                    .fillMaxWidth()
                    .padding(top = 60.dp)
            ) {
                TextField(
                    value = "",
                    onValueChange = { },
                    // shape = AnimalRoundedCornerShape(animatedRound.value),
                    colors = TextFieldDefaults.textFieldColors(
                        unfocusedIndicatorColor = Color.Transparent,
                        focusedIndicatorColor = Color.Transparent,
                        backgroundColor = Color.Transparent),
                    modifier = Modifier.height(48.dp).border(
                        1.2.dp,
                        //animatedColor.value.copy(alpha = 1f)
                     Color(animatedColor.value.red,animatedColor.value.green,animatedColor.value.blue,1f),
                        AnimalRoundedCornerShape(animatedRound.value)
                    ),
                    leadingIcon = {
                        Icon(
                            bitmap = getBitmap(R.mipmap.yinzhang),
                            contentDescription = "")})}// Input box border animation
@Stable
class AnimalRoundedCornerShape(val value:Float=30f):Shape{
    override fun createOutline(
        size: Size,
        layoutDirection: LayoutDirection,
        density: Density
    ): Outline {
        val path = Path()
        path.lineTo(value,0f)
        path.cubicTo(value,0f,0f,0f,0f,value)
        path.lineTo(0f,size.height-value)
        path.cubicTo(0f,size.height-value,0f,size.height,value,size.height)
        path.quadraticBezierTo(size.width/2,size.height-value,size.width-value,size.height)
        path.quadraticBezierTo(size.width,size.height,size.width,size.height-value)
        path.lineTo(size.width,value)
        path.quadraticBezierTo(size.width,0f,size.width-value,0f)
        path.quadraticBezierTo(size.width/2,value,value,0f)
        path.lineTo(value,0f)
        return Outline.Generic(path)
    }

}          
            
Copy the code

4, dynamic effect CheckBox

  • CheckBox’s animation is the sameModifier.clip(a). Clipping using the API to provide CircleShaper radius Settings does not work.

Checkbox(checked = true,
          onCheckedChange = { },
          colors = CheckboxDefaults.colors(checkedColor = Color(0XFF0DBEBF)),
           modifier = Modifier.clip(CicleImageShape(animatedCheckBox.value))
/ / cut round
@Stable
class CicleImageShape(val circle: Float = 0f) : Shape {
    override fun createOutline(
        size: Size,
        layoutDirection: LayoutDirection,
        density: Density
    ): Outline {
        val minWidth = Math.min(size.width-circle, size.width-circle)
        val rect = Rect(circle, circle, minWidth, minWidth)
        val path = Path()
        path.addOval(rect)
        return Outline.Generic(path)
    }
}
Copy the code

4, Flutter and Compose –bottomBar

  • BottomBarThe bottom navigation bar is a must on mobile, and history is not full of bold designs. I remember that Flutter wrote the bottom navigation bar, when the Flutter group said that it was hard to write by myself, I secretly tried it for about ten minutes. So the topic for today is Compose and can Compose handle that? Of course, all the UI is curves and animations. So let’s analyze it.

1. Basic curve drawing

  • Town map. Let’s do it next, okay?

Compose’s bottomBar source code provides a @compose custom play at will.

We see three toggle buttons in the finished drawing, so how do we draw buttons and curves?

Width /3/2+size. Width /3*2; width/3/2+size. Simple elementary school math.

In order to smooth the curve, we use the third-order curve, and control the intersection of the second dotted line and the third dotted line with the curve. For those who do not understand the third-order curve, see the above.

   Box(
        modifier = Modifier
            .fillMaxWidth(),
        contentAlignment = Alignment.BottomEnd,
    ) {
        Canvas(modifier = Modifier
            .fillMaxWidth()
            .height(70.dp), onDraw = {
            drawIntoCanvas { canvas ->
                val paint = Paint()
                paint.color = Color(0XFF0DBEBF)
                paint.style = PaintingStyle.Fill

                val path = Path()
                // Divide into three equal parts
                val widthOfOne = size.width / 3
                // The central control point for each radian
                val centerWidthOfOneX = widthOfOne / 2
                // Radian port to ONewidth twice
                val marginLeftAndRigth = centerWidthOfOneX / 1.6 f

                val controllerX = centerWidthOfOneX / 6f
                // This is the movement process from the animation section by default the first selection has a radian.
                val keyAnimal = widthOfOne * 0
                canvas.save()
                // Draw a circle background
                //canvas.drawCircle(Offset(centerWidthOfOneX + keyAnimal, 0f), 60f, paint)
                // This is a simple adjustment, just a few minutes
                path.moveTo(0f.0f)
                path.lineTo(marginLeftAndRigth / 2 + keyAnimal, 0f)
                path.cubicTo(
                    marginLeftAndRigth + keyAnimal,
                    0f,
                    centerWidthOfOneX - (centerWidthOfOneX - controllerX) / 2f + keyAnimal,
                    size.height / 3f,
                    centerWidthOfOneX + keyAnimal,
                    size.height / 2.6 f
                )
                path.cubicTo(
                    centerWidthOfOneX + (centerWidthOfOneX - controllerX) / 2f + keyAnimal,
                    size.height / 2.6 f,
                    widthOfOne - (marginLeftAndRigth) + keyAnimal,
                    0f,
                    widthOfOne - marginLeftAndRigth / 2 + keyAnimal,
                    0f
                )
                path.lineTo(size.width, 0f)
                path.lineTo(size.width, size.height)
                path.lineTo(0f, size.height)
                path.close()
                canvas.clipPath(path)
                canvas.nativeCanvas.drawColor(Color(0XFF0DBEBF).toArgb())
            }
        })
        Row(
            modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceAround
        ) {
            Image(
                bitmap = getBitmap(resource = R.drawable.home),
                contentDescription = "1",
                modifier = Modifier
                    .modifier(animalCenterIndex, 0, animalBooleanState) .clickable { animalBoolean.value = ! animalBoolean.value animalCenterIndex.value =0
                    }
            )

            Image(
                bitmap = getBitmap(resource = R.drawable.center),
                contentDescription = "1",
                modifier = Modifier
                    .modifier(animalCenterIndex, 1, animalBooleanState) .clickable { animalBoolean.value = ! animalBoolean.value animalCenterIndex.value =1
                    }
            )
            Image(
                bitmap = getBitmap(resource = R.drawable.min),
                contentDescription = "1",
                modifier = Modifier
                    .modifier(animalCenterIndex, 2, animalBooleanState) .clickable { animalBoolean.value = ! animalBoolean.value animalCenterIndex.value =2}}})Copy the code

Background circles how to add of course simple canvas drawing circles directly is easiest.

// Draw a circle background
canvas.drawCircle(Offset(centerWidthOfOneX + keyAnimal, 0f), 60f, paint)
Copy the code

2. Curve animation

How does the radian follow when you click? Width /3 + sie.width /31 + sie.width /32 + sie.width /32 + sie.width /32 + sie.width /32 The key is how do I get which index is selected when I click on it? Modifer.click We can set the selected index. Add an animation to switch the transition effect. Due to space and time problems animation separate opening code case notes can be viewed by yourself.

@Composable
fun BottomNavigation(a){
    // Record the selected index
    val animalCenterIndex = remember { mutableStateOf(0)}val animalBoolean = remember { mutableStateOf(true)}val animalBooleanState: Float by animateFloatAsState(
        if (animalBoolean.value) {
            0f
        } else {
            1f
        }, animationSpec = TweenSpec(durationMillis = 600))// Click on the selected state change to deliver animateFloatAsState to start animation execution
    val indexValue: Float by animateFloatAsState(
        // The target value of the animation. Animation execution begins when animalCenterIndex.value triggers down
        when (animalCenterIndex.value) {
            0- > {0f
            }
            1- > {1f
            }
            else- > {2f}},// Set the format of the animation
        animationSpec = TweenSpec(durationMillis = 500)
    )

    Box(
        modifier = Modifier
            .fillMaxWidth(),
        contentAlignment = Alignment.BottomEnd,
    ) {
        Canvas(modifier = Modifier
            .fillMaxWidth()
            .height(70.dp), onDraw = {
            drawIntoCanvas { canvas ->
                val paint = Paint()
                paint.color = Color(0XFF0DBEBF)
                paint.style = PaintingStyle.Fill

                val path = Path()
                // Divide into three equal parts
                val widthOfOne = size.width / 3
                // The central control point for each radian
                val centerWidthOfOneX = widthOfOne / 2
                // Radian port to ONewidth twice
                val marginLeftAndRigth = centerWidthOfOneX / 1.6 f

                val controllerX = centerWidthOfOneX / 6f
                //⭐️⭐️ port (
                valKeyAnimal = widthOfOne * indexValue⭐️⭐️ ️ ️⭐ canvas. Save () canvas. DrawCircle (Offset(centerWidthOfOneX + keyAnimal,0f), 60f, paint)

                path.moveTo(0f.0f)
                path.lineTo(marginLeftAndRigth / 2 + keyAnimal, 0f)
                path.cubicTo(
                    marginLeftAndRigth + keyAnimal,
                    0f,
                    centerWidthOfOneX - (centerWidthOfOneX - controllerX) / 2f + keyAnimal,
                    size.height / 3f,
                    centerWidthOfOneX + keyAnimal,
                    size.height / 2.6 f
                )
                path.cubicTo(
                    centerWidthOfOneX + (centerWidthOfOneX - controllerX) / 2f + keyAnimal,
                    size.height / 2.6 f,
                    widthOfOne - (marginLeftAndRigth) + keyAnimal,
                    0f,
                    widthOfOne - marginLeftAndRigth / 2 + keyAnimal,
                    0f
                )
                path.lineTo(size.width, 0f)
                path.lineTo(size.width, size.height)
                path.lineTo(0f, size.height)
                path.close()
                canvas.clipPath(path)
                canvas.nativeCanvas.drawColor(Color(0XFF0DBEBF).toArgb())
            }
        })
        Row(
            modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceAround
        ) {
            Image(
                bitmap = getBitmap(resource = R.drawable.home),
                contentDescription = "1",
                modifier = Modifier
                    .modifier(animalCenterIndex, 0, animalBooleanState) .clickable { animalBoolean.value = ! animalBoolean.value animalCenterIndex.value =0
                    }
            )

            Image(
                bitmap = getBitmap(resource = R.drawable.center),
                contentDescription = "1",
                modifier = Modifier
                    .modifier(animalCenterIndex, 1, animalBooleanState) .clickable { animalBoolean.value = ! animalBoolean.value animalCenterIndex.value =1
                    }
            )
            Image(
                bitmap = getBitmap(resource = R.drawable.min),
                contentDescription = "1",
                modifier = Modifier
                    .modifier(animalCenterIndex, 2, animalBooleanState) .clickable { animalBoolean.value = ! animalBoolean.value animalCenterIndex.value =2})}}}// Encapsulate the click selection. Handles return button position and rotation separately.
fun Modifier.modifier(
    animalCenterIndex: MutableState<Int>,
    i: Int,
    animalBooleanState: Float
): Modifier {
    return if (animalCenterIndex.value == i) {
        return Modifier
            .padding(bottom = 57.dp)
            .width(25.dp)
            .height(25.dp)
            .rotate(animalBooleanState * 360)}else {
        return Modifier
            .padding(top = 20.dp)
            .width(25.dp)
            .height(25.dp)
    }

}

Copy the code

3. What other UI can’t do?

  • Bad UI writing is not the programmer’s fault, only the product manager is not perverted enough. Of course, I can’t change color according to the phone case. Baidu Google half a day, also did not find very rare interface, the following interface seems to be ok.

The first picture looks good. Why don’t we build on what we have now? start

This is the third UI article, so I don’t think I’m going to repeat curves here. Read the first two chapters and the customization section

 Box(contentAlignment = Alignment.TopStart) {
            androidx.compose.foundation.Canvas(
                modifier = Modifier
                    .fillMaxHeight()
                    .width(250.dp),
                onDraw = {
                    drawIntoCanvas { canvas ->
                        val paint = Paint()
                        paint.color = Color(36.36.92.255)
                        paint.style = PaintingStyle.Fill
                        paint.isAntiAlias = true
                        paint.blendMode = BlendMode.ColorDodge
                        val roundRect = Path()
                        roundRect.moveTo(0f.0f)
                        roundRect.lineTo(size.width - 350f.0f)
                        roundRect.quadraticBezierTo(
                            size.width,
                            size.height / 2f,
                            size.width - 350f,
                            size.height
                        )
                        roundRect.lineTo(0f, size.height)
                        roundRect.close()
                        canvas.clipPath(roundRect)
                        canvas.drawPath(roundRect, paint)
                    }
                }
            )
            Column(
                horizontalAlignment = Alignment.CenterHorizontally,
                modifier = Modifier
                    .fillMaxHeight()
            ) {
                Image(
                    bitmap = getBitmap(R.drawable.head_god),
                    contentDescription = "w",
                    contentScale = ContentScale.FillBounds,
                    modifier = Modifier
                        .height(50.dp)
                        .width(50.dp)
                        .offset(x = 40.dp, y = 50.dp)
                        .background(color = Color(0XFF0DBEBF), shape = CircleShape)
                        .padding(3.dp)
                        .clip(
                            CircleShape
                        )
                        .shadow(elevation = 150.dp, clip = true)
                        .rotate(
                            animatedOffset.value
                        )
                )
                Column(
                    modifier = Modifier.offset(x = 40.dp, y = 50.dp),
                    horizontalAlignment = Alignment.CenterHorizontally,

                    ) {
                    Text(text = "Hello_World", fontSize = 13.sp, color = Color.White)
                    Text(text = "It's a long way. Come on.", fontSize = 8.sp, color = Color.White)
                    
                    Row(modifier = Modifier.padding(top = 45.dp)) {
                        Image(
                            bitmap = getBitmap(resource = R.drawable.home),
                            contentDescription = "1",
                            modifier = Modifier
                                .clickable {
                                }.padding(end = 15.dp)
                        )
                        Text(text = "Login", fontSize = 13.sp, color = Color.White)
                    }
                    Row(modifier = Modifier.padding(top = 45.dp)) {
                        Image(
                            bitmap = getBitmap(resource = R.drawable.home),
                            contentDescription = "1",
                            modifier = Modifier
                                .clickable {
                                }.padding(end = 15.dp)
                        )
                        Text(text = "Login", fontSize = 13.sp, color = Color.White)
                    }
                    Row(modifier = Modifier.padding(top =45.dp)) {
                        Image(
                            bitmap = getBitmap(resource = R.drawable.home),
                            contentDescription = "1",
                            modifier = Modifier
                                .clickable {
                                }.padding(end = 15.dp)
                        )
                        Text(text = "Login", fontSize = 13.sp, color = Color.White)
                    }

                    Row(modifier = Modifier.padding(top = 45.dp)) {
                        Image(
                            bitmap = getBitmap(resource = R.drawable.home),
                            contentDescription = "1",
                            modifier = Modifier
                                .clickable {
                                }.padding(end = 15.dp)
                        )
                        Text(text = "Login", fontSize = 13.sp, color = Color.White)
                    }



                    Row(modifier = Modifier.padding(top = 95.dp)) {
                        Image(
                            bitmap = getBitmap(resource = R.drawable.home),
                            contentDescription = "1",
                            modifier = Modifier
                                .clickable {
                                }.padding(end = 15.dp)
                        )
                        Text(text = "Login", fontSize = 13.sp, color = Color.White)
                    }
                    Row(modifier = Modifier.padding(top = 45.dp)) {
                        Image(
                            bitmap = getBitmap(resource = R.drawable.home),
                            contentDescription = "1",
                            modifier = Modifier
                                .clickable {
                                }.padding(end = 15.dp)
                        )
                        Text(text = "Login", fontSize = 13.sp, color = Color.White)
                    }
                    Row(modifier = Modifier.padding(top = 45.dp)) {
                        Image(
                            bitmap = getBitmap(resource = R.drawable.home),
                            contentDescription = "1",
                            modifier = Modifier
                                .clickable {
                                }.padding(end = 15.dp)
                        )
                        Text(text = "Login", fontSize = 13.sp, color = Color.White)
                    }
                }

            }

        }
Copy the code

Results the following

If that’s not fancy enough for you, I suggest making an animation where you click on a curve and run, without further ado.

I think you can do it

All three articles are about curves, but curves play an irreplaceable role in many scenarios, and customization is an important skill if you want to differentiate your UI and interactions. I can’t even think of a second latitude UI that we can’t figure out. If so, please add QQ skirt 730772561 together to discuss research. The picture below is for you to do your homework and get a feel for your UI level.

Can I make it look like I wrote it?

Four,

I have been writing the article and hope to make progress together. Your thumbs up and comments are the motivation for my creation. Recently, I was forced into Lenovo by my younger brother. I may have to study a wave of four components of Android recently. I hope that I can join the company smoothly. So Compose will have to put it on hold for a while before it can continue.