Chapters

Hide chapters

Jetpack Compose by Tutorials

First Edition · Android 11 · Kotlin 1.4 · Android Studio Canary

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

4. Building Lists with Jetpack Compose
Written by Tino Balint

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

In previous chapters, you learned about different elements in Compose and how to group and position them inside layouts to build complex UIs. Using that knowledge, you could potentially build any screen.

However, you’re missing some functionality that you’ll eventually need. What happens when you have to display more elements than you can fit on the screen? In that case, the elements are all composed, but the limited screen size prevents you from seeing all of them. There are even situations where you want to dynamically add an infinite number of new elements on the screen and still be able to see them all.

The solution to this problem is allowing your content to scroll, either vertically or horizontally. The traditional way of implementing this feature is to use ScrollView, which allows you to scroll content vertically. For horizontal scrolling, you use HorizontalScrollView. Both of them can have only one child view inside them, so to add multiple elements, you need to use a single layout that wraps those elements.

Jetpack Compose gives you a new way to achieve the same result — using scrollable and lazily composed containers.

In this chapter, you’ll learn how to make lists and grids in Jetpack Compose to help you fit all your content on the screen. You’ll learn how to show content that scrolls vertically or horizontally and how to build an alternative for the traditional RecyclerView using composable functions.

Using ScrollableColumn

As you know by now, Column is the replacement for LinearLayout in the vertical orientation. For vertical scrolling elements, there’s a similar layout called ScrollableColumn, which is actually a variation of the Column that enables scrolling! Let’s see how to implement a simple ScrollableColumn.

To follow along with the code examples, open Android Studio and select Open an Existing Project. Then, navigate to 04-building-lists-with-jetpack-compose/projects and select the starter folder.

Once the project builds, you’ll see the following structure:

Project Structure
Project Structure

You’ll start off by building a ScrollableColumn after which you’ll explore its horizontal counterpart. To do that, open ScrollingScreen.kt and you’ll see two composable functions — ScrollingScreen() and MyScrollingScreen():

@Composable
fun ScrollingScreen() {
  MyScrollingScreen()
  BackButtonHandler {
    JetFundamentalsRouter.navigateTo(Screen.Navigation)
  }
}

@Composable
fun MyScrollingScreen() {
  //TODO add your code here
}

As in the previous chapters, ScrollingScreen() is already set up to handle the back navigation, so you only need to implement MyScrollingScreen().

Add the following code to the body of MyScrollingScreen() and include the required imports with the help of Android Studio:

@Composable
fun MyScrollingScreen() {
  ScrollableColumn {
    Image(bitmap = imageResource(R.drawable.advanced_architecture_android))
    Image(bitmap = imageResource(R.drawable.kotlin_aprentice))
    Image(bitmap = imageResource(R.drawable.kotlin_coroutines))
  }
}

Here, you added three existing images from the drawable folder to the screen. You also used ScrollableColumn() to make the wrapped content scrollable and to display it in a vertical orientation. What happens here is that you’ll show a Column, a vertical list of items. But if the items are too large to show all at once, it will be scrollable and you’ll be able to go through each item specifically.

Build and run the app, then select Scrolling from the navigation menu. You’ll see the three images, one below the other — but unfortunately, they don’t fit on the screen together. Luckily, you made the screen scrollable! :]

Scroll down to see the images that aren’t displayed yet.

Scrolling Column
Scrolling Column

Using a ScrollableColumn is very easy, but there is much more you can do with it. Let’s explore how it works.

Exploring ScrollableColumn

Look at its source code to see what a ScrollableColumn can do and how it works when you use it:

@Composable
@OptIn(InternalLayoutApi::class)
fun ScrollableColumn(
    modifier: Modifier = Modifier,
    scrollState: ScrollState = rememberScrollState(0f),
    verticalArrangement: Arrangement.Vertical = Arrangement.Top,
    horizontalAlignment: Alignment.Horizontal = Alignment.Start,
    reverseScrollDirection: Boolean = false,
    isScrollEnabled: Boolean = true,
    contentPadding: PaddingValues = PaddingValues(0.dp),
    content: @Composable ColumnScope.() -> Unit
) {
    Column(
        modifier = modifier
            .verticalScroll(
                scrollState,
                isScrollEnabled,
                reverseScrolling = reverseScrollDirection
            )
            .padding(contentPadding),
        verticalArrangement = verticalArrangement,
        horizontalAlignment = horizontalAlignment,
        content = content
    )
}

