How to stick item at bottom in LazyColumn jetpack compose - android

I want to put items at bottom of LazyColumn. So what is the best recommended way of doing in LazyColumn. Some content I put in item or itemIndexed which is correctly, but unable to know how to stick in bottom of screen?
LazyColumn(
contentPadding = PaddingValues(
horizontal = 16.dp, vertical = 16.dp)
),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
item { Header() }
itemsIndexed(storedDevice) { index, item ->
Label({ index }, item, { list.size })
}
item {
Button() // stick in bottom of screen
Button() // stick in bottom of screen
}
}
Expected Outout

You can move the Button outside the LazyColumn, using a Column
and applying the weight modifier to the LazyColumn.
Something like:
Column() {
LazyColumn(
modifier = Modifier.weight(1f),
contentPadding = PaddingValues(
horizontal = 16.dp, vertical = 16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
items(itemsList) {
Text("Item $it")
}
}
//Footer
Column() {
Button(onClick={}){ Text("Button") } // stick in bottom of screen
Button(onClick={}){ Text("Button") } // stick in bottom of screen
}
}

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 make a LazyColumn item occupy the remaining height on Jetpack Compose

Can I make a item on LazyColumn occupy only the remaining height available? I'm I tried to use fillParentMaxSize but it make the item as the same size of the LazyColumn, so i can't put another item at the top of the list, like a header that I want to scroll with the content.
Sample code
#Composable
fun LazyColumnTest() {
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
item {
Column {
Text(text = "This is a title", style = MaterialTheme.typography.h4)
Text(text = "With a subtitle", style = MaterialTheme.typography.subtitle1)
}
}
item {
OtherLayout(modifier = Modifier.fillParentMaxHeight())
}
}
}
#Composable
fun OtherLayout(modifier: Modifier = Modifier) {
Box(modifier = modifier.fillMaxSize()) {
Icon(
Icons.Default.Close,
contentDescription = null,
modifier = Modifier
.size(150.dp)
.align(Alignment.TopCenter)
)
Button(
onClick = { /*TODO*/ },
modifier = Modifier
.padding(bottom = 16.dp)
.align(Alignment.BottomCenter)
) {
Text(text = "Button at bottom")
}
}
}
Here is the current result. The button is outside the screen, so I need to scroll to see it.
Update
In the example above, the idea is to use this OtherLayout like a state. I can show the items or this OtherLayout that has a button at bottom, like a retry button.
I can do about the same layout on view system if I add fillViewport="true" on a ScrollView. It's possible to add a gravity="bottom" on another view and it will stay at the bottom of screen.
I will add a new example here with the header/footer layout to see if I can explain better.
#Composable
fun LazyColumnTest() {
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
item {
// need to scroll with the content
Header()
}
items(2) { position ->
Text(text = "Item $position")
}
item {
// need to stay at bottom if the items not fill the screen
// and scroll with the content if has a lot of items
Footer(modifier = Modifier)
}
}
}
#Composable
private fun Header() {
Column {
Text(text = "This is a title", style = MaterialTheme.typography.h4)
Text(text = "With a subtitle", style = MaterialTheme.typography.subtitle1)
}
}
#Composable
fun Footer(modifier: Modifier = Modifier) {
Column(modifier = modifier.fillMaxWidth()) {
Text(text = "This is a default footer that cannot be changed")
Button(
onClick = { /*TODO*/ },
modifier = Modifier
.padding(bottom = 16.dp)
) {
Text(text = "With a button")
}
}
}
in this example I need that header and footer scrolls with the content, but if the items not fill the entire screen, the footer remains at bottom of the screen.
Using the item function you are adding another item to the scrollable content of the LazyColumn.
If you want that the LazyColumn occupies only the remaining height available, and the footer that doesn't scroll with the list, you can move the footer out of the LazyColumn and apply the weight modifier to the LazyColumn.
Something like:
Column(){
Header()
LazyColumn(Modifier.weight(1f)) {
//....
}
Footer() //your Box
}
In your case:
Column {
//Header
Box(Modifier.fillMaxWidth().height(30.dp).background(Red))
LazyColumn(Modifier.weight(1f)) {
items(itemsList){
Text("Item $it")
}
}
//Footer
OtherLayout()
}
With:
#Composable
fun OtherLayout(modifier: Modifier = Modifier) {
Box(modifier = modifier.fillMaxWidth()) {
//..
}
}

Android Jetpack Compose LazyVerticalGrid horizontal span also adds unnecessary vertical span

