Slide 1

Slide 1 text

Ahmad Arif Faizin Curriculum Developer at Dicoding Indonesia @arif_faizin Deep Dive in Compose Layout, Lazy Layout, and Material Design 3

Slide 2

Slide 2 text

Deep dive in Compose Layout

Slide 3

Slide 3 text

Column Row Box Constraint Layout

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

Easy Powerful Performant

Slide 6

Slide 6 text

Layout Model

Slide 7

Slide 7 text

UI State

Slide 8

Slide 8 text

Layout UI tate Composition Drawing

Slide 9

Slide 9 text

Layout Drawing Composition

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

Drawing Composition Layout

Slide 13

Slide 13 text

Composition Layout Drawing

Slide 14

Slide 14 text

Drawing Composition Layout

Slide 15

Slide 15 text

Drawing Composition Layout Measure Place

Slide 16

Slide 16 text

Measure children Drawing Composition Layout Decide own size Place children

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

@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

Slide 23

Slide 23 text

@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

Slide 24

Slide 24 text

@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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

@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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

@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

Slide 29

Slide 29 text

Modifiers

Slide 30

Slide 30 text

Box( modifier = Modifier .fillMaxSize() .wrapContentSize() .size(50.dp) .background(Color.Blue) )

Slide 31

Slide 31 text

Box( modifier = Modifier .size(50.dp) .background(Color.Blue) )

Slide 32

Slide 32 text

Box( modifier = Modifier .wrapContentSize(align = Alignment.Center) .size(50.dp) .background(Color.Blue) )

Slide 33

Slide 33 text

Box( modifier = Modifier .wrapContentSize() .size(50.dp) .background(Color.Blue) )

Slide 34

Slide 34 text

Box( modifier = Modifier .fillMaxSize() .wrapContentSize() .size(50.dp) .background(Color.Blue) )

Slide 35

Slide 35 text

Box( modifier = Modifier .fillMaxSize() .wrapContentSize() .size(50.dp) .background(Color.Blue) )

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

Modifier order is matter!

Slide 53

Slide 53 text

No content

Slide 54

Slide 54 text

Advanced features

Slide 55

Slide 55 text

Intrinsic Measurement

Slide 56

Slide 56 text

Single Pass? Need to know all children's sizes before children assign the size. Not Always

Slide 57

Slide 57 text

Column { Text() Text() Text() Text() Text() } Menu the easy way

Slide 58

Slide 58 text

Column { Text() Text() Text() Text() Text() } Menu the easy way

Slide 59

Slide 59 text

Column { Text(Modifier.fillMaxWidth()) Text(Modifier.fillMaxWidth()) Text(Modifier.fillMaxWidth()) Text(Modifier.fillMaxWidth()) Text(Modifier.fillMaxWidth()) } Try to fix that

Slide 60

Slide 60 text

Column(Modifier.width(IntrinsicSize.Max)) { Text(Modifier.fillMaxWidth()) Text(Modifier.fillMaxWidth()) Text(Modifier.fillMaxWidth()) Text(Modifier.fillMaxWidth()) Text(Modifier.fillMaxWidth()) } Use Intrinsic Size

Slide 61

Slide 61 text

Column(Modifier.width(IntrinsicSize.Min)) { Text(Modifier.fillMaxWidth()) Text(Modifier.fillMaxWidth()) Text(Modifier.fillMaxWidth()) Text(Modifier.fillMaxWidth()) Text(Modifier.fillMaxWidth()) } Use Intrinsic Size (Min)

Slide 62

Slide 62 text

ParentData

Slide 63

Slide 63 text

Box(Modifier.size(200.dp, 300.dp)) { Box( modifier = Modifier .fillMaxSize() .wrapContentSize() .size(50.dp) .background(Color.Blue) ) }

Slide 64

Slide 64 text

Box(Modifier.size(200.dp, 300.dp)) { Box( modifier = Modifier .fillMaxSize() .wrapContentSize() .size(50.dp) .background(Color.Blue) ) }

Slide 65

Slide 65 text

@Composable inline fun Box( modifier: Modifier = Modifier, contentAlignment: Alignment = Alignment.TopStart, propagateMinConstraints: Boolean = false, content: @Composable BoxScope.() -> Unit ) { ... }