Using ScrollableRow

Vertical scrolling now works on your screen — but in some cases, you need a horizontal scroll, instead.

@Composable
fun MyScrollingScreen() {
  ScrollableRow { // here
    Image(bitmap = imageResource(R.drawable.advanced_architecture_android))
    Image(bitmap = imageResource(R.drawable.kotlin_aprentice))
    Image(bitmap = imageResource(R.drawable.kotlin_coroutines))
  }
}
Scrolling Row
Fdsiynowg Sik

Lists in Compose

To display a large collection of elements in Android, you used the RecyclerView. The only elements RecyclerView renders are the ones visible on the screen. Only after the user begins to scroll does it render the new elements and display them onscreen. It then recycles the elements that go off the screen into a pool of view holders.

Introducing LazyColumn & LazyRow

LazyColumn and LazyRow are used for vertical and horizontal scenarios, respectively.

Creating lists with LazyColumn & LazyRow

There are many awesome books in our raywenderlich.com library and in different categories. It’s best to show them all categorized, so you can easily pick and choose your favorites.

Book Categories
Naar Jipowubeac

@Composable
fun ListScreen() {
  MyList()
  BackButtonHandler {
    JetFundamentalsRouter.navigateTo(Screen.Navigation)
  }
}

@Composable
fun MyList() {
  //TODO add your code here
}

@Composable
fun ListItem(bookCategory: BookCategory, modifier: Modifier = Modifier) {
  //TODO add your code here
}
@Composable
fun MyList() {
  LazyColumn {
      items(
        items = items,
        itemContent = {
          ListItem(it)
        }
      )
  }
}
@Composable
fun ListItem(bookCategory: BookCategory, modifier: Modifier = Modifier) {
  Column(modifier = Modifier.padding(8.dp)) {
    Text(
        text = stringResource(bookCategory.categoryResourceId),
        fontSize = 22.sp,
        fontWeight = FontWeight.Bold,
        color = colorResource(id = R.color.colorPrimary)
    )
    Spacer(modifier = modifier.height(8.dp))

    // TODO
  }
}
LazyRow(bookCategory.bookImageResources) {
  items(
      items = bookCategory.bookImageResources,
      itemContent = {
        BookImage(imageResource = it)
      }
  )
}
@Composable
fun BookImage(imageResource: Int) {
  Image(
    modifier = Modifier.size(170.dp, 200.dp),
    bitmap = imageResource(id = imageResource),
    contentScale = ContentScale.Fit
  )
}
List
Tord

Exploring Lists

Now that you understand the difference and how to implement specific lists, take a look at the signature for LazyColumn and LazyRow:

@Composable
fun <T> LazyColumn(
    modifier: Modifier = Modifier,
    state: LazyListState = rememberLazyListState(),
    contentPadding: PaddingValues = PaddingValues(0.dp),
    reverseLayout: Boolean = false,
    verticalArrangement: Arrangement.Vertical = if (!reverseLayout) Arrangement.Top else Arrangement.Bottom,
    horizontalAlignment: Alignment.Horizontal = Alignment.Start,
    content: LazyListScope.() -> Unit
)

@Composable
fun <T> LazyRow(
    modifier: Modifier = Modifier,
    state: LazyListState = rememberLazyListState(),
    reverseLayout: Boolean = false,
    horizontalArrangement: Arrangement.Horizontal = if (!reverseLayout) Arrangement.Start else Arrangement.End,
    verticalAlignment: Alignment.Vertical = Alignment.Top,
    content: LazyListScope.() -> Unit
)
interface LazyListScope {

    fun <T> items(
        items: List<T>,
        itemContent: @Composable LazyItemScope.(item: T) -> Unit
    )

    fun item(content: @Composable LazyItemScope.() -> Unit)

    fun <T> itemsIndexed(
        items: List<T>,
        itemContent: @Composable LazyItemScope.(index: Int, item: T) -> Unit
    )

    @ExperimentalFoundationApi
    fun stickyHeader(content: @Composable LazyItemScope.() -> Unit)
}

