Compose: Create Text with Circle Background - android

Coming from SwiftUI, I wanted to create a view of a Text where it has a background of a Circle, where the circle's width/height grow as the text inside Text gets longer.
Since there's no Circle() in Compose like there is in SwifUI, I created just a Spacer instead and clipped it. The code below is using ConstraintLayout because I don't know how I would get the width of the Text in order to set the size of my Circle composable to be the same:
#Composable
fun CircleDemo() {
ConstraintLayout {
val (circle, text) = createRefs()
Spacer(
modifier = Modifier
.background(Color.Black)
.constrainAs(circle) {
centerTo(text)
}
)
Text(
text = "Hello",
color = Color.White,
modifier = Modifier
.constrainAs(text) {
centerTo(parent)
}
)
}
}
I can use a mutableStateOf { 0 } where I update that in an onGloballyPositioned modifier attached to the Text and then set that as the requiredSize for the Spacer, but 1. that seems stupid and 2. the Spacer now draws outside the boundaries of the ConstraintLayout.
Visually I want to achieve this:
How would I go about doing this? Thank you :)

It is also possible to use drawBehind from the modifier of the textView itself such as below:
Text(
modifier = Modifier
.padding(16.dp)
.drawBehind {
drawCircle(
color = red,
radius = this.size.maxDimension
)
},
text = "Hello",
)
of course, you can further customize the radius and other properties as you wish!

You have to calculate the dimension of the background circle depending on the dimension of the text.
You can use a custom modifier based on Modifier.layout:
fun Modifier.circleLayout() =
layout { measurable, constraints ->
// Measure the composable
val placeable = measurable.measure(constraints)
//get the current max dimension to assign width=height
val currentHeight = placeable.height
val currentWidth = placeable.width
val newDiameter = maxOf(currentHeight, currentWidth)
//assign the dimension and the center position
layout(newDiameter, newDiameter) {
// Where the composable gets placed
placeable.placeRelative((newDiameter-currentWidth)/2, (newDiameter-currentHeight)/2)
}
}
Then just just apply it the Text with a background with a CircleShape:
Text(
text = "Hello World",
textAlign = TextAlign.Center,
color = Color.White,
modifier = Modifier
.background(Color.Black, shape = CircleShape)
.circleLayout()
.padding(8.dp)
)

#Composable
fun Avatar(color: Color) {
Box(
modifier = Modifier
.size(size.Dp)
.clip(CircleShape)
.background(color = color),
contentAlignment = Alignment.Center
) {
Text(text = "Hello World")
}
}

Use a background drawable of a black circle inside a transparent color. The background drawable will stretch to fill the view, and circles should stretch well without artifacting.

Related

How to set Box(contentAlignment=CenterVertically40%DownThePage)? Need advanced alignment option for Box

I'm building this screeen from the Android Jetpack Compose lessons, and I want to somewhat-center-vertically the middle / 'Full Name' red box -- but I don't want it fully centered (i.e. 50% down the page), I want it 40% down the page, but the built-in 'contentAlignment' modifiers only cover 'Top', 'Center', and 'Bottom' -- I want something in between.
This is my current code, and it looks fine-ish, but I want to know how to manipulate the vertical alignment of the contents of the Box more fine-grained.
#Composable
fun Profile(){
Box(
modifier = Modifier
.padding(16.dp)
.fillMaxSize(),
contentAlignment = Alignment.Center
,
) {
Column() {
// icon
Text("aslkjdf",
fontSize = 48.sp)
// Full Name
Text("aslkjdf",
fontSize = 48.sp)
// Title
Text("aslkjdf",
fontSize = 48.sp)
}
}
}
You can use the BiasAlignment.
A bias of -1 represents alignment to the start/top, a bias of 0 will represent centering, and a bias of 1 will represent end/bottom.
Just note that the Alignment.Center is defined as:
val Center: Alignment = BiasAlignment(0f, 0f)
You can use something like:
Box(
modifier = Modifier
.padding(16.dp)
.fillMaxSize(),
contentAlignment = BiasAlignment(
horizontalBias = 0f,
verticalBias = -0.2f
)
) {
Column(
modifier = Modifier.fillMaxWidth().border(1.dp,Red),
horizontalAlignment = Alignment.CenterHorizontally
){
// icon
Icon(Icons.Default.Email,"", modifier = Modifier.size(64.dp))
// Full Name
Text("FullName",
fontSize = 36.sp)
// Title
Text("Title",
fontSize = 24.sp)
}
}
Inside the -1, 1 range, the obtained alignment will position the aligned size fully inside the available space, using as center
val y = centerY * (1 + verticalBias)

