Centering Text in Jetpack Compose - android

I am trying to center the content inside a Text of Jetpack Compose, but I'm not succeeding. The code seems fine to me (I'm very new to Compose though). The code:
Row(
modifier = Modifier
.padding(8.dp)
.height(30.dp)
.fillMaxHeight(),
) {
IconButton(..) {..}
Spacer(modifier = Modifier.width(4.dp))
Text(
text = "Some text",
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxHeight()
.border(1.dp, Color.Black, RoundedCornerShape(20))
.width(120.dp)
.background(color = Color.White, RoundedCornerShape(20))
.align(Alignment.CenterVertically)
)
Spacer(modifier = Modifier.width(4.dp))
IconButton(..) {..}
}
This yields the following result:

Your Text takes full height according to your modifier, in this case .align(Alignment.Center) won't help you.
You can place your Text inside a Box and center it there:
Row(
modifier = Modifier
.padding(8.dp)
.height(30.dp)
.fillMaxHeight()
) {
IconButton({ }) { Text("hello ") }
Spacer(modifier = Modifier.width(4.dp))
Box(
Modifier
.fillMaxHeight()
.border(1.dp, Color.Black, RoundedCornerShape(20))
.width(120.dp)
.background(color = Color.White, RoundedCornerShape(20))
) {
Text(
text = "Some text",
textAlign = TextAlign.Center,
modifier = Modifier
.align(Alignment.Center)
)
}
Spacer(modifier = Modifier.width(4.dp))
IconButton({ }) { Text("hello ") }
}
You can solve this with adding a padding to your text, but in this case you can't specify Row height explicitly, and need to let it wrap content size:
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.padding(8.dp)
) {
IconButton({ }) { Text("hello ") }
Spacer(modifier = Modifier.width(4.dp))
Text(
text = "Some text",
textAlign = TextAlign.Center,
modifier = Modifier
.border(1.dp, Color.Black, RoundedCornerShape(20))
.width(120.dp)
.background(color = Color.White, RoundedCornerShape(20))
.padding(vertical = 10.dp)
)
Spacer(modifier = Modifier.width(4.dp))
IconButton({ }) { Text("hello ") }
}

Putting it on a column and centering both ways can also work:
Column(
modifier = Modifier
.fillMaxHeight()
.border(1.dp, Color.Black, RoundedCornerShape(20))
.width(120.dp)
.background(color = Color.White, RoundedCornerShape(20)),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Some text")
}

First of all, Compose is different than Android Views in many ways. There is no 'C' in 'View', but 'Composable' STARTS with a 'C'. Ok, so now that the basics are clear, you see the behaviour that you are observing is indeed the expected one. Here's why
Composables, just like views, have bounds (the four corner points separating it from the rest of the environment), which we can modify using Modifiers. Composables including text to be rendered, such as the Text and all the TextFields follow the same basic principle. If I have made my text very large (say a quarter to the maxHeight), then I must have done so because I assume my text value to be placed in is large, or else there is no point of doing that, right? Hence, when you create a Text with dimensions larger than required by the value, it should be treated as a Composable 'ready' to hold the maximum capacity it is composed to.
Ok so if you were to change your text value in such a way so as to center the text, what would you do? You would add Line-Breaks (Press Enter Multiple Times) until the text looks vertically in the middle, but that's just lame. You did not need to specify bounds so large if your value was smaller than would fill the Comp. Let's say a person can modify this value. Every time the value changes, the space from the edges needs to be calculated based on the length of the string + font size + font family + font style. If there is a faster approach, why would a person not take that? You are provided with a golden Modifier called as wrapContentHeight. Now, this will wrap the Composable itself around the text (asynchronously), limiting the amount of space to the required. Ok now that the bounds are set, you can position the Composable literally anywhere you wish to on the screen by specifying the co-ordinates. For the desired behaviour, there's the handy verticalAlignemnt parameter provided with some Composables, or something to that effect, which will automatically and asynchronously do the job for you.
You may as well ask why the horizontal alignment is provided despite having the same effect, I think it is because of the convention that people had been used to since the early typewriting days. The vertical one might be inefficient from a memory and/or performance perspective.
However, if for some bizarre reason you are sworn not to use external Composables for content Alignment, then I guess it is not such a big deal to implement your own version of the aligner. It is just that vertical space is divided into 'lines' of fixed width, and at any given time, a text value can be displayed only on a specific line. Hence, if the height of the Composable is not perfectly an integral multiple of the line-height, it would be tricky to place the block (actually it would be impossible using the pre-built stuff). In your case for example, the Composable does not seem out two line-heights. Now to center, you would have to place it at 1.5 the line height which is something Text cannot achieve, so the line break implementation would fail here. Also the alignment will not be perfect if the height is an even multiple of the line-height.
It's just messed up.