So, my code looks like this (I simplified it a lot for readability)
LazyVerticalGrid(
modifier = modifier
.background(Color.Black)
.padding(16.dp),
cells = GridCells.Fixed(5),
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
items(26) {
Spacer(
modifier = Modifier
.size(64.dp)
.background(Color.Cyan)
)
}
item {
Spacer(
modifier = Modifier
.height(64.dp)
.fillMaxWidth()
.background(Color.Magenta)
)
}
}
And it looks like this (screenshot from preview):
However, when I add a span to the last item (the magenta one):
item(span = { GridItemSpan(2) }) {
//The last item goes here
}
An unwanted space in the bottom is added:
If the item with a span is the first one (before the cyan ones), the space remains in the bottom.

How to set visible indicator' dot and show the others when scrolling in jetpack compose

I have a lazyRow and I want to show list of indicators:
what I want: I want to show 6 items and when user scrolls other indicators get visible.
#Composable
private fun ImagesDotsIndicator(
modifier: Modifier,
totalDots: Int,
selectedIndex: Int
) {
LazyRow(
modifier = modifier,
horizontalArrangement = Arrangement.Center,
reverseLayout = true,
verticalAlignment = Alignment.CenterVertically
) {
if (totalDots == 1) return#LazyRow
items(totalDots) { index ->
if (index == selectedIndex) {
Box(
modifier = Modifier
.size(8.dp)
.clip(CircleShape)
.background(color = Color.White)
)
} else {
Box(
modifier = Modifier
.size(6.dp)
.clip(CircleShape)
.background(color = Color.LightGray)
)
}
Spacer(modifier = Modifier.padding(horizontal = 2.dp))
}
}
}
how can I make this indicator?
I would suggest you use Google's Accompanist HorizontalPager and HorizontalPagerIndicator if you want to swipe pages and show the dots. This is a layout that lays out items in a horizontal row, and allows the user to horizontally swipe between pages and also show the page indicator.
You need to add these 2 lines to your app build gradle file to add the dependencies.
// Horizontal Pager and Indicators - Accompanist
implementation "com.google.accompanist:accompanist-pager:0.24.7-alpha"
implementation "com.google.accompanist:accompanist-pager-indicators:0.24.7-alpha"
On your composable file, you can add a simple Sealed class to hold the data that you want to display e.g. text.
sealed class CustomDisplayItem(val text1:String, val text2: String){
object FirstItem: CustomDisplayItem("Hi", "World")
object SecondItem: CustomDisplayItem("Hello", "I'm John")
}
Thereafter make a template of the composable element or page that you want to show if the user swipes left or right.
#Composable
fun DisplayItemTemplate(item: CustomDisplayItem) {
Column() {
Text(text = item.text2 )
Spacer(modifier = Modifier.height(4.dp))
Text(text = item.text2)
}
}
Lastly use HorizontalPager and HorizontalPageIndicator composables to display the corresponding page when a user swipes back and forth.
#OptIn(ExperimentalPagerApi::class)
#Composable
fun ImagesDotsIndicator(
modifier: Modifier,
) {
//list of pages to display
val displayItems = listOf(CustomDisplayItem.FirstItem, CustomDisplayItem.SecondItem)
val state = rememberPagerState()
Column(modifier = modifier.fillMaxSize()) {
//A horizontally scrolling layout that allows users to
// flip between items to the left and right.
HorizontalPager(
count = 6,
state = state,
) {
/*whenever we scroll sideways the page variable changes
displaying the corresponding page */
item ->
//call template item and add the data
DisplayItemTemplate(item = displayItems[item])
}
//HorizontalPagerIndicator dots
HorizontalPagerIndicator(
pagerState = state,
activeColor = MaterialTheme.colors.primary,
inactiveColor = Color.Gray,
indicatorWidth = 16.dp,
indicatorShape = CircleShape,
spacing = 8.dp,
modifier = Modifier
.weight(.1f)
.align(CenterHorizontally)
)
}
}
Please see the above links to read more on how you can customize your composables to work in your case.
Actually it is preaty straight forward without any additional library:
val list = (0..100).toList()
val state = rememberLazyListState()
val visibleIndex by remember {
derivedStateOf {
state.firstVisibleItemIndex
}
}
Text(text = visibleIndex.toString())
LazyColumn(state = state) {
items(list) { item ->
Text(text = item.toString())
}
}
Create scroll state and use it on your list, and on created scroll state observe first visible item.

What is the ItemDecoration for Jetpack Compose LazyColumn?

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
)
}
}

Categories

Resources