Setting LazyColumn Item Height to Maximum Available in Jetpack Compose [duplicate]

I have a compose card and inside a row with circle views, I need to make it so the last item in this row is half shown only. Is there a easy way to achieve this without measuring the screen width after everything is drawn and then modify the padding for the row items dynamically to achieve it?)
If you need to calculate the number of elements depending on the size of the cell contents, it is impossible to do this without real measurements.
But if you know exactly how many elements you need to display, you can use Modifier.fillParentMaxWidth with the desired fraction. This is only available in lazy views like LazyRow.
Things are a bit more complicated with spacings: usually contentPadding is used to offset the first element, which reduces the parent size, depending on which Modifier.fillParentPaxMaxWidth calculates the actual value. Also, if you apply it to both start and end, you won't get the desired result. That's why I apply it only to start, and create an equivalent effect at the end with another item.
I also manually surround the Spacers item instead of using Arrangement.spacedBy, because the spacers should be inside the Modifier.fillParentMaxWidth too.
val spacing = 20.dp
val halfSpacing = spacing / 2
val shape = RoundedCornerShape(20)
LazyRow(
contentPadding = PaddingValues(start = halfSpacing),
modifier = Modifier
.padding(30.dp)
.border(1.dp, color = Color.Black, shape = shape)
.clip(shape)
.padding(vertical = 20.dp)
) {
items(10) {
Row(
Modifier
.fillParentMaxWidth(1f / 3.5f)
) {
Spacer(Modifier.size(halfSpacing))
Box(
Modifier
.weight(1f)
.aspectRatio(1f)
.background(Color.Blue, shape = CircleShape)
)
Spacer(Modifier.size(halfSpacing))
}
}
item {
Spacer(Modifier.size(halfSpacing))
}
}
Result:
LazyColumn variant:
val spacing = 20.dp
val halfSpacing = spacing / 2
val shape = RoundedCornerShape(20)
LazyColumn(
contentPadding = PaddingValues(top = halfSpacing),
modifier = Modifier
.padding(30.dp)
.border(1.dp, color = Color.Black, shape = shape)
.clip(shape)
.padding(horizontal = 20.dp)
) {
items(10) {
Column(
Modifier
.fillParentMaxHeight(1f / 3.5f)
) {
Spacer(Modifier.size(halfSpacing))
Box(
Modifier
.weight(1f)
.aspectRatio(1f)
.background(Color.Blue, shape = CircleShape)
)
Spacer(Modifier.size(halfSpacing))
}
}
item {
Spacer(Modifier.size(halfSpacing))
}
}

Spacing views inside a compose row so the last view always is half shown

