Make a Row of rotated Text in Jetpack compose - android

I want to make a Row of -90 degrees rotated Text Composables to make something like below:
However this code (repro case):
#Preview(showBackground = true, backgroundColor = 0xffffffff)
#Composable
fun Preview_Row_With_Rotated_Text() {
Row {
Text(
text = "A text",
modifier = Modifier
.padding(2.dp)
.rotate(-90f),
maxLines = 1,
)
Text(
text = "A text which is a bit longer",
modifier = Modifier
.padding(2.dp)
.rotate(-90f),
maxLines = 1,
)
Text(
text = "A text which is kinda longer than previous one",
modifier = Modifier
.padding(2.dp)
.rotate(-90f),
maxLines = 1,
)
}
}
produces this:
The Row uses old width of Text composables (the width of non-rotated Text) to place them one after another.
Where is the reason of this problem?

You can try using it in a Column and then rotating that Column by -90f:
#Composable
fun Main() {
Column(
modifier = Modifier
.width(200.dp)
.rotate(-90f)
) {
MyText(text = "Financial Advice")
MyText(text = "Strategy and Marketing")
MyText(text = "Information Technology")
}
}
#Composable
fun MyText(text: String) {
Text(
text = text,
modifier = Modifier
.padding(4.dp)
.fillMaxWidth()
.background(MaterialTheme.colors.secondary)
.padding(16.dp),
textAlign = TextAlign.Center,
maxLines = 1
)
}

Related

Android Text is too wide when it contains long words

I want to display a text with an icon left to the text. The text and icon should be centered horizontally. Here is a composable function for this:
Column(Modifier.fillMaxSize()) {
Row(
modifier = Modifier.align(Alignment.CenterHorizontally),
verticalAlignment = Alignment.CenterVertically,
) {
// imagine this Box is an icon
Box(
Modifier
.size(48.dp)
.background(Color.Red)
)
Spacer(Modifier.width(8.dp))
Text(
text = "text ".repeat(3),
textAlign = TextAlign.Center,
)
}
}
It works fine with short words:
But adding long words to the text makes it too wide, and it seems that there is too much space between the icon and the text:
I've tried to add Modifier.width(IntrinsicSize.Min) to the text, and it actually solves the issue with long words:
But it breaks displaying short words:
I don't know how to make work both long and short words. Hope to get help here.
UPD:
The same result is for Android native views. Gist with xmls.
You can consider this one, all of the codes below are copy-and-paste-able.
#Composable
fun MyScreen() {
var text by remember { mutableStateOf("") }
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally
) {
TextField(
modifier = Modifier.fillMaxWidth(),
value = text,
onValueChange = { text = it}
)
Spacer(modifier = Modifier.height(24.dp))
Row(
modifier = Modifier.wrapContentSize()
) {
Spacer(modifier = Modifier.weight(.5f))
SomeComposable(text = text, modifier = Modifier.weight(1f))
Spacer(modifier = Modifier.weight(.5f))
}
}
}
#Composable
fun SomeComposable(
text: String,
modifier: Modifier = Modifier
) {
Row(modifier = modifier) {
Box(
Modifier
.align(Alignment.CenterVertically)
.size(48.dp)
.background(Color.Red)
)
Spacer(Modifier.width(8.dp))
Text(
modifier = Modifier.width(IntrinsicSize.Min),
text = text,
textAlign = TextAlign.Center,
)
}
}
I just put a Spacer between the components and weighted them accordingly.
Output:
Solution proposed by my colleague.
#Composable
fun FlowText(text: String) {
FlowRow(
mainAxisSpacing = 4.dp,
mainAxisAlignment = MainAxisAlignment.Center,
crossAxisAlignment = FlowCrossAxisAlignment.Center,
) {
text.splitToSequence(' ').filter { it.isNotEmpty() }.forEach {
Text(text = it, textAlign = TextAlign.Center)
}
}
}
Demo: https://youtu.be/WXqvxlsJ3xM

Jetpack Compose Smart Recomposition

