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 clipping
anddraw
Let the UI diagram of the application break the ceiling, andgestures
andanimation
The 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 simple
gestures
,animation
In 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?
Transform
orRotationTransition
Such 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?Modifier
It not only solves the problem that too many parameters can be unified configuration, but also provides extremely powerfultailoring
,transform
,Pointer to the gestures
,decoration
And other methods, greatly improve the convenience and creativity.
1, Modifier. The rotate (degrees: Float)
- Any component can be used through the Modifier
Rotating [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 through
Modifier.rotate
To set it, just by clicking on the screenPerform the animation
Continue 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 zoom
andcolor
animation
- By the same token
val 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 passed
The second order curve
Draw the path for clipping.The intermediate point is the control point
When 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.border
addCustom drawing
Can 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 same
Modifier.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
BottomBar
The 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.