I have a compose card and inside a row with circle views, I need to make it so the last item in this row is half shown only. Is there a easy way to achieve this without measuring the screen width after everything is drawn and then modify the padding for the row items dynamically to achieve it?)
If you need to calculate the number of elements depending on the size of the cell contents, it is impossible to do this without real measurements.
But if you know exactly how many elements you need to display, you can use Modifier.fillParentMaxWidth with the desired fraction. This is only available in lazy views like LazyRow.
Things are a bit more complicated with spacings: usually contentPadding is used to offset the first element, which reduces the parent size, depending on which Modifier.fillParentPaxMaxWidth calculates the actual value. Also, if you apply it to both start and end, you won't get the desired result. That's why I apply it only to start, and create an equivalent effect at the end with another item.
I also manually surround the Spacers item instead of using Arrangement.spacedBy, because the spacers should be inside the Modifier.fillParentMaxWidth too.
val spacing = 20.dp
val halfSpacing = spacing / 2
val shape = RoundedCornerShape(20)
LazyRow(
contentPadding = PaddingValues(start = halfSpacing),
modifier = Modifier
.padding(30.dp)
.border(1.dp, color = Color.Black, shape = shape)
.clip(shape)
.padding(vertical = 20.dp)
) {
items(10) {
Row(
Modifier
.fillParentMaxWidth(1f / 3.5f)
) {
Spacer(Modifier.size(halfSpacing))
Box(
Modifier
.weight(1f)
.aspectRatio(1f)
.background(Color.Blue, shape = CircleShape)
)
Spacer(Modifier.size(halfSpacing))
}
}
item {
Spacer(Modifier.size(halfSpacing))
}
}
Result:
LazyColumn variant:
val spacing = 20.dp
val halfSpacing = spacing / 2
val shape = RoundedCornerShape(20)
LazyColumn(
contentPadding = PaddingValues(top = halfSpacing),
modifier = Modifier
.padding(30.dp)
.border(1.dp, color = Color.Black, shape = shape)
.clip(shape)
.padding(horizontal = 20.dp)
) {
items(10) {
Column(
Modifier
.fillParentMaxHeight(1f / 3.5f)
) {
Spacer(Modifier.size(halfSpacing))
Box(
Modifier
.weight(1f)
.aspectRatio(1f)
.background(Color.Blue, shape = CircleShape)
)
Spacer(Modifier.size(halfSpacing))
}
}
item {
Spacer(Modifier.size(halfSpacing))
}
}

Wrap content to a text with clipped shape in Compose?

I have a text compose and I want to apply a triangle shape on it, I applied the tringle successfully but the text doesn't wrap inside the triangle shape, it seems that the shape doesn't cut the view to be tringle, it only draws above it or thing like that.
here is the code:
Text(
modifier = Modifier
.clip(TriangleShape)
.background(
Brush.horizontalGradient(
listOf(
Color(0xff4F0D37),
Color(0xffD73046)
)
)
),
text = "150",
color = Color.White,
)
Any tip will be appreciated :)
Here's how I did it:
Text(
modifier = Modifier
.clip(TriangleShape)
.background(
Brush.horizontalGradient(
listOf(
Color(0xff4F0D37),
Color(0xffD73046)
)
)
)
.layout { measurable, constraints ->
val placeable = measurable.measure(constraints)
layout(placeable.width * 2, placeable.height * 2) {
placeable.placeRelative(0, 0)
}
},
text = "150",
color = Color.White,
)
I used the layout lambda expression to double the width and the height of the composable, while keeping the everything else the same. Took some messing around with rows and columns but I think this is a fitting solution.
Here's the result:
BoxWithConstraints{
val constraints = size
Text(
modifier = Modifier
.clip(TriangleShape)
.background(
Brush.horizontalGradient(
listOf(
Color(0xff4F0D37),
Color(0xffD73046)
)
)
),
text = "150",
color = Color.White,
fontSize = (constraints.width / 2) // ... Modify if necessary
)
}
Here I am using the size parameter exposed by BoxWithConstraints. This is the size of the box, which I am storing in a variable to later re-use to set the font size of the text accordingly. Text positioning can be done easily. Use Modifier.align(TopStart)

How to make a surface background half transparent in jetpack compose, but not the content?

I want to achieve this layout:
In XML I would add an image in a relative layout with match_parent attributes, then a view with a black half-transparent background set to match_parent as well, then the content.
In compose I made this composeable:
#Composable
fun ImageCover(resourceId: Int, alpha: Float = 0.5f, content: #Composable () -> Unit) {
Box(modifier = Modifier.fillMaxSize()) {
Image(
painter = painterResource(id = resourceId),
contentDescription = null,
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop
)
Surface(
color = Color.Black, modifier = Modifier
.fillMaxSize()
.alpha(alpha)
) {
content()
}
}
}
But the problem is alpha is applied to the surface and its content. So no matter what I put in the content, even if it's another surface with a background, will also be half transparent. Here, for example, the two sentences and two components at the bottom will be half transparent as well.
The background color of the Surface is based on the color attribute.
Apply the alpha to the color property instead of the Modifier.
Something like:
Surface(
color = Color.Black.copy(alpha = 0.6f),
modifier = Modifier.fillMaxSize()
){
//....
}

Categories

Resources