Im just learning jetpack compose, an I got a problem to make a border..
so I wanna make a border just in partial side like border bottom, border top etc. only
so how to make like that
Row(horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp)
.defaultMinSize(minHeight = 56.dp)) {}
thanks
You can try this code & draw lines,
Row(
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically,
modifier =
Modifier
.fillMaxWidth()
.wrapContentHeight()
.wrapContentWidth()
.padding(horizontal = 16.dp, vertical = 8.dp)
.drawBehind {
val strokeWidth = 2f
val x = size.width - strokeWidth
val y = size.height - strokeWidth
//top line
drawLine(
color = Color.Green,
start = Offset(0f, 0f), //(0,0) at top-left point of the box
end = Offset(x, 0f), //top-right point of the box
strokeWidth = strokeWidth
)
//left line
drawLine(
color = Color.Magenta,
start = Offset(0f, 0f), //(0,0) at top-left point of the box
end = Offset(0f, y),//bottom-left point of the box
strokeWidth = strokeWidth
)
//right line
drawLine(
color = Color.Red,
start = Offset(x, 0f),// top-right point of the box
end = Offset(x, y),// bottom-right point of the box
strokeWidth = strokeWidth
)
//bottom line
drawLine(
color = Color.Cyan,
start = Offset(0f, y),// bottom-left point of the box
end = Offset(x, y),// bottom-right point of the box
strokeWidth = strokeWidth
)
}) {
Column(modifier = Modifier.padding(2.dp)) {
Text(text = "testing", color = Color.Black)
Text(text = "another testing")
}
}
Related
I want to draw this shape in jetpack compose, anybody has idea how we can achieve this?
You can simply draw a shape inside a Box.
Something like:
Box(
Modifier
.fillMaxWidth()
.height(200.dp)
.background(Color.DarkGray)
.padding(10.dp)
.background(Red)
) {
Canvas(
modifier = Modifier
.fillMaxSize()
) {
val canvasWidth = size.width
val arcHeight = 200f
drawArc(
color = Color.DarkGray,
startAngle = 0f,
sweepAngle = 180f,
useCenter = false,
topLeft = Offset(0f, -arcHeight/2),
size = Size(canvasWidth, arcHeight)
)
}
}
I want to add a border to the top half of the Card component that has a corner radius (10dp).
So, only the bottom part is missing rest of the card has a stroke of 1dp. (kind of like U and inverted U)
And I want to do the same for card that has a bottom corner radius and the top part is missing.
I tried to design a custom shape but it's not respecting the shape of the card.
// this is just a line but it doesn't respect the card corner radius?
private fun createShape(thickness: Float) : Shape {
return GenericShape { size, _ ->
moveTo(0f,0f)
lineTo(0f, size.height)
lineTo(thickness, size.height)
lineTo(thickness, 0f)
lineTo(0f, thickness)
}
}
val thickness = with(LocalDensity.current) {
1.dp.toPx()
}
Card(
shape = RoundedCornerShape(topEnd = 10.dp, topStart = 10.dp, bottomEnd = 0.dp, bottomStart = 0.dp),
modifier = Modifier
.border(BorderStroke(width = 1.dp, color = Color.Black), createShape(thickness))
) {
...
}
Border doesn't seem to allow open shapes, forces shape to be closed even if you draw 3 lines because of that you need to use Modifier.drawBehind or Modifier.drawWithContent.
Created a Modifier that draws u shaped border as
fun Modifier.semiBorder(strokeWidth: Dp, color: Color, cornerRadiusDp: Dp) = composed(
factory = {
val density = LocalDensity.current
val strokeWidthPx = density.run { strokeWidth.toPx() }
val cornerRadius = density.run { cornerRadiusDp.toPx() }
Modifier.drawBehind {
val width = size.width
val height = size.height
drawLine(
color = color,
start = Offset(x = 0f, y = height),
end = Offset(x = 0f, y = cornerRadius),
strokeWidth = strokeWidthPx
)
// Top left arc
drawArc(
color = color,
startAngle = 180f,
sweepAngle = 90f,
useCenter = false,
topLeft = Offset.Zero,
size = Size(cornerRadius * 2, cornerRadius * 2),
style = Stroke(width = strokeWidthPx)
)
drawLine(
color = color,
start = Offset(x = cornerRadius, y = 0f),
end = Offset(x = width - cornerRadius, y = 0f),
strokeWidth = strokeWidthPx
)
// Top right arc
drawArc(
color = color,
startAngle = 270f,
sweepAngle = 90f,
useCenter = false,
topLeft = Offset(x = width - cornerRadius * 2, y = 0f),
size = Size(cornerRadius * 2, cornerRadius * 2),
style = Stroke(width = strokeWidthPx)
)
drawLine(
color = color,
start = Offset(x = width, y = height),
end = Offset(x = width, y = cornerRadius),
strokeWidth = strokeWidthPx
)
}
}
)
Usage
#Composable
private fun UShapeBorderSample() {
Card(
shape = RoundedCornerShape(
topEnd = 10.dp,
topStart = 10.dp,
bottomEnd = 0.dp,
bottomStart = 0.dp
),
modifier = Modifier
.semiBorder(1.dp, Color.Black, 10.dp)
) {
Box(
modifier = Modifier
.size(150.dp)
.background(Color.White),
contentAlignment = Alignment.Center
) {
Text("Hello World")
}
}
Spacer(modifier = Modifier.height(10.dp))
Card(
shape = RoundedCornerShape(
topEnd = 20.dp,
topStart = 20.dp,
bottomEnd = 0.dp,
bottomStart = 0.dp
),
modifier = Modifier
.semiBorder(1.dp, Color.Black, 20.dp)
) {
Box(
modifier = Modifier
.size(150.dp)
.background(Color.White),
contentAlignment = Alignment.Center
) {
Text("Hello World")
}
}
}
You need to use arc to create rounded corners. And when creating shape you don't pass thickness but radius of shape. Thickness is required when drawing border. What you draw is a rectangle with 1.dp width.
#Composable
private fun createShape(cornerRadius: Dp): Shape {
val density = LocalDensity.current
return GenericShape { size, _ ->
val width = size.width
val height = size.height
val cornerRadiusPx = density.run { cornerRadius.toPx() }
moveTo(0f, height)
// Vertical line on left size
lineTo(0f, cornerRadiusPx * 2)
arcTo(
rect = Rect(
offset = Offset.Zero,
size = Size(cornerRadiusPx * 2, cornerRadiusPx * 2)
),
startAngleDegrees = 180f,
sweepAngleDegrees = 90f,
forceMoveTo = false
)
lineTo(width - cornerRadiusPx * 2, 0f)
arcTo(
rect = Rect(
offset = Offset(width - cornerRadiusPx * 2, 0f),
size = Size(cornerRadiusPx * 2, cornerRadiusPx * 2)
),
startAngleDegrees = 270f,
sweepAngleDegrees = 90f,
forceMoveTo = false
)
// Vertical line on right size
lineTo(width, height)
}
}
Usage
#Composable
private fun UShapeBorderSample() {
Card(
shape = RoundedCornerShape(
topEnd = 10.dp,
topStart = 10.dp,
bottomEnd = 0.dp,
bottomStart = 0.dp
),
modifier = Modifier
.border(BorderStroke(width = 1.dp, color = Color.Black), createShape(10.dp))
) {
Box(modifier = Modifier
.size(200.dp)
.background(Color.White),
contentAlignment = Alignment.Center
) {
Text("Hello World")
}
}
}
Border doesn't respect shape because it's a drawing but Card is a Box under the hood that uses shape with Modifier.clip() which itself is Modifier.graphicsLayer{clip} that applies operations on a layer.
You can check out this answer about clip and border for the difference.
https://stackoverflow.com/a/73091667/5457853
I'm trying to add a left/start vertical border to view (Column), Not able to get the solution. as of now was trying to achieve using a divider inside the column it also need a height, but it depends on the contents inside the column, sometime it may grow.
Column(modifier = Modifier.padding(start = 34.dp)) {
Divider(
color = Color.Red,
modifier = Modifier
.height(100.dp)
.padding(end = 34.dp).width(2.dp)
)
You can use the drawWithCache modifier using the drawLine function.
Something like:
Column(modifier =
Modifier
.padding(start = 34.dp)
.size(100.dp, 75.dp)
.drawWithCache {
onDrawWithContent {
// draw behind the content the vertical line on the left
drawLine(
color = Color.Red,
start = Offset.Zero,
end = Offset(0f, this.size.height),
strokeWidth= 1f
)
// draw the content
drawContent()
}
}
){
//...content
}
If you want to use a Divider you can use fillMaxHeight() applying an intrinsic measurements to its parent container.
Something like:
Row(modifier = Modifier.height(IntrinsicSize.Min)) {
Divider(
color = Color.Red,
modifier = Modifier
.fillMaxHeight() //important
.width(2.dp)
)
Box(Modifier.fillMaxWidth().height(100.dp).background(Yellow))
}
You can achive this with Modifier.drawBehind and drawLine
Code
TextButton(
onClick = {
//Click Functions
},
modifier = Modifier.drawBehind {
val strokeWidth = 1 * density
//Draw line function for left border
drawLine(
Color.LightGray,
Offset(0f, strokeWidth),
Offset(0f, size.height),
strokeWidth
)
}
)
{
Text("Left Border")
}
Output
I am trying to create a semicircle speed progress bar in Jetpack Compose. Unless the view is square the semicircle will not look as expected, if I use 1:2 width: height it will be flattened. I want a Composable representing half of the circle where I don't have unusable bottom half of the view.
Box(
modifier = modifier
.background(Color.Red)
) {
Canvas(modifier = Modifier.size(300.dp)) {
drawArc(
color = Color.LightGray,
-180f,
180f,
useCenter = false,
style = Stroke(8.dp.toPx(), cap = StrokeCap.Round)
)
}
Text(
modifier = Modifier.align(alignment = Alignment.Center),
text = "20 Mbps",
color = Color.White,
fontSize = 20.sp
)
}
The expected outcome would be a reusable semicircle composable with a height of the actual semicircle so I can easily position other content against it. The expected view size is marked by a dotted green line.
As i mentioned in comments arc uses rectangle if you want a semi arc that covers whole hight just double the height you draw arc with
#Composable
private fun ArcComposable(modifier: Modifier) {
Box(
modifier = modifier
.background(Color.Red)
) {
Canvas(modifier = Modifier
.size(300.dp)
.clipToBounds()) {
drawArc(
color = Color.LightGray,
-180f,
180f,
useCenter = false,
size = Size(size.width, size.height * 2),
style = Stroke(8.dp.toPx(), cap = StrokeCap.Round)
)
}
Text(
modifier = Modifier.align(alignment = Alignment.Center),
text = "20 Mbps",
color = Color.White,
fontSize = 20.sp
)
}
}
I added Modifier.clipToBounds() because of strokeCap round which is added to length of the line by default. You can just reduce size and height few px to match inside the canvas. Canvas by default even if you don't set a modifier with size it draws anything out of its bounds unless you use Modifier.clipToBounds()
private fun ArcComposable(modifier: Modifier) {
Box(
modifier = modifier
.background(Color.Red)
) {
Canvas(
modifier = Modifier
.size(300.dp)
// .clipToBounds()
) {
drawArc(
color = Color.LightGray,
-180f,
180f,
useCenter = false,
topLeft = Offset(4.dp.toPx(), 6.dp.toPx()),
size = Size(size.width - 8.dp.toPx(), size.height * 2 - 20.dp.toPx()),
style = Stroke(8.dp.toPx(), cap = StrokeCap.Round)
)
}
Text(
modifier = Modifier.align(alignment = Alignment.Center),
text = "20 Mbps",
color = Color.White,
fontSize = 20.sp
)
}
}
I want to add border on bottom of the layout. I know i can use Divider composable but i just want to learn how to draw a border.
Currently, I can add border for all sides which is not what I want.
Row(
modifier = Modifier
.border(border = BorderStroke(width = 1.dp, Color.LightGray))
) {
TextField(value = "", onValueChange = {}, modifier = Modifier.weight(1f))
Switch(checked = true, onCheckedChange = {})
Icon(Icons.Filled.Close, "Remove", tint = Color.Gray)
}
You can use the drawBehind modifier to draw a line.
Something like:
Row(
modifier = Modifier
.drawBehind {
val strokeWidth = indicatorWidth.value * density
val y = size.height - strokeWidth / 2
drawLine(
Color.LightGray,
Offset(0f, y),
Offset(size.width, y),
strokeWidth
)
}){
//....
}
If you prefer you can build your custom Modifier with the same code above
fun Modifier.bottomBorder(strokeWidth: Dp, color: Color) = composed(
factory = {
val density = LocalDensity.current
val strokeWidthPx = density.run { strokeWidth.toPx() }
Modifier.drawBehind {
val width = size.width
val height = size.height - strokeWidthPx/2
drawLine(
color = color,
start = Offset(x = 0f, y = height),
end = Offset(x = width , y = height),
strokeWidth = strokeWidthPx
)
}
}
)
and then just apply it:
Row(
modifier = Modifier
.padding(horizontal = 8.dp)
.fillMaxWidth()
.bottomBorder(1.dp, DarkGray)
){
//Row content
}
You can draw a line in a draw scope. In my opinion, a divider looks cleaner in code.
Row(modifier = Modifier
.drawWithContent {
drawContent()
clipRect { // Not needed if you do not care about painting half stroke outside
val strokeWidth = Stroke.DefaultMiter
val y = size.height // - strokeWidth
// if the whole line should be inside component
drawLine(
brush = SolidColor(Color.Red),
strokeWidth = strokeWidth,
cap = StrokeCap.Square,
start = Offset.Zero.copy(y = y),
end = Offset(x = size.width, y = y)
)
}
}
) {
Text("test")
}
Yeah this oughta do it:-
#Suppress("UnnecessaryComposedModifier")
fun Modifier.topRectBorder(width: Dp = Dp.Hairline, brush: Brush = SolidColor(Color.Black)): Modifier = composed(
factory = {
this.then(
Modifier.drawWithCache {
onDrawWithContent {
drawContent()
drawLine(brush, Offset(width.value, 0f), Offset(size.width - width.value, 0f))
}
}
)
},
inspectorInfo = debugInspectorInfo {
name = "border"
properties["width"] = width
if (brush is SolidColor) {
properties["color"] = brush.value
value = brush.value
} else {
properties["brush"] = brush
}
properties["shape"] = RectangleShape
}
)
You can define a rectangular Shape on the bottom of your element, using the bottom line thickness as parameter:
private fun getBottomLineShape(bottomLineThickness: Float) : Shape {
return GenericShape { size, _ ->
// 1) Bottom-left corner
moveTo(0f, size.height)
// 2) Bottom-right corner
lineTo(size.width, size.height)
// 3) Top-right corner
lineTo(size.width, size.height - bottomLineThickness)
// 4) Top-left corner
lineTo(0f, size.height - bottomLineThickness)
}
}
And then use it in the border modifier like this:
val lineThickness = with(LocalDensity.current) {[desired_thickness_in_dp].toPx()}
Row(
modifier = Modifier
.height(rowHeight)
.border(width = lineThickness,
color = Color.Black,
shape = getBottomLineShape(lineThickness))
) {
// Stuff in the row
}
Using a "Divider" worked for me,
Column {
Divider (
color = Color.White,
modifier = Modifier
.height(1.dp)
.fillMaxHeight()
.fillMaxWidth()
)
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
) {
// Something else
}
}