Related

Jetpack Compose - Setting dimensions for Accompanist placeholder

The library I'm using: "com.google.accompanist:accompanist-placeholder-material:0.23.1"
I want to display a placeholder in the place of (or over) a component when it's in the loading state.
I do the following for a Text:
MaterialTheme() {
var placeholderVisible by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
while (true) {
delay(1000)
placeholderVisible = !placeholderVisible
}
}
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Box(
modifier = Modifier
.border(1.dp, Color.Red)
.padding(16.dp)
) {
Text(
modifier = Modifier
.then(
if (placeholderVisible) {
Modifier.height(28.dp).width(62.dp)
} else {
Modifier
}
)
.placeholder(
visible = placeholderVisible,
highlight = PlaceholderHighlight.shimmer()
),
text = if (placeholderVisible) "" else "Hello"
)
}
}
}
And I get this:
I want instead that no matter how big I set the placeholder's height or width, it will not participate in any way in the measuring process and, if I want to, to be able to draw itself even over other components (in this case let's say the red border).
As an effect of what I want, the box with red border will always have the dimension as if that Modifier.height(28.dp).width(62.dp) is not there.
I know I can draw outside a component's borders using drawWithContent, specifying the size of a rectangle or a circle (or whatever) to be component's size + x.dp.toPx() (or something like that). But how do I do this with Modifier.placeholder?
Ideally, I would need something like Modifier.placeholder(height = 28.dp, width = 62.dp)
So, with or without this ideal Modifier, the UI should never change (except, of course, the shimmer box that may be present or not).
I think I can pull this off by modifying the source code of this Modifier, but I hope I won't need to turn to that.
Just replace your Text() with below code, maybe conditional Modifier is the issue in above code!
Text(
modifier = Modifier
.size(width = 62.dp, height = 28.dp)
.placeholder(
visible = placeholderVisible,
highlight = PlaceholderHighlight.shimmer()
),
text = if (placeholderVisible) "" else "Hello",
textAlign = TextAlign.Center
)

Box doesn't fill max size - Jetpack Compose

