Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Deep Dive in Compose Layout, Lazy Layout, and ...

Deep Dive in Compose Layout, Lazy Layout, and Material Design 3 - Compose Camp x Juara Android

Avatar for Ahmad Arif Faizin

Ahmad Arif Faizin

October 29, 2022
Tweet

More Decks by Ahmad Arif Faizin

Other Decks in Programming

Transcript

  1. Ahmad Arif Faizin Curriculum Developer at Dicoding Indonesia @arif_faizin Deep

    Dive in Compose Layout, Lazy Layout, and Material Design 3
  2. @Composable fun SearchResult(result: SearchResult, modifier: Modifier = Modifier) { Row(modifier

    = modifier .padding(8.dp) .background(MaterialTheme.colors.surface) ) { Image(modifier = Modifier.size(72.dp)) Column(modifier = Modifier .padding(16.dp) .align(Alignment.CenterVertically) ) { Text(result.title) Text(result.subtitle) } } }
  3. @Composable fun SearchResult(...) { Row(...) { Image(...) Column(...) { Text(...)

    Text(..) } } } Layout Drawing Composition SearchResult Row Image Column Text Text
  4. @Composable fun SearchResult(...) { Row(...) { Image(...) Column(...) { Text(...)

    Text(..) } } } Drawing Composition SearchResult Row Image Column Text Text Layout
  5. @Composable fun SearchResult(...) { Row(...) { Image(...) Column(...) { Text(...)

    Text(..) } } } Drawing Composition SearchResult Row Image Column Text Text Layout 1 measure
  6. @Composable fun SearchResult(...) { Row(...) { Image(...) Column(...) { Text(...)

    Text(..) } } } Drawing Composition SearchResult Row Image Column Text Text Layout 1 measure 2 measure
  7. @Composable fun SearchResult(...) { Row(...) { Image(...) Column(...) { Text(...)

    Text(..) } } } Drawing Composition SearchResult Row Image Column Text Text Layout 1 measure 2 measure 3 size
  8. SearchResult Row Image Column Text Text @Composable fun SearchResult(...) {

    Row(...) { Image(...) Column(...) { Text(...) Text(..) } } } place Drawing Composition Layout 1 measure 2 measure 3 size
  9. @Composable fun SearchResult(...) { Row(...) { Image(...) Column(...) { Text(...)

    Text(..) } } } SearchResult Row Image Column Text Text place Drawing Composition Layout 1 measure 2 measure 4 measure 3 size
  10. @Composable fun SearchResult(...) { Row(...) { Image(...) Column(...) { Text(...)

    Text(..) } } } SearchResult Row Image Column Text Text place Drawing Composition Layout 1 measure 2 measure 4 measure 5 measure 3 size
  11. @Composable fun SearchResult(...) { Row(...) { Image(...) Column(...) { Text(...)

    Text(..) } } } SearchResult Row Image Column Text Text place place Drawing Composition Layout 1 measure 2 measure 4 measure 5 measure 6 size 3 size
  12. SearchResult Row Image Column Text Text @Composable fun SearchResult(...) {

    Row(...) { Image(...) Column(...) { Text(...) Text(..) } } } place place place Drawing Composition Layout 1 measure 2 measure 4 measure 5 measure 6 size 7 measure 8 size 3 size
  13. @Composable fun SearchResult(...) { Row(...) { Image(...) Column(...) { Text(...)

    Text(..) } } } SearchResult Row Image Column Text Text place place place place Drawing Composition Layout 1 measure 2 measure 3 size 4 measure 5 measure 6 size 7 measure 8 size 9 size
  14. SearchResult Row Image Column Text Text @Composable fun SearchResult(...) {

    Row(...) { Image(...) Column(...) { Text(...) Text(..) } } } place place place place place Drawing Composition Layout 1 measure 2 measure 4 measure 5 measure 6 size 7 measure 8 size 9 size 10 size 3 size
  15. @Composable fun SearchResult(...) { Row(...) { Image(...) Column(...) { Text(...)

    Text(..) } } } SearchResult Row Image Column Text Text 1 measure 2 measure 4 measure 5 measure 6 size 7 measure 8 size 9 size 10 size 3 size place place place place place Drawing Composition Layout Measure Place
  16. Box( modifier = Modifier .fillMaxSize() .wrapContentSize() .size(50.dp) .background(Color.Blue) ) measure

    w: 0–200, h: 0–300 val childConstraints = Constraints( minWidth = outerConstraints.maxWidth, maxWidth = outerConstraints.maxWidth, minHeight = outerConstraints.maxHeight, maxHeight = outerConstraints.maxHeight, )
  17. Box( modifier = Modifier .fillMaxSize() .wrapContentSize() .size(50.dp) .background(Color.Blue) ) measure

    w: 0–200, h: 0–300 val childConstraints = Constraints( minWidth = outerConstraints.maxWidth, maxWidth = outerConstraints.maxWidth, minHeight = outerConstraints.maxHeight, maxHeight = outerConstraints.maxHeight, ) w: 200, h: 300
  18. measure w: 0–200, h: 0–300 measure w: 200, h: 300

    Box( modifier = Modifier .fillMaxSize() .wrapContentSize() .size(50.dp) .background(Color.Blue) )
  19. measure w: 0–200, h: 0–300 measure w: 200, h: 300

    Box( modifier = Modifier .fillMaxSize() .wrapContentSize() .size(50.dp) .background(Color.Blue) ) val childConstraints = Constraints( minWidth = 0, maxWidth = outerConstraints.maxWidth, minHeight = 0, maxHeight = outerConstraints.maxHeight, )
  20. measure w: 0–200, h: 0–300 measure w: 200, h: 300

    Box( modifier = Modifier .fillMaxSize() .wrapContentSize() .size(50.dp) .background(Color.Blue) ) val childConstraints = Constraints( minWidth = 0, maxWidth = outerConstraints.maxWidth, minHeight = 0, maxHeight = outerConstraints.maxHeight, ) w: 0–200, h: 0–300
  21. measure w: 200, h: 300 w: 0–200, h: 0–300 measure

    measure w: 0–200, h: 0–300 Box( modifier = Modifier .fillMaxSize() .wrapContentSize() .size(50.dp) .background(Color.Blue) )
  22. measure w: 200, h: 300 w: 0–200, h: 0–300 measure

    measure w: 0–200, h: 0–300 Box( modifier = Modifier .fillMaxSize() .wrapContentSize() .size(50.dp) .background(Color.Blue) ) val childConstraints = Constraints( minWidth = 50, maxWidth = 50, minHeight = 50, maxHeight = 50, )
  23. measure w: 200, h: 300 w: 0–200, h: 0–300 measure

    measure w: 0–200, h: 0–300 Box( modifier = Modifier .fillMaxSize() .wrapContentSize() .size(50.dp) .background(Color.Blue) ) val childConstraints = Constraints( minWidth = 50, maxWidth = 50, minHeight = 50, maxHeight = 50, ) w: 50, h: 50
  24. measure w: 0–200, h: 0–300 measure w: 200, h: 300

    w: 0–200, h: 0–300 measure Box( modifier = Modifier .fillMaxSize() .wrapContentSize() .size(50.dp) .background(Color.Blue) ) w: 50, h: 50
  25. Box( modifier = Modifier .fillMaxSize() .wrapContentSize() .size(50.dp) .background(Color.Blue) ) measure

    w: 0–200, h: 0–300 w: 50, h: 50 measure w: 200, h: 300 w: 0–200, h: 0–300 measure place 50*50
  26. place 50*50 measure w: 0–200, h: 0–300 w: 50, h:

    50 measure w: 200, h: 300 w: 0–200, h: 0–300 measure place 50*50 Box( modifier = Modifier .fillMaxSize() .wrapContentSize() .size(50.dp) .background(Color.Blue) )
  27. place 50*50 measure w: 0–200, h: 0–300 w: 50, h:

    50 place measure w: 200, h: 300 w: 0–200, h: 0–300 measure 50*50 200*300 place Box( modifier = Modifier .fillMaxSize() .wrapContentSize() .size(50.dp) .background(Color.Blue) )
  28. place 50*50 measure w: 0–200, h: 0–300 w: 50, h:

    50 place measure w: 200, h: 300 w: 0–200, h: 0–300 measure 50*50 200*300 place Box( modifier = Modifier .fillMaxSize() .wrapContentSize() .size(50.dp) .background(Color.Blue) )
  29. place 50*50 measure w: 0–200, h: 0–300 w: 50, h:

    50 place 50*50 measure w: 200, h: 300 w: 0–200, h: 0–300 measure 200*300 place place 200*300 Box( modifier = Modifier .fillMaxSize() .wrapContentSize() .size(50.dp) .background(Color.Blue) )
  30. @Composable inline fun Box( modifier: Modifier = Modifier, contentAlignment: Alignment

    = Alignment.TopStart, propagateMinConstraints: Boolean = false, content: @Composable BoxScope.() -> Unit ) { ... }
  31. Box(Modifier.size(200.dp, 300.dp)) { // this: BoxScope Box( modifier = Modifier

    .fillMaxSize() .wrapContentSize() .size(50.dp) .background(Color.Blue) ) }
  32. fun MyCustomLayout( modifier: Modifier = Modifier, content: @Composable () ->

    Unit ) { Layout( modifier = modifier, content = content ) { measurables: List<Measurable>, constraints: Constraints -> val placeables = measurables.map { measurable -> val parentData = measurable.parentData as? MyParentData val myConstraints = makeConstraints(parentData) measurable.measure(myConstraints) } // TODO report size and place items } }
  33. class FlowersAdapter(private val onClick: (Flower) -> Unit) : ListAdapter<Flower, FlowersAdapter.FlowerViewHolder>(FlowerDiffCallback)

    { class FlowerViewHolder(itemView: View, val onClick: (Flower) -> Unit) : RecyclerView.ViewHolder(itemView) { private val flowerTextView: TextView = itemView.findViewById(R.id.flower_text) ... fun bind(flower: Flower) { ... } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FlowerViewHolder { ... } override fun onBindViewHolder(holder: FlowerViewHolder, position: Int) { ... } }
  34. class FlowersListActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val flowersAdapter = FlowersAdapter { flower -> adapterOnClick(flower) } val recyclerView: RecyclerView = findViewById(R.id.recycler_view) recyclerView.adapter = flowersAdapter ... } }
  35. @Composable fun FlowerList(flowers: List<Flower>) { LazyColumn { items(flowers) { flower

    -> FlowerItem(flower) } } } @Composable fun FlowerItem(flower: Flower) { Column { Image(flower.image) Text(flower.name) } }
  36. LazyRow( modifier = Modifier.padding( start = 24.dp, end = 24.dp

    ) ) { items(data) { item -> Item(item) } }
  37. LazyRow( modifier = Modifier.padding( start = 24.dp, end = 24.dp

    ) ) { items(data) { item -> Item(item) } }
  38. LazyRow( contentPadding = PaddingValues( start = 24.dp, end = 24.dp

    ) ) { items(data) { item -> Item(item) } }
  39. Common Performance Gotchas in Jetpack Compose val state = rememberLazyListState()

    val showScrollToTopButton by remember { derivedStateOf { state.firstVisibleItemIndex > 0 } }
  40. val state = rememberLazyListState() ScrollToTopButton( onClick = { // suspend

    function state.animateScrollToItem( index = 0 ) } )
  41. val state = rememberLazyListState() val coroutineScope = rememberCoroutineScope() ScrollToTopButton( onClick

    = { coroutineScope.launch { state.animateScrollToItem( index = 0 ) } } )
  42. val state = rememberLazyListState() val coroutineScope = rememberCoroutineScope() ScrollToTopButton( onClick

    = { coroutineScope.launch { state.animateScrollToItem( index = 0 ) } } )
  43. LazyVerticalGrid( contentPadding = PaddingValues(...), state = state // LazyGridState, ...

    ) LazyColumn( contentPadding = PaddingValues(...), state = state // LazyListState, ... )
  44. LazyVerticalGrid( state = state // LazyGridState, ... ) state.firstVisibleItemIndex //

    Int state.layoutInfo // LazyGridLayoutInfo LazyColumn( state = state // LazyListState, ... ) state.firstVisibleItemIndex // Int state.layoutInfo // LazyListLayoutInfo
  45. LazyVerticalGrid( state = state // LazyGridState, ... ) state.firstVisibleItemIndex //

    Int state.layoutInfo // LazyGridLayoutInfo state.scrollToItem(...) state.animateScrollToItem(...) LazyColumn( state = state // LazyListState, ... ) state.firstVisibleItemIndex // Int state.layoutInfo // LazyListLayoutInfo state.scrollToItem(...) state.animateScrollToItem(...)
  46. Available width Spacing LazyVerticalGrid( columns = object : GridCells {

    override fun Density.calculateCrossAxisCellSizes( availableSize: Int, spacing: Int ): List<Int> { ... } } )
  47. LazyVerticalGrid( columns = object : GridCells { override fun Density.calculateCrossAxisCellSizes(

    availableSize: Int, spacing: Int ): List<Int> { val firstColumn = (availableSize - spacing) * 2 / 3 val secondColumn = availableSize - spacing - firstColumn ... } } ) Available width Spacing
  48. LazyVerticalGrid( columns = object : GridCells { override fun Density.calculateCrossAxisCellSizes(

    availableSize: Int, spacing: Int ): List<Int> { val firstColumn = (availableSize - spacing) * 2 / 3 val secondColumn = availableSize - spacing - firstColumn return listOf(firstColumn, secondColumn) } } ) Available width Spacing
  49. maxLineSpan = 3 LazyVerticalGrid( ... ) { item(span = {

    // LazyGridItemSpanScope: // maxLineSpan GridItemSpan(maxLineSpan) }) { CategoryCard(“Fruits”) } ... }
  50. LazyVerticalGrid( ... ) { item(span = { GridItemSpan(maxLineSpan) }) {

    CategoryCard(“Fruits”) } items(fruitPlants) { plant -> PlantCard(plant) } }
  51. 134

  52. 135

  53. val childConstraints = Constraints( maxWidth = if (isVertical) constraints.maxWidth else

    Constraints.Infinity, maxHeight = if (!isVertical) constraints.maxHeight else Constraints.Infinity )
  54. val childConstraints = Constraints( maxWidth = if (isVertical) constraints.maxWidth else

    Constraints.Infinity, maxHeight = if (!isVertical) constraints.maxHeight else Constraints.Infinity )
  55. val childConstraints = Constraints( maxWidth = if (isVertical) constraints.maxWidth else

    Constraints.Infinity, maxHeight = if (!isVertical) constraints.maxHeight else Constraints.Infinity )
  56. val childConstraints = Constraints( maxWidth = if (isVertical) constraints.maxWidth else

    Constraints.Infinity, maxHeight = if (!isVertical) constraints.maxHeight else Constraints.Infinity )
  57. @Composable fun Item() { Image( painter = rememberImagePainter( data =

    imageUrl ), modifier = Modifier.size(30.dp), ... ) }
  58. LazyColumn { item { Header() } items(data) { item ->

    Item(item) } item { Footer() } }
  59. LazyColumn { item { Header() } items(data) { item ->

    Item(item) } item { Footer() } }
  60. 0 3 ? LazyVerticalGrid( ... ) { item { Item(0)

    } item { Item(1) Item(2) } item { Item(3) } ... }
  61. LazyVerticalGrid( ... ) { item { Item(0) } item {

    Item(1) Item(2) } item { Item(3) } ... } 0 3 1 2
  62. LazyVerticalGrid( ... ) { item { Item(0) } item {

    Item(1) Item(2) } item { Item(3) } ... } 0 3 1 2
  63. LazyVerticalGrid( ... ) { item { Item(0) } item {

    Item(1) Item(2) } item { Item(3) } ... } 3 After a call to: scrollToItem(2)
  64. 0 1 2 LazyVerticalGrid( ... ) { item { Item(0)

    } item { Item(1) Divider() } item { Item(2) } ... }
  65. LazyColumn( modifier = Modifier.fillMaxHeight(), verticalArrangement = TopWithFooter ) { ...

    } object TopWithFooter : Arrangement.Vertical { ... } 0 3 - Footer 1 2
  66. object TopWithFooter : Arrangement.Vertical { override fun Density.arrange( totalSize: Int,

    sizes: IntArray, outPositions: IntArray ) { ... } } 0 3 - Footer 1 2
  67. object TopWithFooter : Arrangement.Vertical { override fun Density.arrange( totalSize: Int,

    sizes: IntArray, outPositions: IntArray ) { var y = 0 sizes.forEachIndexed { index, size -> outPositions[index] = y y += size } ... } } 0 3 - Footer 1 2
  68. object TopWithFooter : Arrangement.Vertical { override fun Density.arrange( totalSize: Int,

    sizes: IntArray, outPositions: IntArray ) { var y = 0 sizes.forEachIndexed { index, size -> outPositions[index] = y y += size } if (y < totalSize) { val lastIndex = outPositions.lastIndex outPositions[lastIndex] = totalSize - sizes.last() } } } 0 3 - Footer 1 2
  69. import androidx.compose.material3.MaterialTheme @Composable fun MaterialTheme( colorScheme: ColorScheme, typography: Typography, //

    Updates to Shapes coming soon content: @Composable () -> Unit ) MaterialTheme Material 3
  70. @Composable fun Message(...) { val avatarBorderColor = if (isUserMe) {

    MaterialTheme.colorScheme.primary } else { MaterialTheme.colorScheme.tertiary } ... } Jetchat message
  71. val dynamic = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S val colorScheme = if

    (dynamic) { val context = LocalContext.current if (dark) dynamiclightColorScheme(context) else dynamicDarkColorScheme(context) } else { // Use lightColorScheme, darkColorScheme, etc. } Dynamic ColorScheme
  72. val dynamic = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S val colorScheme = if

    (dynamic) { val context = LocalContext.current if (dark) dynamiclightColorScheme(context) else dynamicDarkColorScheme(context) } else { // Use lightColorScheme, darkColorScheme, etc. } Dynamic ColorScheme
  73. val dynamic = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S val colorScheme = if

    (dynamic) { val context = LocalContext.current if (dark) dynamiclightColorScheme(context) else dynamicDarkColorScheme(context) } else { // Use lightColorScheme, darkColorScheme, etc. } Dynamic ColorScheme
  74. import androidx.compose.material3.Typography class Typography( val displayLarge: TextStyle, val displayMedium: TextStyle,

    val displaySmall: TextStyle, // headlineLarge, titleMedium, bodySmall, etc. ) Typography
  75. Resources • Deep dive into Compose Layouts: https://youtu.be/zMKMwh9gZuI • Lazy

    layouts in Compose: https://youtu.be/1ANt65eoNhQ • Material You using Jetpack Compose: https://youtu.be/jrfuHyMlehc • Compose layout codelab: d.android.com/codelabs/jetpack-compose-layouts