I'm doing experiments to comprehend recomposition and smart recomposition and made a sample
Sorry for the colors, they are generated with Random.nextIn() to observe recomposition visually, setting colors has no effect on recomposition, tried without changing colors either.
What's in gif is composed of three parts
Sample1
#Composable
private fun Sample1() {
Column(
modifier = Modifier
.background(getRandomColor())
.fillMaxWidth()
.padding(4.dp)
) {
var counter by remember { mutableStateOf(0) }
Text("Sample1", color = getRandomColor())
Button(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp),
colors = ButtonDefaults.buttonColors(backgroundColor = getRandomColor()),
onClick = {
counter++
}) {
Text("Counter: $counter", color = getRandomColor())
}
}
}
I have no questions here since smart composition works as expected, Text on top is not reading changes in counter so recomposition only occurs for Text inside Button.
Sample2
#Composable
private fun Sample2() {
Column(
modifier = Modifier.background(getRandomColor())
) {
var update1 by remember { mutableStateOf(0) }
var update2 by remember { mutableStateOf(0) }
println("ROOT")
Text("Sample2", color = getRandomColor())
Button(
modifier = Modifier
.padding(start = 8.dp, end = 8.dp, top = 4.dp)
.fillMaxWidth(),
colors = ButtonDefaults.buttonColors(backgroundColor = getRandomColor()),
onClick = {
update1++
},
shape = RoundedCornerShape(5.dp)
) {
println("๐Ÿ”ฅ Button1๏ธ")
Text(
text = "Update1: $update1",
textAlign = TextAlign.Center,
color = getRandomColor()
)
}
Button(
modifier = Modifier
.padding(start = 8.dp, end = 8.dp, top = 2.dp)
.fillMaxWidth(),
colors = ButtonDefaults.buttonColors(backgroundColor = getRandomColor()),
onClick = { update2++ },
shape = RoundedCornerShape(5.dp)
) {
println("๐Ÿ Button 2๏ธ")
Text(
text = "Update2: $update2",
textAlign = TextAlign.Center,
color = getRandomColor()
)
}
Column(
modifier = Modifier.background(getRandomColor())
) {
println("๐Ÿš€ Inner Column")
var update3 by remember { mutableStateOf(0) }
Button(
modifier = Modifier
.padding(start = 8.dp, end = 8.dp, top = 2.dp)
.fillMaxWidth(),
colors = ButtonDefaults.buttonColors(backgroundColor = getRandomColor()),
onClick = { update3++ },
shape = RoundedCornerShape(5.dp)
) {
println("โœ… Button 3๏ธ")
Text(
text = "Update2: $update2, Update3: $update3",
textAlign = TextAlign.Center,
color = getRandomColor()
)
}
}
Column() {
println("โ˜•๏ธ Bottom Column")
Text(
text = "Sample2",
textAlign = TextAlign.Center,
color = getRandomColor()
)
}
}
}
It also works as expected each mutableState is updating only the scope they have been observed in. Only Text that observes update2 and update3 is changed when either of these mutableStates are updated.
Sample3
#Composable
private fun Sample3() {
Column(
modifier = Modifier.background(getRandomColor())
) {
var update1 by remember { mutableStateOf(0) }
var update2 by remember { mutableStateOf(0) }
println("ROOT")
Text("Sample3", color = getRandomColor())
Button(
modifier = Modifier
.padding(start = 8.dp, end = 8.dp, top = 4.dp)
.fillMaxWidth(),
colors = ButtonDefaults.buttonColors(backgroundColor = getRandomColor()),
onClick = {
update1++
},
shape = RoundedCornerShape(5.dp)
) {
println("๐Ÿ”ฅ Button1๏ธ")
Text(
text = "Update1: $update1",
textAlign = TextAlign.Center,
color = getRandomColor()
)
}
Button(
modifier = Modifier
.padding(start = 8.dp, end = 8.dp, top = 2.dp)
.fillMaxWidth(),
colors = ButtonDefaults.buttonColors(backgroundColor = getRandomColor()),
onClick = { update2++ },
shape = RoundedCornerShape(5.dp)
) {
println("๐Ÿ Button 2๏ธ")
Text(
text = "Update2: $update2",
textAlign = TextAlign.Center,
color = getRandomColor()
)
}
Column {
println("๐Ÿš€ Inner Column")
var update3 by remember { mutableStateOf(0) }
Button(
modifier = Modifier
.padding(start = 8.dp, end = 8.dp, top = 2.dp)
.fillMaxWidth(),
colors = ButtonDefaults.buttonColors(backgroundColor = getRandomColor()),
onClick = { update3++ },
shape = RoundedCornerShape(5.dp)
) {
println("โœ… Button 3๏ธ")
Text(
text = "Update2: $update2, Update3: $update3",
textAlign = TextAlign.Center,
color = getRandomColor()
)
}
}
// ๐Ÿ”ฅ๐Ÿ”ฅ Reading update1 causes entire composable to recompose
Column(
modifier = Modifier.background(getRandomColor())
) {
println("โ˜•๏ธ Bottom Column")
Text(
text = "Update1: $update1",
textAlign = TextAlign.Center,
color = getRandomColor()
)
}
}
}
Only difference between Sample2 and Sample3 is Text at the bottom is reading update1 mutableState which causing entire composable to be recomposed. As you can see in gif changing update1 recomposes or changes entire color schema for Sample3.
What's the reason for recomposing entire composable?
Column(
modifier = Modifier.background(getRandomColor())
) {
println("โ˜•๏ธ Bottom Column")
Text(
text = "Update1: $update1",
textAlign = TextAlign.Center,
color = getRandomColor()
)
}
}
To have smart recomposition scopes play a pivotal role. You can check Vinay Gaba's What is โ€œdonut-hole skippingโ€ in Jetpack Compose? article.
Leland Richardson explains in this tweet as
The part that is "donut hole skipping" is the fact that a new lambda
being passed into a composable (ie Button) can recompose without
recompiling the rest of it. The fact that the lambda are recompose
scopes are necessary for you to be able to do this, but not
sufficient
In other words, composable lambda are "special" :)
We wanted to do this for a long time but thought it was too
complicated until #chuckjaz had the brilliant realization that if the
lambdas were state objects, and invokes were reads, then this is
exactly the result
You can also check other answers about smart recomposition here, and here.
https://dev.to/zachklipp/scoped-recomposition-jetpack-compose-what-happens-when-state-changes-l78
When a State is read it triggers recomposition in nearest scope. And a scope is a function that is not marked with inline and returns Unit. Column, Row and Box are inline functions and because of that they don't create scopes.
Created RandomColorColumn that take other Composables and its scope content: #Composable () -> Unit
#Composable
fun RandomColorColumn(content: #Composable () -> Unit) {
Column(
modifier = Modifier
.padding(4.dp)
.shadow(1.dp, shape = CutCornerShape(topEnd = 8.dp))
.background(getRandomColor())
.padding(4.dp)
) {
content()
}
}
And replaced
Column(
modifier = Modifier.background(getRandomColor())
) {
println("โ˜•๏ธ Bottom Column")
Text(
text = "Update1: $update1",
textAlign = TextAlign.Center,
color = getRandomColor()
)
}
}
with
RandomColorColumn() {
println("โ˜•๏ธ Bottom Column")
/*
๐Ÿ”ฅ๐Ÿ”ฅ Observing update(mutableState) does NOT causes entire composable to recompose
*/
Text(
text = "๐Ÿ”ฅ Update1: $update1",
textAlign = TextAlign.Center,
color = getRandomColor()
)
}
}
Only this scope gets updated as expected and we have smart recomposition.
What causes Text, or any Composable, inside Column to not have a scope, thus being recomposed when a mutableState value changes is Column having inline keyword in function signature.
#Composable
inline fun Column(
modifier: Modifier = Modifier,
verticalArrangement: Arrangement.Vertical = Arrangement.Top,
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
content: #Composable ColumnScope.() -> Unit
) {
val measurePolicy = columnMeasurePolicy(verticalArrangement, horizontalAlignment)
Layout(
content = { ColumnScopeInstance.content() },
measurePolicy = measurePolicy,
modifier = modifier
)
}
If you add inline to RandomColorColumn function signature you will see that it causes whole Composable to recompose.
Compose uses call sites defined as
The call site is the source code location in which a composable is
called. This influences its place in Composition, and therefore, the
UI tree.
If during a recomposition a composable calls different composables
than it did during the previous composition, Compose will identify
which composables were called or not called and for the composables
that were called in both compositions, Compose will avoid recomposing
them if their inputs haven't changed.
Consider the following example:
#Composable
fun LoginScreen(showError: Boolean) {
if (showError) {
LoginError()
}
LoginInput() // This call site affects where LoginInput is placed in Composition
}
#Composable
fun LoginInput() { /* ... */ }
Call site of a Composable function affects smart recomposition, and having inline keyword in a Composable sets its child Composables call site same level, not one level below.
For anyone interested here is the github repo to play/test recomposition