My PersonalDataBox() doesn't fill max available space even if there is .fillMaxSize()
There is a empty space between my box and the bottom of the screen. I want to fill my whole screen with Box(). It looks like my box has .fillWrapHeight(). Parent of the box is also .fillMaxSize() and it works correctly - fills max height.
#Composable
private fun PersonalDataBox(user: User) {
Box(
modifier = Modifier
.fillMaxSize()
.padding(vertical = 10.dp)
.clip(RoundedCornerShape(topStart = 10.dp, topEnd = 10.dp))
.background(Color.Red)
.padding(horizontal = 25.dp, vertical = 15.dp)
) {
Column(modifier = Modifier.fillMaxSize()) {
Text(
text = stringResource(R.string.personal_data),
modifier = Modifier
.fillMaxWidth()
.padding(top = 10.dp),
fontSize = 16.sp,
fontWeight = FontWeight.SemiBold,
color = MaterialTheme.colors.primary
)
listOf(
Pair(stringResource(R.string.name), user.name),
Pair(stringResource(R.string.surname), user.surname),
Pair(stringResource(R.string.e_mail), user.email),
).forEach {
InputField(it)
}
}
}
}
I pulled down your code and it actually renders in the whole screen just fine--so I'm assuming you have your PersonalDatabox in a compose view with infinite height such as a LazyColumn.
Check out the documentation about constraints here
Specifically:
Most commonly, when measured with unbounded Constraints, these children will fallback to size themselves to wrap their content, instead of expanding to fill the available space (this is not always true as it depends on the child layout model, but is a common behavior for core layout components).
In other words, if the constraint is infinite, then .fillMaxSize will default to wrapping content.
To get around this, you can make the constraint equal to the height of the Lazy Column by using fillParentMaxHeight.
This only works if you're using a LazyColumn, though. Otherwise, if you set the height specifically or measure the screen you can accomplish the same thing.
Here's an example of what that might look like.
LazyColumn() {
item {
Column(modifier = Modifier.fillParentMaxHeight(1f)) {
PersonalDataBox(User(name = "John", surname = "Robless", "coolemail#email.com"))
}
}
}
Your Box has the following modifiers:
modifier = Modifier
.fillMaxSize()
.padding(vertical = 10.dp)
.clip(RoundedCornerShape(topStart = 10.dp, topEnd = 10.dp))
.background(Color.Red)
.padding(horizontal = 25.dp, vertical = 15.dp)
The important ones are
.padding(vertical = 10.dp)
.padding(horizontal = 25.dp, vertical = 15.dp)
There are two padding modifiers adding a total of 25.dp padding to the top and bottom of your Box, remove these and it should fix your issue

What is the difference between `fillMaxSize()` and `matchParentSize()` in a component inside a Box in Jetpack Compose?

I'm creating a component in Jetpack Compose and realized that when I'm making a Composable inside a Box it's possible that this component assumes 2 maximum fill possibilities: Modifier.fillMaxSize() and Modifier.matchParentSize(). As shown below:
Box(
modifier = modifier // This modifier is received by parameter of another composable function
) {
Canvas(modifier = Modifier.matchParentSize()) {
// Using match parent size
}
}
and
Box(
modifier = modifier // This modifier is received by parameter of another composable function
) {
Canvas(modifier = Modifier.fillMaxSize()) {
// Using fill max size
}
}
What is the practical difference between these 2 modes? And why can't I set Modifier.matchParentSize() to a Column or a Row?
From official doc
Modifier.fillMaxSize modifier, which makes an element occupy all available space, will take part in defining the size of the Box.
So it specifies the size of the element.
But if you use Modifier.matchParentSize() in an element inside of a box it has nothing to do with specifying the size of the box.
The size of the box will be measured by other children element of the box. Then the element with the Modifier.matchParentSize() will match and occupy that size.
You can't use .matchParentSize() in Row or Column because this modifier is part of the BoxScope interface. So it works only with boxscope.
For example, if you use fillMaxSize with something like the below.
Box() {
Text(
modifier = Modifier
.fillMaxSize()
.background(Color.Green),
text = ""
)
Text(
modifier = Modifier
.size(100.dp)
.background(Color.Blue),
text = "Hello",
)
}
You will get this. It will fill the entire screen because of that .fillMaxSize() modifier in the first child.
But if you use this
Box() {
Text(
modifier = Modifier
.matchParentSize()
.background(Green),
text = ""
)
Text(
modifier = Modifier
.size(100.dp),
text = "Hello",
)
}
It will take only 100.dp for the Hello text and then the green background will fill that 100.dp because of that .matchParentSize() modifier in the first child.
I could have used Box instead of Text but more Box can make it confused.

Always display placeholder and RTL input in TextField with Jetpack Compose

