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
)
}
}
Related
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.
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
}
}
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.
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
I want to create a dashboard and I want this kind of layout on top. How do I achieve a layout like this one in jetpack compose?
top dashboard layout,
top dashboard layout 2
You can use a Box or BoxWithConstraints to achieve that look. The key thing here is to align the one in center to bottom and add bottom padding as big as the sum of height of Composable at the bottom and the half height of at the bottom since your Composables at the bottom and at the top don't have equal heights.
#Composable
private fun MyComposable(modifier: Modifier = Modifier) {
BoxWithConstraints(modifier = modifier.fillMaxWidth(1f)) {
val maxHeight = this.maxHeight
val topHeight: Dp = maxHeight * 2 / 3
val bottomHeight: Dp = maxHeight / 3
val centerHeight = 100.dp
val centerPaddingBottom = bottomHeight - centerHeight / 2
Top(
modifier = Modifier
.fillMaxWidth()
.align(Alignment.TopCenter)
.height(topHeight)
)
Bottom(
modifier = Modifier
.fillMaxWidth()
.align(Alignment.BottomCenter)
.height(bottomHeight)
)
Center(
modifier = Modifier
.padding(start = 10.dp, end = 10.dp, bottom = centerPaddingBottom)
.fillMaxWidth()
.height(centerHeight)
.align(Alignment.BottomCenter)
)
}
}
#Composable
private fun Top(modifier: Modifier) {
Column(modifier.background(Blue400)) {
}
}
#Composable
private fun Bottom(modifier: Modifier) {
Column(modifier.background(Color.White)) {
}
}
#Composable
fun Center(modifier: Modifier) {
Column(modifier.background(Color.Red, shape = RoundedCornerShape(10.dp))) {
}
}
Result
Generally you can use a Box.
See the example here https://foso.github.io/Jetpack-Compose-Playground/layout/box/
Box(Modifier.fillMaxSize()) {
Text("This text is drawn first", modifier = Modifier.align(Alignment.TopCenter))
Box(
Modifier.align(Alignment.TopCenter).fillMaxHeight().width(
50.dp
).background( Color.Blue)
)
Text("This text is drawn last", modifier = Modifier.align(Alignment.Center))
FloatingActionButton(
modifier = Modifier.align(Alignment.BottomEnd).padding(12.dp),
onClick = {}
) {
Text("+")
}
}