Row with two Text in Constraint fashion in Jetpack Compose

I want to include two Text in a Row where the first Text's width is upto the start of 2nd Text, like this
I am trying Modifier weight but the result achieved is not the same.
Is there a way to do it by using Row itself and not ConstraintLayout.
EDIT :
Row(modifier = Modifier.fillMaxWidth()) {
Text(
"Some long title abcd efgh ijkl mnop qrst uvwx yzzzzz Some long title abcd efgh ijkl mnop qrst uvwx yzzzzz",
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(5f)
)
Text("View all", modifier = Modifier.weight(1f))
}
This works, please suggest a better solution if I am missing something.
EDIT 2 :
Its giving me results like this:
I want the Title to start from the beginning of Row
You can use something like:
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween) {
Text(
"Short title",
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(1f)
)
Text("View all")
}
Just for comparison, this is the same solution but done with ConstraintLayout:
ConstraintLayout(
modifier = Modifier.fillMaxSize()
) {
val (title, viewAll) = createRefs()
Text(text = "View all", Modifier
.background(Color.Green)
.constrainAs(viewAll) {
top.linkTo(parent.top, 8.dp)
end.linkTo(parent.end, 8.dp)
})
Text(text = "Short title",
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier
.background(Color.White)
.constrainAs(title) {
top.linkTo(parent.top, 8.dp)
start.linkTo(parent.start, 8.dp)
end.linkTo(viewAll.start, 8.dp)
width = Dimension.fillToConstraints
})
}

