I want to make row like this
Expected Output
AND
I tried this piece of code
DividerWithItem
#Composable
fun DividerWithItem(
modifier: Modifier = Modifier,
index: () -> Int,
itemName: String,
lastIndex: () -> Int,
moreRowContent: #Composable RowScope.() -> Unit,
) {
Column {
if (index() == 0) {
Divider(color = Cloudy, thickness = dimensionResource(R.dimen.separator_height_width))
}
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = itemName,
modifier = Modifier.padding(vertical = 12.dp),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
moreRowContent()
}
if (index() <= lastIndex()) {
Divider(color = Cloudy, thickness = 1.dp)
}
}
}
BpmOptionsLabel
#OptIn(ExperimentalFoundationApi::class)
#Composable
fun LazyItemScope.BpmOptionsLabel(
index: () -> Int,
optionName: String,
lastIndex: () -> Int
) {
DividerWithItem(
modifier = Modifier
.fillMaxSize()
.animateItemPlacement(),
index = index,
itemName = optionName,
lastIndex = lastIndex
) {
Image(
modifier = Modifier.weight(.3f),
painter = painterResource(R.drawable.ic_menu),
contentDescription = null,
)
}
}
BpmOptionsLabelPreview
#Preview(showBackground = true)
#Composable
fun BpmOptionsLabelPreview() {
LazyColumn {
item {
BpmOptionsLabel(
index = { 0 },
"Item item item item item m 1",
lastIndex = { 1 }
)
}
}
}
Actual Output
Only problem is Text and Image item is not in proper place
In the DividerWithItem apply the weight(1f) to the Text
Text(
text = itemName,
modifier = Modifier.padding(vertical = 12.dp).weight(1f),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
moreRowContent()
and in the LazyItemScope.BpmOptionsLabel remove the weight modifier from the Image:
Image(
//modifier = Modifier.weight(.3f),
painter = painterResource(R.drawable.ic_menu_gallery),
contentDescription = null
)
If you want to increase the space occupied by the Image, use a padding modifier:
Image(
//...
modifier = Modifier.padding(horizontal = 20.dp)
)
You can try this, just add weight to your text, and the image will follow the remaining size that it needs depending on its size.
#Preview(showBackground = true)
#Composable
fun TextOverflow(){
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp)
) {
Text(
modifier = Modifier.weight(1f),
text = "This is should be long-long text that ever you see today, I even don't know how far this text will be ended, so just enjoy reading while you code.",
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Icon(imageVector = Icons.Rounded.MoreHoriz, contentDescription = "")
}
}
The Result:
Related
I am using IconButton in my project. I am opening Dialog when I am click on IconButton. Whenever I clicked on IconButton the dialog opens and IconButton little bit shifts other side and back to position when Dialog close. You can see in this video. So what is the problem of this? I want to avoid this..
OptionItemStateFul
#Composable
fun OptionItemStateFul() {
val isOptionItemClickAction = remember { mutableStateOf(false) }
OptionItemStateLess(isOptionItemClickAction) {
DialogOptionsView(
openDialogCustom = isOptionItemClickAction,
dialogOptionsData = DialogOptionsData(headerText = "Hi There", itemsList = listOf(1, 2)),
)
}
}
OptionItemStateLess
#Composable
fun OptionItemStateLess(
isOptionItemClickAction: MutableState<Boolean>,
onOptionItemClickAction: #Composable () -> Unit,
) {
Row(
modifier = Modifier.fillMaxSize(),
horizontalArrangement = Arrangement.spacedBy(5.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = " itemName 1 item 2",
modifier = Modifier
.padding(vertical = 13.dp)
.weight(1f),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
OptionItemLabel(isOptionItemClickAction, onOptionItemClickAction)
}
}
OptionItemLabel
#Composable
fun OptionItemLabel(
isOptionItemClickAction: MutableState<Boolean>,
onOptionItemClickAction: #Composable () -> Unit
) {
IconButton(
modifier = Modifier.padding(end = 10.dp),
onClick = { isOptionItemClickAction.value = true }
) {
Icon(
painter = painterResource(R.drawable.ic_menu),
contentDescription = null,
tint = Aqua,
)
}
AnimatedVisibility(isOptionItemClickAction.value) {
onOptionItemClickAction()
}
}
PreviewOptionItemStateFul
#Preview(showBackground = true)
#Composable
fun PreviewOptionItemStateFul() {
OptionItemStateFul()
}
Thanks
This change is coming because the dialog is in the row, so the dialog should be kept outside the row. Like this
updated - OptionItemStateLess
#Composable
fun OptionItemStateLess(
isOptionItemClickAction: MutableState<Boolean>,
onOptionItemClickAction: #Composable () -> Unit,
) {
Row(
modifier = Modifier.fillMaxSize(),
horizontalArrangement = Arrangement.spacedBy(5.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = " itemName 1 item 2",
modifier = Modifier
.padding(vertical = 13.dp)
.weight(1f),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
OptionItemLabel(isOptionItemClickAction)
}
AnimatedVisibility(isOptionItemClickAction.value) {
onOptionItemClickAction()
}
}
updated - OptionItemLabel
#Composable
fun OptionItemLabel(
isOptionItemClickAction: MutableState<Boolean>
) {
IconButton(
modifier = Modifier.padding(end = 10.dp),
onClick = { isOptionItemClickAction.value = true }
) {
Icon(
Icons.Default.Menu,
contentDescription = null,
)
}
}
ISSUE
I am trying to align the text Enable Notifications and the switch on the same line. If I were to use XML layouts, then I would have gone with constraint layout, but I am trying to figure out how to do this with Compose.
SET UP
#Composable
fun Settings() {
val viewModel: SettingsViewModel = viewModel()
val uiState: SettingsState = viewModel.uiState.collectAsState().value
SettingsList(uiState = uiState, viewModel)
}
#Composable
fun SettingsList(uiState: SettingsState, viewModel: SettingsViewModel, modifier: Modifier = Modifier) {
val scaffoldState = rememberScaffoldState()
Scaffold(
modifier = Modifier, scaffoldState = scaffoldState,
backgroundColor = MaterialTheme.colors.background,
topBar = {
TopAppBar(
modifier = Modifier.semantics {
heading()
},
backgroundColor = MaterialTheme.colors.surface,
contentPadding = PaddingValues(start = 12.dp)
) {
Icon(
tint = MaterialTheme.colors.onSurface,
imageVector = Icons.Default.ArrowBack,
contentDescription = stringResource(id = R.string.header_settings_back)
)
Spacer(modifier = modifier.width(16.dp))
Text(
text = stringResource(id = R.string.header_settings),
fontSize = 18.sp,
color = MaterialTheme.colors.onSurface
)
}
}
) {
val scrollState = rememberScrollState()
Column(
modifier = Modifier
.verticalScroll(scrollState).padding(paddingValues = it)
) {
NotificationSettingsComposable(uiState.notificationsEnabled, {
viewModel.toggleNotificationSettings()
}, Modifier.fillMaxWidth())
}
}
}
#Composable
fun NotificationSettingsComposable(checked: Boolean, onCheckedChanged: (Boolean) -> Unit, modifier: Modifier = Modifier) {
Surface(modifier = modifier.padding(12.dp)) {
Row {
Text(
text = stringResource(id = R.string.body_enable_notifications), fontSize = 16.sp,
color = MaterialTheme.colors.onSurface,
)
Switch(checked = checked, onCheckedChange = onCheckedChanged, modifier = Modifier.align(Alignment.Top))
}
}
}
However the text and the switch are not being aligned on the same line.
DESIRED OUTCOME
The text and the switch should be aligned on the same line as shown in this picture.
This is what I am getting
What I want to achieve
Add verticalAlignment = Alignment.CenterVertically in the Row:
Something like:
Row(
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Enable Notification", fontSize = 16.sp,
)
Switch(
checked = checkedState.value,
onCheckedChange = { checkedState.value = it }
)
}
I have a LazyVerticalGrid with two columns, What I want is to make all my items to have the same size. The widths of all the items take the same size which is good but I cannot make the height of the items to be the same (take all the available height). Here is my code.
LazyVerticalGrid(
columns = GridCells.Fixed(2),
modifier = modifier,
contentPadding = PaddingValues(dimensionResource(id = R.dimen.margin_large)),
verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.margin_medium)),
horizontalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.margin_large))
) {
items(days,
key = { day -> day.startDate }) { day ->
DayItem(
day = day,
modifier = Modifier,
onClicked = { onItemClicked(day.startDate) }
)
}
}
#Composable
fun DayItem(
day: Day, modifier: Modifier = Modifier, onClicked: () -> Unit = {}
) {
ElevatedCard(
onClick = onClicked, modifier = modifier
) {
if (day.actions.isEmpty())
NewDayComponent()
else
Column(
modifier = modifier.padding(dimensionResource(id = R.dimen.margin_large)),
verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.margin_medium))
) {
day.feelingDuration.keys.toList().forEach { item ->
Row(
modifier = Modifier,
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.margin_small))
) {
Box(
modifier = modifier
.size(
dimensionResource(id = R.dimen.icon_width),
dimensionResource(id = R.dimen.icon_height)
)
.clip(RoundedCornerShape(dimensionResource(id = R.dimen.margin_small)))
.background(FeelingTypeColors[item] ?: Color.Gray)
)
Text(
text = day.getTypeDurationLabel(LocalContext.current, item)
)
}
}
}
}
}
#Composable
fun NewDayComponent(modifier: Modifier = Modifier, onClicked: () -> Unit = {}) {
Box(modifier = modifier.fillMaxSize()
.clickable { onClicked() }
.background(MaterialTheme.colorScheme.primary)) {
Image(
imageVector = Icons.Filled.Add,
contentDescription = "Add Actions",
modifier = Modifier.align(Alignment.Center)
)
}
}
So the NewDayComponent needs to take the same max height which it does not. I tried setting IntrinsicHeight to the Card but it did not fix the issue.
In Jetpack Compose, How to find the width of each child of a Row?
I tried using onGloballyPositioned like below, but the size is wrong.
Also, not sure if this is the optimal one.
I need the width of each child and further processing will be done based on that, the below is a simplified code for example.
#Composable
fun MyTabSample() {
MyCustomRow(
items = listOf("Option 1 with long", "Option 2 with", "Option 3"),
)
}
#Composable
fun MyCustomRow(
modifier: Modifier = Modifier,
items: List<String>,
) {
val childrenWidths = remember {
mutableStateListOf<Int>()
}
Box(
modifier = modifier
.background(Color.LightGray)
.height(IntrinsicSize.Min),
) {
// To add more box here
Box(
modifier = Modifier
.widthIn(
min = 64.dp,
)
.fillMaxHeight()
.width(childrenWidths.getOrNull(0)?.dp ?: 64.dp)
.background(
color = DarkGray,
),
)
Row(
horizontalArrangement = Arrangement.Center,
) {
items.mapIndexed { index, text ->
Text(
modifier = Modifier
.widthIn(min = 64.dp)
.padding(
vertical = 8.dp,
horizontal = 12.dp,
)
.onGloballyPositioned {
childrenWidths.add(index, it.size.width)
},
text = text,
color = Black,
textAlign = TextAlign.Center,
)
}
}
}
}
The size you get from Modifier.onSizeChanged or Modifier.onGloballyPositioned is in px with unit Int, not in dp. You should convert them to dp with
val density = LocalDensity.current
density.run{it.size.width.toDp()}
Full code
#Composable
fun MyCustomRow(
modifier: Modifier = Modifier,
items: List<String>,
) {
val childrenWidths = remember {
mutableStateListOf<Dp>()
}
Box(
modifier = modifier
.background(Color.LightGray)
.height(IntrinsicSize.Min),
) {
val density = LocalDensity.current
// To add more box here
Box(
modifier = Modifier
.widthIn(
min = 64.dp,
)
.fillMaxHeight()
.width(childrenWidths.getOrNull(0) ?: 64.dp)
.background(
color = DarkGray,
),
)
Row(
horizontalArrangement = Arrangement.Center,
) {
items.mapIndexed { index, text ->
Text(
modifier = Modifier
.onGloballyPositioned {
childrenWidths.add(index, density.run { it.size.width.toDp() })
}
.widthIn(min = 64.dp)
.padding(
vertical = 8.dp,
horizontal = 12.dp,
),
text = text,
color = Black,
textAlign = TextAlign.Center,
)
}
}
}
}
However this is not the optimal way to get size since it requires at least one more recomposition. Optimal way for getting size is using SubcomposeLayout as in this answer
How to get exact size without recomposition?
TabRow also uses SubcomposeLayout for getting indicator and divider widths
#Composable
fun TabRow(
selectedTabIndex: Int,
modifier: Modifier = Modifier,
containerColor: Color = TabRowDefaults.containerColor,
contentColor: Color = TabRowDefaults.contentColor,
indicator: #Composable (tabPositions: List<TabPosition>) -> Unit = #Composable { tabPositions ->
TabRowDefaults.Indicator(
Modifier.tabIndicatorOffset(tabPositions[selectedTabIndex])
)
},
divider: #Composable () -> Unit = #Composable {
Divider()
},
tabs: #Composable () -> Unit
) {
Surface(
modifier = modifier.selectableGroup(),
color = containerColor,
contentColor = contentColor
) {
SubcomposeLayout(Modifier.fillMaxWidth()) { constraints ->
val tabRowWidth = constraints.maxWidth
val tabMeasurables = subcompose(TabSlots.Tabs, tabs)
val tabCount = tabMeasurables.size
val tabWidth = (tabRowWidth / tabCount)
val tabRowHeight = tabMeasurables.fold(initial = 0) { max, curr ->
maxOf(curr.maxIntrinsicHeight(tabWidth), max)
}
val tabPlaceables = tabMeasurables.map {
it.measure(
constraints.copy(
minWidth = tabWidth,
maxWidth = tabWidth,
minHeight = tabRowHeight
)
)
}
val tabPositions = List(tabCount) { index ->
TabPosition(tabWidth.toDp() * index, tabWidth.toDp())
}
layout(tabRowWidth, tabRowHeight) {
tabPlaceables.forEachIndexed { index, placeable ->
placeable.placeRelative(index * tabWidth, 0)
}
subcompose(TabSlots.Divider, divider).forEach {
val placeable = it.measure(constraints.copy(minHeight = 0))
placeable.placeRelative(0, tabRowHeight - placeable.height)
}
subcompose(TabSlots.Indicator) {
indicator(tabPositions)
}.forEach {
it.measure(Constraints.fixed(tabRowWidth, tabRowHeight)).placeRelative(0, 0)
}
}
}
}
}
So I've been struggling with the following problem for a while. I achieved this (John's example):
But what I'm trying to do is to force the hour to always be shown directly after the text, and if the text is too long - overflow the text. So Jane Doe's example is perfect, same as Jack Doe's (but in Jack's case that's all a dummy text).
And I can't really figure out what I'm doing wrong.
That's the piece of code I wrote:
Row(modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 10.dp, vertical = 7.dp),
verticalAlignment = Alignment.CenterVertically
) {
Column {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
// there's another Row printing the name
}
Spacer(Modifier.height(5.dp))
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Start) {
Row(modifier = Modifier.wrapContentWidth(), horizontalArrangement = Arrangement.Start) {
Text(
text = item.message,
style = MaterialTheme.typography.subtitle1,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Start) {
Circle() // that's my function which just shows the circle
Text(
text = dateString,
style = MaterialTheme.typography.subtitle1,
maxLines = 1,
modifier = Modifier.padding(horizontal = 4.dp)
)
}
}
}
}
I'll be really grateful for any kind of help.
Something like this?
#Preview(showBackground = true)
#Composable
fun Test() {
Column {
Message(message = "short message")
Message(message = "short")
Message(message = "very long message")
}
}
#Composable
fun Message(message: String) {
Text("John Doe")
Row(horizontalArrangement = Arrangement.SpaceBetween) {
Text(
message,
modifier = Modifier.weight(1F, fill = false),
overflow = TextOverflow.Ellipsis,
maxLines = 1,
)
Text("8:35PM")
}
}
You can achieve this with a Layout and using IntrinsicWidth. You can build on this example as it only uses 2 Texts for the children, but this should get you on the right path.
#Composable
fun MyRow(
modifier: Modifier = Modifier,
content: #Composable () -> Unit,
) {
Layout(
content = content,
modifier = modifier,
) { measurables, constraints ->
check(measurables.size == 2) { "This composable requires 2 children" }
val first = measurables.first()
val second = measurables[1]
val looseConstraints = constraints.copy(
minWidth = 0,
minHeight = 0,
)
val secondMeasurable = second.measure(looseConstraints)
val maxHeight = secondMeasurable.height
val availableWidth = constraints.maxWidth - secondMeasurable.width
val maxWidth = first.maxIntrinsicWidth(maxHeight).coerceAtMost(availableWidth)
val firstMeasurable = first.measure(
Constraints(
minWidth = maxWidth,
maxWidth = maxWidth,
minHeight = 0,
maxHeight = maxHeight
)
)
layout(
constraints.maxWidth,
maxHeight,
) {
firstMeasurable.place(0, 0)
secondMeasurable.place(maxWidth, 0)
}
}
}
#Composable
#Preview
fun MyRowPreview() {
SampleTheme {
Surface(modifier = Modifier.width(320.dp)) {
Column(modifier = Modifier.fillMaxWidth()) {
Text(
text = "John Doe",
style = MaterialTheme.typography.h5,
)
MyRow(modifier = Modifier.fillMaxWidth()) {
Text(
text = "Short Label",
style = MaterialTheme.typography.body1,
modifier = Modifier.padding(end = 8.dp),
overflow = TextOverflow.Ellipsis,
maxLines = 1,
)
Text(
text = "8:35 PM",
style = MaterialTheme.typography.body1,
modifier = Modifier.padding(start = 8.dp),
)
}
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "John Doe",
style = MaterialTheme.typography.h5,
)
MyRow(modifier = Modifier.fillMaxWidth()) {
Text(
text = "A long label that will require truncation goes here",
style = MaterialTheme.typography.body1,
modifier = Modifier.padding(end = 8.dp),
overflow = TextOverflow.Ellipsis,
maxLines = 1,
)
Text(
text = "8:35 PM",
style = MaterialTheme.typography.body1,
modifier = Modifier.padding(start = 8.dp),
)
}
}
}
}
}