Slide 66

Slide 66 text

interface BoxScope { @Stable fun Modifier.align(alignment: Alignment): Modifier @Stable fun Modifier.matchParentSize(): Modifier }

Slide 67

Slide 67 text

Box(Modifier.size(200.dp, 300.dp)) { // this: BoxScope Box( modifier = Modifier .fillMaxSize() .wrapContentSize() .size(50.dp) .background(Color.Blue) ) }

Slide 68

Slide 68 text

Box(Modifier.size(200.dp, 300.dp)) { Box( modifier = Modifier .align(Alignment.Center) .size(50.dp) .background(Color.Blue) ) }

Slide 69

Slide 69 text

fun MyCustomLayout( modifier: Modifier = Modifier, content: @Composable () -> Unit ) { Layout( modifier = modifier, content = content ) { measurables: List, 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 } }

Slide 70

Slide 70 text

Lazy layouts in Compose

Slide 71

Slide 71 text

LazyColumn LazyRow Lazy grids DONE Option 2

Slide 72

Slide 72 text

class FlowersAdapter(private val onClick: (Flower) -> Unit) : ListAdapter(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) { ... } }

Slide 73

Slide 73 text

Slide 74

Slide 74 text

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

@Composable fun FlowerList(flowers: List) { LazyColumn { items(flowers) { flower -> FlowerItem(flower) } } } @Composable fun FlowerItem(flower: Flower) { Column { Image(flower.image) Text(flower.name) } }

Slide 77

Slide 77 text