Issue with defining weight to views in Jetpack compose

I want to make a simple jetpack compose layout, using weights. below is the code
Row() {
Column(
Modifier.weight(1f).background(Blue)){
Text(text = "Weight = 1", color = Color.White)
}
Column(
Modifier.weight(2f).background(Yellow)
) {
Text(text = "Weight = 2")
}
}
which will result in a layout like this
but what if my inner views are coming from some other Composable function, which is unaware that its going to be a child of a column or a row?
Row() {
// Calling Some composable function here
}
In that case i am not able to access the Modifier.weight(..) function, because it thinks that its not in the row or column scope, because it is an independent function.
You can add the modifier as parameter in your Composable.
Something like:
#Composable
fun myCard(modifier: Modifier = Modifier, name:String){
Box(modifier){
Text(name,textAlign = TextAlign.Center,)
}
}
In this way the myCard is unaware that its going to be a child of a Column or a Row.
Then in your implementation you can use:
Row(Modifier.padding(16.dp).height(50.dp)) {
myCard(Modifier.weight(1f).background(Color.Yellow),"Weight = 1")
myCard(Modifier.weight(2f).background(Color.Red),"Weight = 2")
}
You can prefix RowScope. or ColumnScope. to your function declaration
#Composable
fun RowScope.SomeComposable() {
// weights should work in here
Column(Modifier.weight(1f)){ /**/ }
}
Row(
modifier = mainRowModifier,
verticalAlignment = rowCenterVerticallyAlign
) {
Row(
verticalAlignment = rowCenterVerticallyAlign,
horizontalArrangement = rowContentArrangementCenter,
modifier = Modifier.weight(.5f, true).wrapContentHeight()
) {
Text(
text = "971",
maxLines = 1,
overflow = TextOverflow.Ellipsis,
textAlign = TextAlign.Center
)
Icon(
painter = painterResource(id = resIdDropDownIcon),
contentDescription = null,
modifier = Modifier.padding(start = startPaddingDropdownIcon.dp)
)
}
Divider(
color = dividerColor,
modifier = dividerModifier
)
SimpleTextField(
modifier = Modifier.weight(2f),
value = "",
placeHolder = textFieldPlaceholder,
keyboardType = KeyboardType.Phone,
singleLine = true
)
Text(
text = optionalTextTxt,
modifier = Modifier.weight(.5f, true),
style = MaterialTheme.typography.bodySmall,
fontSize = optionalTextFontSize.sp
)
}
no weight is issue to main row, then i will assign subrow weight to 0.5 and for textField weight will be 2f and last textview weight is issue to 0.5f
then Output will be looks like

