LazyVerticalGrid Custom Vertical Arrangement - android

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
}

Related

Avoid side padding from particular child item of LazyColumn in jetpack compose

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.

How to get correct systemBars height using compose

I know this seems trivial but I've looked up every doc, maybe I'm missing something.
I am trying to get this
across different device screen size (focusing on compact types). I successfully got screen height using LocalConfiguration.current.screenHeightDp
Now I'm implementing edge-to-edge display.
I followed this doc, but i get this
looking at this there is a space between them (blue), because of the feature to draw behind systembars. then i tried to get the systemBars height(top & bottom) to add to the boxes resp to compensate for the extra space.
I used inset.getInsets(WindowInsetsCompat.Type.systemBars()) which returns the heights of the systembars but after that the boxes overlap(yellow)
I logged the values I'm getting from the insets, it was quite more than the values of LocalConfiguration.current.screenHeightDP
How do i compensate for the space or get the correct height for the systemBars?
Here is my code :
#Composable
fun MyBox() {
val view = LocalView.current
val inset = ViewCompat.getRootWindowInsets(view)!!
val insetSystemBar = inset.getInsets(WindowInsetsCompat.Type.systemBars()) //Insets{left=0, top=136, right=0, bottom=132}
val insetStatusBar = insetSystemBar.top // 136
val insetNavBar = insetSystemBar.bottom // 132
val configuration = LocalConfiguration.current
val screenHeight = configuration.screenHeightDp //This returns 753
val testHeightRatio = 0.5
val TopBoxHeight = (((screenHeight * testHeightRatio).roundToInt())).dp // 377
val BottomBoxHeight = (((screenHeight * testHeightRatio).roundToInt())).dp // 377
val TopBoxAndStatusBarHeight = TopBoxHeight + insetStatusBar.dp // 513
val BottomBoxAndNavBar = BottomBoxHeight + insetNavBar.dp // 509
MyBoxTheme {
Surface(Modifier.fillMaxSize()) {
Box(
Modifier
.fillMaxSize()
.background(Color(0xFF00B8D4))
) {
Box(
modifier = Modifier
.height(BottomBoxAndNavBar)
.fillMaxWidth()
.align(Alignment.BottomCenter)
.background(Color(0x86FF0303)),
)
Box(
modifier = Modifier
.height(TopBoxAndStatusBarHeight)
.fillMaxWidth()
.background(Color(0x8FFFDD03))
.align(Alignment.TopCenter)
)
}
}
}
}

Compose material3 OverScroll Shows in all 4 directions

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.

How to make item appear in the center of the screen Jetpack Compose when using LazyRow or Column?

I have been trying to make it so that the first item on a LazyRow is centered in the middle of the screen as soon as the app is opened. The snapping behavior works once it is scrolled, but when the app is initially opened, the first item is not centered on the page.
LazyRow with snapper and infinite scrolling
#OptIn(ExperimentalSnapperApi::class)
#Composable
fun CircularList(
data: List<SingleBox>,
modifier: Modifier = Modifier,
isEndless: Boolean = true
) {
val listState = rememberLazyListState(
if (isEndless) Int.MAX_VALUE / 2 else 0
)
val configuration = LocalConfiguration.current
val screenWidth = configuration.screenWidthDp.dp
val contentPadding = PaddingValues(horizontal = screenWidth / 2) //This moves starting point of item horizontally
BoxWithConstraints {
LazyRow(
state = listState,
modifier = modifier,
contentPadding = contentPadding,
flingBehavior = rememberSnapperFlingBehavior(listState, SnapOffsets.Center, snapIndex = { _, startIndex, targetIndex ->
targetIndex.coerceIn(startIndex - 7, startIndex + 7) //This snaps item to center of page when LazyRow stops moving
})
)
{
items(
count = if (isEndless) Int.MAX_VALUE else data.size, //This makes it scroll infinitly
itemContent = {
val index = it % data.size
CustomItem(data[index]) // item composable
},
)
}
}
}
How it looks when opened
How it should look when app is opened
I think the best option here would be the Accompanist Pager Library.

