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

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

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

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