Grids in Compose

When working with a RecyclerView, you can use different types of LayoutManagers to place your elements on the screen in different ways. To make grids, for example, you use a GridLayoutManager and then set the number of columns inside the grid.

Grid Calculation
Pded Seqkivumaof

Implementing a grid

Open GridScreen.kt and take a moment to look inside. You’ll find the usual function to handle the navigation and a list containing the icons that you’ll use as the grid’s content. At the bottom of the file, you’ll find the following composable functions that you need to implement:

@Composable
fun GridView(columnCount: Int) {
  //TODO add your code here
}

@Composable
fun RowItem(rowItems: List<IconResource>) {
  //TODO add your code here
}

@Composable
fun GridIcon(iconResource: IconResource) {
  //TODO add your code here
}

Implementing GridView

First, you’ll deal with GridView(). This composable takes a parameter named columnCount, which determines the maximum number of elements you need to place in each row.

@Composable
fun GridView(columnCount: Int) {
  val itemSize = items.size
  val rowCount = ceil(itemSize.toFloat() / columnCount).toInt()
  val gridItems = mutableListOf<List<IconResource>>()
  var position = 0
}
@Composable
fun GridView(columnCount: Int) {
  ...
  for (i in 0 until rowCount) {
    val rowItem = mutableListOf<IconResource>()
    for (j in 0 until columnCount) {
      if (position.inc() <= itemSize) {
        rowItem.add(IconResource(items[position++], true))
      }
    }
  // TODO
}
@Composable
fun GridView(columnCount: Int) {
  ...
  for (i in 0 until rowCount) {
    val rowItem = mutableListOf<IconResource>()
    for (j in 0 until columnCount) {
      if (position.inc() <= itemSize) {
        rowItem.add(IconResource(items[position++], true))
      }
    }
    // here  
    val itemsToFill = columnCount - rowItem.size

    for (j in 0 until itemsToFill) {
      rowItem.add(IconResource(Icons.Filled.Delete, false))
    }
    gridItems.add(rowItem)
  }
  // here
  LazyColumn(modifier = Modifier.fillMaxSize()) {
    items(
        items = gridItems,
        itemContent = {
          RowItem(it)
        }
    )
  }
}

Implementing RowItem

Each RowItem() will represent a series of GridIcons for that row. Replace the code of the RowItem() with the following:

@Composable
fun RowItem(rowItems: List<IconResource>) {
  Row {
    for (element in rowItems)
      GridIcon(element)
  }
}

Implementing GridIcon

Each GridItem() will show the icon you passed in, or show an invisible icon if you need to add dummy elements to the grid, to fill up the row. Replace the GridIcon with the following code to achieve such behavior:

@Composable
fun GridIcon(iconResource: IconResource) {
  val color = if (iconResource.isVisible)
    colorResource(R.color.colorPrimary)
  else Color.Transparent

  with(RowScope) {
    Icon(
      imageVector = iconResource.imageVector,
      tint = color,
      modifier = Modifier
        .size(80.dp, 80.dp)
        .weight(1f)
    )
  }
}
Grid With Three Columns
Kmev Rudr Vvjiu Wejamlf

Key points

  • Use ScrollableColumn as a parent to scroll the content vertically.
  • Use ScrollableRow as a parent to scroll the content horizontally.
  • You can make your own composables scrollable by adding the verticalScroll or horizontalScroll modifiers.
  • Use scrollers only for a fixed amount of content.
  • For dynamic and larger amounts of content, use lists instead.
  • The composable alternatives to RecyclerView are called LazyColumn and LazyRow for the vertical and horizontal scenarios, respectively.
  • You can group lists inside each other to make content scrollable in both directions.
  • To make grids, you need a custom implementation.
  • Use a transparent color or set an alpha to zero to make an invisible composable.
  • Alternatively, you can use LazyRow and LazyColumn components if you want to manually add items to the list, allowing you to build headers and footers. Learn more about them here: https://developer.android.com/reference/kotlin/androidx/compose/foundation/lazy/package-summary#lazycolumn.

Where to go from here?

In this chapter, you learned how to make scrollable content, scrollable lists for dynamically created elements and custom grids.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.

Unlock now