Jetpack Compose How to Align text or content of Text component with specific size to bottom left or right?

How can i align text to bottom section of a Text component with Jetpack Compose? TextAlign only has Start, End, Left, Center, Right and Justify options.
Text(
text = "First",
textAlign = TextAlign.Start,
modifier = Modifier
.background(Color(0xFF1976D2))
.size(200.dp),
color = Color.White,
)
I want to align Text component's content, each Text has a specific size using modifier.size(x), to align their text to bottom. In the image blue rectangles are Text with different sizes should align the text inside them like in classic Android done with android:gravity.
It is similar to textAlign = TextAlign.x but for bottom.
Setting alignment from a Box aligns Text inside Box or Modifier.align(Alignment.BottomEnd) in BoxScope does what android:layout_gravity does for views, aligns the Text component, not the content of Text component, you can see the difference here.
Code for the blue rectangles in the image is
#Composable
fun BoxExample() {
Box(
modifier = Modifier
.fillMaxWidth()
.height(250.dp)
.background(Color.LightGray)
) {
// This is the one at the bottom
Text(
text = "First",
modifier = Modifier
.background(Color(0xFF1976D2))
.size(200.dp),
color = Color.White,
)
// This is the one in the middle
Text(
text = "Second",
modifier = Modifier
.background(Color(0xFF2196F3))
.size(150.dp),
color = Color.White
)
// This is the one on top
Text(
text = "Third ",
modifier = Modifier
.background(Color(0xFF64B5F6))
.size(100.dp),
color = Color.White
)
}
}
For orange rectangles
#Composable
fun BoxShadowAndAlignmentExample() {
Box(
modifier = Modifier
.fillMaxWidth()
.height(250.dp)
.background(Color.LightGray)
.padding(8.dp)
) {
Box(
modifier = Modifier.shadow(
elevation = 4.dp,
shape = RoundedCornerShape(8.dp)
)
) {
// This is the one at the bottom
Text(
text = "First",
modifier = Modifier
.background(Color(0xFFFFA000))
.size(200.dp),
color = Color.White
)
}
Box(
modifier = Modifier.shadow(
elevation = 4.dp,
shape = RoundedCornerShape(8.dp)
)
.align(Alignment.TopEnd)
) {
// This is the one in the middle
Text(
text = "Second",
modifier = Modifier
.background(Color(0xFFFFC107))
.size(150.dp),
color = Color.White
)
}
val modifier = Modifier.shadow(
elevation = 4.dp,
shape = RoundedCornerShape(8.dp)
)
.align(Alignment.BottomStart)
Box(
modifier = modifier
) {
// This is the one on top
Text(
text = "Third ",
modifier = Modifier
.background(Color(0xFFFFD54F))
.size(100.dp),
color = Color.White
)
}
}
}
Using align modifier you can align child components in specific positions relative to their parents:
Box(modifier = Modifier.fillMaxSize()) {
Text(modifier = Modifier.align(Alignment.BottomEnd),text = "Aligned to bottom end")
Text(modifier = Modifier.align(Alignment.BottomStart),text = "Aligned to bottom start")
Text(modifier = Modifier.align(Alignment.CenterStart),text = "Aligned to start center ")
Text(modifier = Modifier.align(Alignment.TopCenter),text = "Aligned to top center ")
}
Say your component is a Box, place your text within the Box like this:
Box(
modifier = Modifier.fillMaxSize(),
Alignment.BottomStart
) {
Text(
"First",
Modifier.padding(16.dp),
)
}
Basically, you define the section of the component that you want to use in that component, not in the text.
2 years later and I think I have the actual solution for this issue:
for your text composable, add this modifier:
.wrapContentHeight() and for the parameter, you may for example align it to the bottom with Alignment.Bottom
you can try with:
Text(
modifier = Modifier
.height(150.dp)
.wrapContentHeight(Alignment.Bottom)
,
text = "whooopla",
)

Categories

Resources