I want the placeholder not to disappear if I start typing in a field and it should be on the left side of the screen.
But the text input and cursor must be on the right.
An example is in the screenshot. Thanks!
Not the best solution, but it works.
Customizing the placeholder as per the requirement is not possible at this point in time. You have to create a completely customized TextField if that is an absolute requirement.
Note.
This is not a placeholder.
The positioning of the text is absolute, it should be adjusted according to the TextField size.
#Composable
fun FixedPlaceholder() {
var name by remember { mutableStateOf("") }
Box {
OutlinedTextField(
shape = MaterialTheme.shapes.medium,
value = name,
onValueChange = {
name = it
},
singleLine = true,
textStyle = LocalTextStyle.current.copy(
textAlign = TextAlign.End,
),
modifier = Modifier
.fillMaxWidth()
.padding(
horizontal = 16.dp,
vertical = 8.dp,
),
)
Text(
text = "to",
modifier = Modifier
.fillMaxWidth()
.padding(
start = 32.dp,
end = 32.dp,
top = 24.dp,
bottom = 8.dp
),
)
}
}
You don't need the placeholder, just set the leadingIcon property to:
leadingIcon={Text(text="To")}
In Compose, leadingIcon is a composable, and you can set it to anything you like.

Modifier .weight() not setted automatically in column and can't add

i am new to jetpack compose and also have some experience with flutter so when move to compose its a little bit similar to flutter declarative UI style but i don't quite understand
like in flutter when i want some widget to expanded and want it to take full available space we can use Exapanded widget and if i use this in Column its automatically add flex of 1 or weight in terms of jetpack compose so i want to do same thing in jetpack compose but its seems like jetpack column not automatically and weight to its children and also i cant add
Modifier.weight(1f) in Card of NetworkCardComposable so i have to pass Modifier from as Parameter and in when using this Compoasable in columns this Modifier.weight(1f) have to set through parameter which is odd i think.
#Preview(showBackground = true, widthDp = 500, heightDp = 200)
#Composable
fun NetworkCardComposable(modifier: Modifier = Modifier) {
Card(
modifier = modifier
.fillMaxSize(1f)
.padding(16.dp),
shape = RoundedCornerShape(16.dp),
backgroundColor = colorResource(id = R.color.jazz_color_primary)
) {
Row {
Card(
modifier = Modifier
.weight(2f)
.fillMaxSize()
.padding(8.dp),
shape = RoundedCornerShape(16.dp),
) {
Image(
modifier = Modifier
.padding(8.dp),
painter = painterResource(R.drawable.jazz_logo),
contentDescription = null,
)
}
Row(
modifier = Modifier
.weight(2f)
.fillMaxSize(),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
modifier = Modifier
.weight(1f)
.fillMaxWidth(),
text = "Bundles & Offers",
color = Color.White,
textAlign = TextAlign.Center,
fontSize = MaterialTheme.typography.h5.fontSize,
)
Icon(
modifier = Modifier.size(56.dp),
imageVector = Icons.Default.ChevronRight,
tint = Color.White,
contentDescription = null,
)
}
}
}
}
#Preview(showBackground = true, showSystemUi = true)
#Composable
fun networkCard() {
Column(Modifier.fillMaxSize()) {
NetworkCardComposable(Modifier.weight(1f))
NetworkCardComposable(Modifier.weight(1f))
NetworkCardComposable(Modifier.weight(1f))
NetworkCardComposable(Modifier.weight(1f))
}
}
all thought i have acheive my desired layout but if there is any better approach to this please answer
Jetpack Compose aims at providing the maximum control over the UI. Hence, by default, the elements of a container like column default to wrap content. If you want them to occupy Max available space of the container, the proper way to do it is using Modifier.fillMaxSize()
As far as weights are concerned, if you do not specify then explicitly while adding more than one elements inside a container, the weights are calculated accordingly, based on the minimum required dimensions, (i.e., again, wrapContentSize ())

Categories

Resources