Jetpack Compose Vertical Grid single item span size

In xml you can use GridLayoutManager.SpanSizeLookup in GridLayoutManager to set the span size on single items (How many columns the item will use in the row, like for example, in a grid of 3 columns I can set the first item to be span size 3 so it will use all the width of the grid), but in Compose I can't find a way to do it, the vertical grid only have a way set the global span count and add items, but not set the span size of an individual item, is there a way to do it?
Jetpack Compose version 1.1.0-beta03 introduced horizontal spans to LazyVerticalGrid.
Example code:
val list by remember { mutableStateOf(listOf("A", "E", "I", "O", "U")) }
LazyVerticalGrid(
cells = GridCells.Fixed(2)
) {
// Spanned Item:
item(
span = {
// Replace "maxCurrentLineSpan" with the number of spans this item should take.
// Use "maxCurrentLineSpan" if you want to take full width.
GridItemSpan(maxCurrentLineSpan)
}
) {
Text("Vowels")
}
// Other items:
items(list) { item ->
Text(item)
}
}
There is no support for this out of the box at present. The way I have solved this for now is to use a LazyColumn then the items are Rows and in each Row you can decide how wide an item is, using weight.
I have implemented and in my case I have headers (full width), and cells of items of equal width (based on how wide the screen is, there could be 1, 2 or 3 cells per row). It's a workaround, but until there is native support from VerticalGrid this is an option.
My solution is here - look for the LazyListScope extensions.
Edit: this is no longer necessary as LazyVerticalGrid supports spans now, here's an example
LazyVerticalGrid(
columns = GridCells.Adaptive(
minSize = WeatherCardWidth,
),
modifier = modifier,
contentPadding = PaddingValues(all = MarginDouble),
horizontalArrangement = Arrangement.spacedBy(MarginDouble),
verticalArrangement = Arrangement.spacedBy(MarginDouble),
) {
state.forecastItems.forEach { dayForecast ->
item(
key = dayForecast.header.id,
span = { GridItemSpan(maxLineSpan) }
) {
ForecastHeader(
state = dayForecast.header,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = MarginDouble),
)
}
items(
items = dayForecast.forecast,
key = { hourForecast -> hourForecast.id }
) { hourForecast ->
ForecastWeatherCard(
state = hourForecast,
modifier = Modifier.fillMaxWidth(),
)
}
}
}
Adapting the code from the answer, I created a more "general" purpose method, It can be used with Adaptive and Fixed, I'm very new with Compose so I accept suggestions
#Composable
fun HeaderGrid(cells: GridCells, content: HeaderGridScope.() -> Unit) {
var columns = 1
var minColumnWidth = 0.dp
when (cells) {
is GridCells.Fixed -> {
columns = cells.count
minColumnWidth = cells.minSize
}
is GridCells.Adaptive -> {
val width = LocalContext.current.resources.displayMetrics.widthPixels
val columnWidthPx = with(LocalDensity.current) { cells.minSize.toPx() }
minColumnWidth = cells.minSize
columns = ((width / columnWidthPx).toInt()).coerceAtLeast(1)
}
}
LazyColumn(modifier = Modifier.fillMaxWidth()){
content(HeaderGridScope(columns, minColumnWidth, this))
}
}
fun <T>HeaderGridScope.gridItems(items: List<T>, content: #Composable (T) -> Unit) {
items.chunked(numColumn).forEach {
listScope.item {
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly) {
it.forEach {
content(it)
}
if (it.size < numColumn) {
repeat(numColumn - it.size) {
Spacer(modifier = Modifier.width(columnWidth))
}
}
}
}
}
}
fun HeaderGridScope.header(content: #Composable BoxScope.() -> Unit) {
listScope.item {
Box(
modifier = Modifier
.fillMaxWidth(),
content = content
)
}
}
data class HeaderGridScope(val numColumn: Int, val columnWidth: Dp, val listScope: LazyListScope)
sealed class GridCells {
class Fixed(val count: Int, val minSize: Dp) : GridCells()
class Adaptive(val minSize: Dp) : GridCells()
}

Categories

Resources