LazyColumn { // LazyListScope block item { Text(header) } items(data) { item -> Item(item) } }

Slide 78

Slide 78 text

LazyColumn { // LazyListScope block item { Text(header) } items(data) { item -> Item(item) } }

Slide 79

Slide 79 text

LazyColumn { // LazyListScope block item { Text(header) } items(data) { item -> Item(item) } }

Slide 80

Slide 80 text

LazyColumn { // LazyListScope block item { Text(header) } itemsIndexed(data) { index, item -> Item(item, index) } } 1 2 3 4

Slide 81

Slide 81 text

LazyRow { items(data) { item -> Item(item) } }

Slide 82

Slide 82 text

LazyRow { items(data) { item -> Item(item) } }

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

LazyRow { items(data) { item -> Item(item) } }

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

LazyRow { items(data) { item -> Item(item) } }

Slide 88

Slide 88 text

LazyRow { items(data) { item -> Item(item) } }

Slide 89

Slide 89 text

LazyRow( horizontalArrangement = Arrangement.spacedBy(8.dp) ) { items(data) { item -> Item(item) } }

Slide 90

Slide 90 text

No content

Slide 91

Slide 91 text

LazyColumn( state = rememberLazyListState() ) { items(data) { item -> Item(item) } }

Slide 92

Slide 92 text

val state = rememberLazyListState() LazyColumn( state = state ) { items(data) { item -> Item(item) } }

Slide 93

Slide 93 text

2. 3. 4. 5. 1. val state = rememberLazyListState() state.firstVisibleItemIndex state.firstVisibleItemScrollOffset

Slide 94

Slide 94 text

val state = rememberLazyListState() val showScrollToTopButton by remember { derivedStateOf { state.firstVisibleItemIndex > 0 } }

Slide 95

Slide 95 text

Common Performance Gotchas in Jetpack Compose val state = rememberLazyListState() val showScrollToTopButton by remember { derivedStateOf { state.firstVisibleItemIndex > 0 } }

Slide 96

Slide 96 text

state.layoutInfo.visibleItemsInfo state.layoutInfo.totalItemsCount state.layoutInfo.visibleItemsInfo .map { it.index }

Slide 97

Slide 97 text

state.layoutInfo.visibleItemsInfo state.layoutInfo.totalItemsCount state.layoutInfo.visibleItemsInfo .map { it.index } 2. 3. 4. 5. 1.

Slide 98

Slide 98 text

val state = rememberLazyListState() ScrollToTopButton( onClick = { } )

Slide 99

Slide 99 text

val state = rememberLazyListState() ScrollToTopButton( onClick = { // suspend function state.scrollToItem( index = 0 ) } )

Slide 100

Slide 100 text

val state = rememberLazyListState() ScrollToTopButton( onClick = { // suspend function state.animateScrollToItem( index = 0 ) } )

Slide 101

Slide 101 text

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

Slide 102

Slide 102 text

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

Slide 103

Slide 103 text

Stable in Compose 1.2 Lazy grids

Slide 104

Slide 104 text

LazyHorizontalGrid LazyVerticalGrid

Slide 105

Slide 105 text

LazyVerticalGrid( columns = GridCells.Fixed(2) ) { items(data) { item -> Item(item) } }

Slide 106

Slide 106 text

LazyVerticalGrid( columns = GridCells.Fixed(2) ) { items(data) { item -> Item(item) } }

Slide 107

Slide 107 text

LazyVerticalGrid( columns = GridCells.Fixed(2), verticalArrangement = Arrangement.spacedBy(16.dp), horizontalArrangement = Arrangement.spacedBy(16.dp) ) { items(data) { item -> Item(item) } }

Slide 108

Slide 108 text

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

Slide 109

Slide 109 text

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

Slide 110

Slide 110 text

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(...)

Slide 111

Slide 111 text

Available width LazyVerticalGrid( columns = GridCells.Fixed(2) ) { ... }

Slide 112

Slide 112 text

LazyVerticalGrid( columns = GridCells.Fixed(2), horizontalArrangement = Arrangements.spacedBy(24.dp) ) { ... } Available width Spacing

Slide 113

Slide 113 text

LazyVerticalGrid( columns = GridCells.Fixed(2), horizontalArrangement = Arrangement.spacedBy(24.dp), verticalArrangement = Arrangement.spacedBy(24.dp) ) { items(plants) { plant -> PlantCard(plant) } }

Slide 114

Slide 114 text

LazyVerticalGrid( columns = GridCells.Fixed(2), horizontalArrangement = Arrangement.spacedBy(24.dp), verticalArrangement = Arrangement.spacedBy(24.dp) ) { items(plants) { plant -> PlantCard(plant) } }

Slide 115

Slide 115 text

LazyVerticalGrid( columns = GridCells.Adaptive(128.dp), horizontalArrangement = Arrangement.spacedBy(24.dp), verticalArrangement = Arrangement.spacedBy(24.dp) ) { items(plants) { plant -> PlantCard(plant) } }

Slide 116

Slide 116 text

LazyVerticalGrid( columns = object : GridCells { ... } )

Slide 117

Slide 117 text

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

Slide 118

Slide 118 text

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

Slide 119

Slide 119 text

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

Slide 120

Slide 120 text

Available width Spacing LazyVerticalGrid( columns = object : GridCells { override fun Density.calculateCrossAxisCellSizes( availableSize: Int, spacing: Int ): List { ... } } )

Slide 121

Slide 121 text

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

Slide 122

Slide 122 text

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

Slide 123

Slide 123 text

LazyVerticalGrid( ... ) { item(span = { GridItemSpan(maxLineSpan) }) { CategoryCard(“Fruits”) } ... }

Slide 124

Slide 124 text

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

Slide 125

Slide 125 text

LazyVerticalGrid( ... ) { item(span = { GridItemSpan(maxLineSpan) }) { CategoryCard(“Fruits”) } items(fruitPlants) { plant -> PlantCard(plant) } }

Slide 126

Slide 126 text

Item animations

Slide 127

Slide 127 text

No content

Slide 128

Slide 128 text

No content

Slide 129

Slide 129 text

LazyColumn { items(books, key = { it.id }) { Row(Modifier.animateItemPlacement()) { ... } } }

Slide 130

Slide 130 text

LazyColumn { items(books, key = { it.id }) { Row(Modifier.animateItemPlacement( tween(durationMillis = 250) )) { ... } } }

Slide 131

Slide 131 text

LazyColumn { items(books, key = { it.id }) { ... } }

Slide 132

Slide 132 text

To make the most out of your lists Lazy tips

Slide 133

Slide 133 text

Tip #1 Don’t use 0-pixel sized items

Slide 134

Slide 134 text

134

Slide 135

Slide 135 text

135

Slide 136

Slide 136 text

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

Slide 137

Slide 137 text

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

Slide 138

Slide 138 text

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

Slide 139

Slide 139 text

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

Slide 140

Slide 140 text

@Composable fun Item() { Image( painter = rememberImagePainter( data = imageUrl ), ... ) } DONE

Slide 141

Slide 141 text

@Composable fun Item() { Image( painter = rememberImagePainter( data = imageUrl ), ... ) }

Slide 142

Slide 142 text

@Composable fun Item() { Image( painter = rememberImagePainter( data = imageUrl ), modifier = Modifier.size(30.dp), ... ) }

Slide 143

Slide 143 text

Adobe Stock#243026154

Slide 144

Slide 144 text

Tip #2 Avoid nesting components scrollable in the same direction

Slide 145

Slide 145 text

// Throws IllegalStateException Column( modifier = Modifier.verticalScroll(state) ) { LazyColumn { ... } }

Slide 146

Slide 146 text

... ...

Slide 147

Slide 147 text

DONE ... ...

Slide 148

Slide 148 text

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

Slide 149

Slide 149 text

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

Slide 150

Slide 150 text

val scrollState = rememberScrollState() Row( modifier = Modifier.horizontalScroll(scrollState) ) { LazyColumn { ... } }

Slide 151

Slide 151 text

val scrollState = rememberScrollState() Column( modifier = Modifier.verticalScroll(scrollState) ) { LazyColumn( modifier = Modifier.height(200.dp) ) { ... } }

Slide 152

Slide 152 text

Tip #3 Beware of putting multiple elements in one item

Slide 153

Slide 153 text

0 1 2 3 LazyVerticalGrid( ... ) { items(count) { index -> Item(index) } }

Slide 154

Slide 154 text

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

Slide 155

Slide 155 text

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

Slide 156

Slide 156 text

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

Slide 157

Slide 157 text

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

Slide 158

Slide 158 text

0 1 2 LazyVerticalGrid( ... ) { item { Item(0) } item { Item(1) Divider() } item { Item(2) } ... }

Slide 159

Slide 159 text

Tip #4 Consider using custom arrangements

Slide 160

Slide 160 text

0 3 - Footer 1 2

Slide 161

Slide 161 text

LazyColumn( modifier = Modifier.fillMaxHeight(), verticalArrangement = TopWithFooter ) { ... } object TopWithFooter : Arrangement.Vertical { ... } 0 3 - Footer 1 2

Slide 162

Slide 162 text

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

Slide 163

Slide 163 text

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

Slide 164

Slide 164 text

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

Slide 165

Slide 165 text

Material Design

Slide 166

Slide 166 text

No content

Slide 167

Slide 167 text

No content

Slide 168

Slide 168 text

No content

Slide 169

Slide 169 text

Material Theming Theming

Slide 170

Slide 170 text

import androidx.compose.material3.MaterialTheme @Composable fun MaterialTheme( colorScheme: ColorScheme, typography: Typography, // Updates to Shapes coming soon content: @Composable () -> Unit ) MaterialTheme Material 3

Slide 171

Slide 171 text

Color scheme Theming

Slide 172

Slide 172 text

New to M3

Slide 173

Slide 173 text

No content

Slide 174

Slide 174 text

primary onPrimary primaryContainer onPrimaryContainer tertiary

Slide 175

Slide 175 text

@Composable fun Message(...) { val avatarBorderColor = if (isUserMe) { MaterialTheme.colorScheme.primary } else { MaterialTheme.colorScheme.tertiary } ... } Jetchat message

Slide 176

Slide 176 text

Dynamic color Theming

Slide 177

Slide 177 text

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

Slide 178

Slide 178 text

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

Slide 179

Slide 179 text

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

Slide 180

Slide 180 text

No content

Slide 181

Slide 181 text

Typography Theming

Slide 182

Slide 182 text

M3 M2

Slide 183

Slide 183 text

import androidx.compose.material3.Typography class Typography( val displayLarge: TextStyle, val displayMedium: TextStyle, val displaySmall: TextStyle, // headlineLarge, titleMedium, bodySmall, etc. ) Typography

Slide 184

Slide 184 text

labelSmall titleMedium bodyLarge

Slide 185

Slide 185 text

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

Slide 186

Slide 186 text

Thank you! Ahmad Arif Faizin Curriculum Developer at Dicoding Indonesia @arif_faizin