I want to removed side padding of particular child item in LazyColum. I solved this problem in xml with the help of this post. I have same scenario in the jetpack compose. I am using BOM versions of compose_bom = "2022.11.00" with Material 3.
Card(shape = RoundedCornerShape(6.dp),) {
Column(modifier.background(Color.White)) {
LazyColumn(
contentPadding = PaddingValues(all =16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
item {
Text(
text = "Device Header",
modifier = Modifier.padding(top = 10.dp),
style = headerTextStyle
)
}
item {
Divider() // remove padding from side in here
}
}
}
}
Actual Output
Expected Output
In Compose you can't use a negative padding in the children to reduce the padding applied by the parent container. You can use offset modifer with a negative value but it will shift the Divider on the left side.
You can use a layout modifier to apply an horizontal offset increasing the width.
Something like:
LazyColumn(
Modifier.background(Yellow),
contentPadding = PaddingValues(all = 16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
//...
item {
val sidePadding = (-8).dp
Divider(modifier = Modifier
.layout { measurable, constraints ->
// Measure the composable adding the side padding*2 (left+right)
val placeable =
measurable.measure(constraints.offset(horizontal = -sidePadding.roundToPx() * 2))
//increase the width adding the side padding*2
layout(
placeable.width + sidePadding.roundToPx() * 2,
placeable.height
) {
// Where the composable gets placed
placeable.place(+sidePadding.roundToPx(), 0)
}
}
)
}
}
Here you can find the output with a Divider() without modifier, and the Divider with the layout modifier.
Related
I am trying to write a custom VerticalArragment for a lazyVerticalGrid where:
items in the first line get a top padding of 10.dp
items in the seconds line get a top padding of 20.dp
and the rest will get a top padding of 5.dp
object CustomVerticalArrangement : Arrangement.Vertical {
override fun Density.arrange(totalSize: Int, sizes: IntArray, outPositions: IntArray) {
var current = 0
sizes.forEachIndexed { index, it ->
when (abs(index / 3) {
0 -> outPositions[index] = current + 10
1 -> outPositions[index] = current + 20
else -> outPositions[index] = current + 5
}
current += it
}
}
}
I pass it over later to my LazyVerticalGrid
LazyVerticalGrid(
modifier = Modifier.fillMaxSize(),
columns = GridCells.Fixed(3),
contentPadding = PaddingValues(start = 70.dp, end = 70.dp, top = 75.dp, bottom = 40.dp),
// TODO: consider custom vertical arrangement
verticalArrangement = CustomVerticalArrangement,
horizontalArrangement = Arrangement.spacedBy(10.dp)
) { ... }
The code is never called I believe or it’s just that LazyGrid doesn’t consider it and use the default spacing 0.dp.
the debugger breakpoint never reached By the way
Am I doing something wrong or this is a bug?
It seems that this is intended! since the LazyGrid composable only uses the spacing field in Arrangement.Vertical which will be 0.dp by default in LazyGrid.kt.
val spaceBetweenSlotsDp = if (isVertical) {
horizontalArrangement?.spacing ?: 0.dp
} else {
verticalArrangement?.spacing ?: 0.dp
}
I am using meterial3 with Compose . I found that all the Scrollable Composable Showing overscroll effect in all 4 directions regardless of scrolling direction. that goes for Column, LazyColumn, LazyVerticalGrid etc ..
I m not using anything custom to override overscroll effect. i can not figure out whats causing this behavior . i will add an example code below with LazyVerticalGrid which is causing this behavior . Since this is vertical grid it should show over scroll only vertically(to and bottom) but it also show it horizontally (left and right)..
Any direction on this will be appreciated.
#Composable
#Destination
fun FavoritesScreen(navigator: DestinationsNavigator) {
val favoritesViewModel: FavoritesViewModel = hiltViewModel()
val favoritesLiveData =
favoritesViewModel.favoritesLiveData.observeAsState(initial = null)
if (favoritesLiveData.value != null) {
if (favoritesLiveData.value!!.isEmpty()) {
// Show empty view
} else {
LazyVerticalGrid(
contentPadding = PaddingValues(
12.dp,
12.dp,
12.dp,
20.dp
),
columns = GridCells.Fixed(2),
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
item(span = { GridItemSpan(2) }) {
Spacer(modifier = Modifier.windowInsetsTopHeight(WindowInsets.statusBars))
Text(
text = stringResource(id = R.string.favorites),
style = MaterialTheme.typography.headlineMedium,
modifier = Modifier.padding(top = 20.dp)
)
ColumnSpacer(value = 10)
}
items(favoritesLiveData.value!!) { game ->
// grid Item
}
}
}
}
}
You may need to update your Compose version. This was a known issue in Compose 1.3.0-alpha01, which was fixed subsequently.
I have a sequence of items I want to show as list of items in a Track. Only thing is, at the beginning, items have to be lined starting from middle of the track. Afterwards, the items can be scrolled normally.
e.g. at beginning :
MyTrack : [----blankSpace 50%-------[dynamic item1[[dynamic item2][dynamic item3]--]
after scrolling when all items are visible for example:
MyTrack[[item1][item2][item3][item4][item5]]
This Row has to be scrollable and each item could have varying width.
This is the item on the track:
data class MyItem(val widthFactor: Long, val color: Color)
Question : is there. a way to give start position of the items in the LazyRow ? Or is there a better Layout I should use in Jetpack Compose ?
A layout like LazyRow() won't work because there is no way to tell to start lining up items from middle of it.
I can use something like Canvas and drawRect of items in it but then I need to implement the swipe and scroll features like in LazyRow.
Thanks in advance.
You can use spacer item with .fillParentMaxWidth which is available for LazyList items:
LazyRow(
modifier = Modifier.fillMaxWidth(),
) {
item {
Spacer(modifier = Modifier.fillParentMaxWidth(0.5f))
}
// Your other items
}
This is better than the other two provided solutions - Configuration.screenWidthDp is only usable when your LazyRow fills whole screen width which is not always the case, BoxWithConstraints adds complexity that is not necessary here.
Use this to get screen width
val configuration = LocalConfiguration.current
val screenWidth = configuration.screenWidthDp.dp
Source - https://stackoverflow.com/a/68919901/9636037
Code
#Composable
fun ScrollableRowWithSpace() {
val configuration = LocalConfiguration.current
val screenWidth = configuration.screenWidthDp.dp
val list = Array(10) {
"Item ${it + 1}"
}
LazyRow(
modifier = Modifier
.background(LightGray)
.fillMaxWidth(),
) {
item {
Spacer(modifier = Modifier
.background(White)
.width(screenWidth / 2))
}
items(list) {
Text(
text = it,
modifier = Modifier
.padding(16.dp)
.background(White),
)
}
}
}
I would recommend following solution you can use it anywhere and get half of the width.
data class MyItem(val widthFactor: Long, val color: Color)
#Composable
fun LazyRowWithPadding() {
val myItemList = mutableListOf(
MyItem(12345, Color.Blue),
MyItem(12345, Color.Cyan),
MyItem(12345, Color.Green),
MyItem(12345, Color.Gray),
MyItem(12345, Color.Magenta),
)
BoxWithConstraints(Modifier.fillMaxSize()) {
LazyRow(
modifier = Modifier.fillMaxWidth(),
contentPadding = PaddingValues(start = maxWidth / 2)
) {
items(myItemList) {
Box(
Modifier
.padding(end = 8.dp)
.background(it.color)
.padding(24.dp)) {
Text(text = it.widthFactor.toString())
}
}
}
}
}
Example
In Compose, how do we get the position or size of a Composable in a screen ? For example, I'm trying to focus the map camera between specific bounds and adding padding. Here I need to get the padding corresponding to the pager top position and TopBar bottom position.
Currently the code of this screen is the following:
BoxWithConstraints {
MapViewWithMarkers(...)
TopAppBar(
modifier = Modifier
.fillMaxWidth()
.statusBarsPadding(),
backgroundColor = Color.Transparent,
elevation = 0.dp,
)
HorizontalPager(
state = pagerState,
modifier = Modifier
.align(Alignment.BottomCenter)
.fillMaxWidth()
.navigationBarsPadding()
.padding(bottom = 32.dp),
itemSpacing = 8.dp,
) { page ->
val hikeOnMapCard = hikeMarkerList[page]
HikeOnMapCard(hikeOnMapCard) {
viewModel.hikeOnMapCardClicked(hikeOnMapCard.id)
}
}
}
I would like to forward to the MapViewWithMarkers Composable the padding corresponding to the TopAppBar size and Pager size on this screen
Thanks !
To get the position and the size of a composable you can use the onGloballyPositioned modifier.
Something like:
var sizeTopBar by remember { mutableStateOf(IntSize.Zero) }
var positionInRootTopBar by remember { mutableStateOf(Offset.Zero) }
TopAppBar(
modifier = Modifier
.onGloballyPositioned { coordinates ->
// size
sizeTopBar = coordinates.size
// global position (local also available)
positionInRootTopBar = coordinates.positionInRoot()
}
//...
)
With complex layout to measure and layout multiple composables, use the Layout composable instead. This composable allows you to measure and lay out children manually.
In JetpackCompose, we can use LazyColumnFor as RecyclerView.
In RecyclerView, to have a proper margin/padding between items, we need to use ItemDecoration, as per this article
Like below
class MarginItemDecoration(private val spaceHeight: Int) : RecyclerView.ItemDecoration() {
override fun getItemOffsets(outRect: Rect, view: View,
parent: RecyclerView, state: RecyclerView.State) {
with(outRect) {
if (parent.getChildAdapterPosition(view) == 0) {
top = spaceHeight
}
left = spaceHeight
right = spaceHeight
bottom = spaceHeight
}
}
}
For JetpackCompose LazyColumnFor, what's the equivalent of ItemDecoration?
You can use the verticalArrangement parameter to add a spacing between each item using Arrangement.spacedBy().
Something like:
LazyColumn(
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
// ...
}
The example below adds 8.dp of space in-between each item
Before and after:
If you want to add padding around the edges of the content you can use the contentPadding parameter.
LazyColumn(
verticalArrangement = Arrangement.spacedBy(8.dp),
contentPadding = PaddingValues(horizontal = 24.dp, vertical = 8.dp)
){ ... }
In the example above, the first item will add 8.dp padding to it’s top, the last item will add 8.dp to its bottom, and all items will have 24.dp padding on the left and the right.
You can use LazyColumn with itemsIndexed (formerly LazyColumnForIndexed, deprecated) and apply the padding depending on the index.
LazyColumn {
itemsIndexed(items = ...) { index, item ->
Box(Modifier.padding(
start = 16.dp, end = 16.dp, bottom = 16.dp, top = if (index == 0) 16.dp else 0.dp
))
}
}
I kind of workaround using contentPadding of LazyColumnFor for top, start and end padding, and Spacer as the bottom padding for all items.
#Composable
fun MyComposeList(
modifier: Modifier = Modifier,
listItems: List<String>,
) {
LazyColumnFor(
modifier = modifier, items = listItems,
contentPadding = PaddingValues(16.dp, 16.dp, 16.dp)
) { itemText ->
ViewItem(
itemText = itemText
)
Spacer(modifier = Modifier.fillMaxWidth().height(16.dp))
}
}
This seems to get the result I needed, as the contentPadding can be scrolled together within the LazyColumnFor
The tutorial comes from the code lab you should refer to for a good plan
https://developer.android.com/codelabs/jetpack-compose-layouts?continue=https%3A%2F%2Fdeveloper.android.com%2Fcourses%2Fpathways%2Fjetpack-compose-for-android-developers-1%23codelab-https%3A%2F%2Fdeveloper.android.com%2Fcodelabs%2Fjetpack-compose-layouts#6
LazyRow(
modifier = modifier.padding(top = 16.dp, bottom = 16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
contentPadding = PaddingValues(horizontal = 8.dp)
) {
items(alignYourBodyData) { item ->
AlignYourBodyElement(
drawable = item.drawable,
text = item.text
)
}
}