[go: nahoru, domu]

blob: 70925649aebbda81a8d5cc5aa3aa7c4ea7283bfb [file] [log] [blame]
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:SuppressLint("FrequentlyChangedStateReadInComposition")
package androidx.compose.foundation.demos
import android.annotation.SuppressLint
import androidx.compose.animation.core.AnimationConstants
import androidx.compose.animation.core.AnimationState
import androidx.compose.animation.core.animateTo
import androidx.compose.animation.core.calculateTargetValue
import androidx.compose.animation.rememberSplineBasedDecay
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.FlingBehavior
import androidx.compose.foundation.gestures.ScrollScope
import androidx.compose.foundation.gestures.animateScrollBy
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.interaction.collectIsDraggedAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan
import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.samples.StickyHeaderSample
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.integration.demos.common.ComposableDemo
import androidx.compose.material.Button
import androidx.compose.material.Checkbox
import androidx.compose.material.DropdownMenu
import androidx.compose.material.DropdownMenuItem
import androidx.compose.material.Icon
import androidx.compose.material.LocalTextStyle
import androidx.compose.material.OutlinedButton
import androidx.compose.material.RadioButton
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.paging.compose.demos.PagingDemos
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.random.Random
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.launch
val LazyListDemos = listOf(
ComposableDemo("Simple column") { LazyColumnDemo() },
ComposableDemo("Add/remove items") { ListAddRemoveItemsDemo() },
ComposableDemo("Hoisted state") { ListHoistedStateDemo() },
ComposableDemo("Horizontal list") { LazyRowItemsDemo() },
ComposableDemo("List with indexes") { ListWithIndexSample() },
ComposableDemo("Pager-like list") { PagerLikeDemo() },
ComposableDemo("Rtl list") { RtlListDemo() },
ComposableDemo("LazyColumn DSL") { LazyColumnScope() },
ComposableDemo("LazyRow DSL") { LazyRowScope() },
ComposableDemo("LazyColumn with sticky headers") { StickyHeaderSample() },
ComposableDemo("Arrangements") { LazyListArrangements() },
ComposableDemo("ReverseLayout and RTL") { ReverseLayoutAndRtlDemo() },
ComposableDemo("Nested lazy lists") { NestedLazyDemo() },
ComposableDemo("LazyGrid") { LazyGridDemo() },
ComposableDemo("LazyGrid with Spacing") { LazyGridWithSpacingDemo() },
ComposableDemo("Custom keys") { ReorderWithCustomKeys() },
ComposableDemo("Fling Config") { LazyWithFlingConfig() },
ComposableDemo("Item reordering") { PopularBooksDemo() },
ComposableDemo("List drag and drop") { LazyColumnDragAndDropDemo() },
ComposableDemo("Grid drag and drop") { LazyGridDragAndDropDemo() },
ComposableDemo("Staggered grid") { LazyStaggeredGridDemo() },
ComposableDemo("Animate item placement") { AnimateItemPlacementDemo() },
PagingDemos
)
@Preview
@Composable
private fun LazyColumnDemo() {
LazyColumn {
items(
items = listOf(
"Hello,", "World:", "It works!", "",
"this one is really long and spans a few lines for scrolling purposes",
"these", "are", "offscreen"
)
) {
Text(text = it, fontSize = 80.sp)
if (it.contains("works")) {
Text("You can even emit multiple components per item.")
}
}
items(100) {
Text(text = "$it", fontSize = 80.sp)
}
}
}
@Preview
@Composable
private fun ListAddRemoveItemsDemo() {
var numItems by remember { mutableStateOf(0) }
var offset by remember { mutableStateOf(0) }
Column {
Row {
val buttonModifier = Modifier.padding(8.dp)
Button(modifier = buttonModifier, onClick = { numItems++ }) { Text("Add") }
Button(modifier = buttonModifier, onClick = { numItems-- }) { Text("Remove") }
Button(modifier = buttonModifier, onClick = { offset++ }) { Text("Offset") }
}
LazyColumn(Modifier.fillMaxWidth()) {
items((1..numItems).map { it + offset }) {
Text("$it", style = LocalTextStyle.current.copy(fontSize = 40.sp))
}
}
}
}
@Preview
@Composable
private fun ListHoistedStateDemo() {
val state = rememberLazyListState()
var lastScrollDescription: String by remember { mutableStateOf("") }
Column {
val numItems = 10000
Row {
val buttonModifier = Modifier.padding(8.dp)
val density = LocalDensity.current
val coroutineScope = rememberCoroutineScope()
Button(
modifier = buttonModifier,
onClick = {
coroutineScope.launch {
state.scrollToItem(state.firstVisibleItemIndex - 1)
}
}
) {
Text("Previous")
}
Button(
modifier = buttonModifier,
onClick = {
coroutineScope.launch {
state.scrollToItem(state.firstVisibleItemIndex + 1)
}
}
) {
Text("Next")
}
Button(
modifier = buttonModifier,
onClick = {
coroutineScope.launch {
val index = min(state.firstVisibleItemIndex + 500, numItems - 1)
state.animateScrollToItem(index)
}
}
) {
Text("+500")
}
Button(
modifier = buttonModifier,
onClick = {
coroutineScope.launch {
val index = max(state.firstVisibleItemIndex - 500, 0)
state.animateScrollToItem(index)
}
}
) {
Text("-500")
}
Button(
modifier = buttonModifier,
onClick = {
coroutineScope.launch {
state.animateScrollToItem(
state.firstVisibleItemIndex,
500
)
}
}
) {
Text("Offset")
}
Button(
modifier = buttonModifier,
onClick = {
with(density) {
coroutineScope.launch {
val requestedScroll = 10000.dp.toPx()
lastScrollDescription = try {
val actualScroll = state.animateScrollBy(requestedScroll)
"$actualScroll/$requestedScroll px"
} catch (_: CancellationException) {
"Interrupted!"
}
}
}
}
) {
Text("Scroll")
}
}
Column {
Text(
"First item: ${state.firstVisibleItemIndex}, Last scroll: $lastScrollDescription",
fontSize = 20.sp
)
Text(
"Dragging: ${state.interactionSource.collectIsDraggedAsState().value}, " +
"Flinging: ${state.isScrollInProgress}",
fontSize = 20.sp
)
}
LazyColumn(
Modifier.fillMaxWidth(),
state = state
) {
items(numItems) {
Text("$it", style = LocalTextStyle.current.copy(fontSize = 40.sp))
}
}
}
}
@Preview
@Composable
private fun LazyRowItemsDemo() {
LazyRow {
items(1000) {
Square(it)
}
}
}
@Composable
private fun Square(index: Int) {
val width = remember { Random.nextInt(50, 150).dp }
Box(
Modifier
.width(width)
.fillMaxHeight()
.background(colors[index % colors.size]),
contentAlignment = Alignment.Center
) {
Text(index.toString())
}
}
@Composable
private fun ListWithIndexSample() {
val friends = listOf("Alex", "John", "Danny", "Sam")
Column {
LazyRow(Modifier.fillMaxWidth()) {
itemsIndexed(friends) { index, friend ->
Text("$friend at index $index", Modifier.padding(16.dp))
}
}
LazyColumn(Modifier.fillMaxWidth()) {
itemsIndexed(friends) { index, friend ->
Text("$friend at index $index", Modifier.padding(16.dp))
}
}
}
}
@Composable
private fun RtlListDemo() {
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
LazyRow(Modifier.fillMaxWidth()) {
items(100) {
Text(
"$it",
Modifier
.requiredSize(100.dp)
.background(if (it % 2 == 0) Color.LightGray else Color.Transparent)
.padding(16.dp)
)
}
}
}
}
@Composable
private fun PagerLikeDemo() {
val pages = listOf(Color.LightGray, Color.White, Color.DarkGray)
LazyRow {
items(pages) {
Spacer(
Modifier
.fillParentMaxSize()
.background(it))
}
}
}
private val colors = listOf(
Color(0xFFffd7d7.toInt()),
Color(0xFFffe9d6.toInt()),
Color(0xFFfffbd0.toInt()),
Color(0xFFe3ffd9.toInt()),
Color(0xFFd0fff8.toInt())
)
@Composable
private fun LazyColumnScope() {
LazyColumn {
items(10) {
Text("$it", fontSize = 40.sp)
}
item {
Text("Single item", fontSize = 40.sp)
}
val items = listOf("A", "B", "C")
itemsIndexed(items) { index, item ->
Text("Item $item has index $index", fontSize = 40.sp)
}
}
}
@Composable
private fun LazyRowScope() {
LazyRow {
items(10) {
Text("$it", fontSize = 40.sp)
}
item {
Text("Single item", fontSize = 40.sp)
}
val items = listOf(Color.Cyan, Color.Blue, Color.Magenta)
itemsIndexed(items) { index, item ->
Box(
modifier = Modifier
.background(item)
.requiredSize(40.dp),
contentAlignment = Alignment.Center
) {
Text("$index", fontSize = 30.sp)
}
}
}
}
@Composable
private fun LazyListArrangements() {
var count by remember { mutableStateOf(3) }
var arrangement by remember { mutableStateOf(6) }
Column {
Row {
Button(onClick = { count-- }) {
Text("--")
}
Button(onClick = { count++ }) {
Text("++")
}
Button(
onClick = {
arrangement++
if (arrangement == Arrangements.size) {
arrangement = 0
}
}
) {
Text("Next")
}
Text("$arrangement ${Arrangements[arrangement]}")
}
Row {
val item = @Composable {
Box(
Modifier
.requiredHeight(200.dp)
.fillMaxWidth()
.background(Color.Red)
.border(1.dp, Color.Cyan)
)
}
Column(
verticalArrangement = Arrangements[arrangement],
modifier = Modifier
.weight(1f)
.fillMaxHeight()
.verticalScroll(rememberScrollState())
) {
repeat(count) {
item()
}
}
LazyColumn(
verticalArrangement = Arrangements[arrangement],
modifier = Modifier
.weight(1f)
.fillMaxHeight()
) {
items(count) {
item()
}
}
}
}
}
private val Arrangements = listOf(
Arrangement.Center,
Arrangement.Top,
Arrangement.Bottom,
Arrangement.SpaceAround,
Arrangement.SpaceBetween,
Arrangement.SpaceEvenly,
Arrangement.spacedBy(40.dp),
Arrangement.spacedBy(40.dp, Alignment.Bottom),
)
@Composable
private fun ReverseLayoutAndRtlDemo() {
val backgroundColor = Color(1f, .8f, .8f)
Column {
val scrollState = rememberScrollState()
val lazyState = rememberLazyListState()
var count by remember { mutableStateOf(10) }
var reverse by remember { mutableStateOf(false) }
var rtl by remember { mutableStateOf(false) }
var column by remember { mutableStateOf(true) }
val direction = if (rtl) LayoutDirection.Rtl else LayoutDirection.Ltr
Row(
modifier = Modifier.padding(10.dp),
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalAlignment = Alignment.CenterVertically
) {
Button(onClick = { count = max(0, count - 5) }) {
Text("--")
}
Button(onClick = { count += 5 }) {
Text("++")
}
Column {
Row {
Checkbox(checked = reverse, onCheckedChange = { reverse = it })
Text("reverse")
}
Row {
Checkbox(checked = rtl, onCheckedChange = { rtl = it })
Text("RTL")
}
}
Column {
Row {
RadioButton(selected = column, { column = true })
Text("Cols")
}
Row {
RadioButton(selected = !column, { column = false })
Text("Rows")
}
}
}
val itemModifier = if (column) {
Modifier
.heightIn(200.dp)
.fillMaxWidth()
} else {
Modifier
.widthIn(200.dp)
.fillMaxHeight()
}
val item1 = @Composable { index: Int ->
Text(
"${index}A",
itemModifier
.background(backgroundColor)
.border(1.dp, Color.Cyan)
)
}
val item2 = @Composable { index: Int ->
Text("${index}B")
}
@Composable
fun NonLazyContent() {
if (reverse) {
(count - 1 downTo 0).forEach {
item2(it)
item1(it)
}
} else {
(0 until count).forEach {
item1(it)
item2(it)
}
}
}
val lazyContent: LazyListScope.() -> Unit = {
items(count) {
item1(it)
item2(it)
}
}
if (column) {
Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
Text("Column: scroll=${scrollState.value}", Modifier.weight(1f))
Text(
"LazyColumn: index=${lazyState.firstVisibleItemIndex}, " +
"offset=${lazyState.firstVisibleItemScrollOffset}",
Modifier.weight(1f)
)
}
Row {
CompositionLocalProvider(LocalLayoutDirection provides direction) {
Column(
verticalArrangement = if (reverse) Arrangement.Bottom else Arrangement.Top,
modifier = Modifier
.weight(1f)
.fillMaxHeight()
.verticalScroll(scrollState, reverseScrolling = reverse)
) {
NonLazyContent()
}
}
CompositionLocalProvider(LocalLayoutDirection provides direction) {
LazyColumn(
reverseLayout = reverse,
state = lazyState,
modifier = Modifier
.weight(1f)
.fillMaxHeight(),
content = lazyContent
)
}
}
} else {
Text("Row: scroll=${scrollState.value}")
CompositionLocalProvider(LocalLayoutDirection provides direction) {
Row(
horizontalArrangement = if (reverse) Arrangement.End else Arrangement.Start,
modifier = Modifier
.weight(1f)
.fillMaxWidth()
.horizontalScroll(scrollState, reverseScrolling = reverse)
) {
NonLazyContent()
}
}
Text(
"LazyRow: index=${lazyState.firstVisibleItemIndex}, " +
"offset=${lazyState.firstVisibleItemScrollOffset}"
)
CompositionLocalProvider(LocalLayoutDirection provides direction) {
LazyRow(
state = lazyState,
modifier = Modifier
.weight(1f)
.fillMaxWidth(),
reverseLayout = reverse,
content = lazyContent
)
}
}
}
}
@Composable
private fun NestedLazyDemo() {
val item = @Composable { index: Int ->
Box(
Modifier
.padding(16.dp)
.requiredSize(200.dp)
.background(Color.LightGray),
contentAlignment = Alignment.Center
) {
var state by rememberSaveable { mutableStateOf(0) }
Button(onClick = { state++ }) {
Text("Index=$index State=$state")
}
}
}
LazyColumn {
item {
LazyRow {
items(100) {
item(it)
}
}
}
items(100) {
item(it)
}
}
}
@Composable
private fun LazyGridDemo() {
val columnModes = listOf(
GridCells.Fixed(3),
GridCells.Adaptive(minSize = 60.dp)
)
var currentMode by remember { mutableStateOf(0) }
Column {
Button(
modifier = Modifier.wrapContentSize(),
onClick = {
currentMode = (currentMode + 1) % columnModes.size
}
) {
Text("Switch mode")
}
LazyGridForMode(columnModes[currentMode])
}
}
@Composable
private fun LazyGridForMode(mode: GridCells) {
LazyVerticalGrid(columns = mode) {
items(100) {
Text(
text = "$it",
fontSize = 20.sp,
modifier = Modifier
.background(Color.Gray.copy(alpha = (it % 10) / 10f))
.padding(8.dp)
)
}
}
}
@Composable
private fun LazyGridWithSpacingDemo() {
val columnModes = listOf(
GridCells.Fixed(3),
GridCells.Adaptive(minSize = 60.dp),
object : GridCells {
// columns widths have ratio 1:1:2:3
override fun Density.calculateCrossAxisCellSizes(
availableSize: Int,
spacing: Int,
): List<Int> {
val totalSlots = 1 + 1 + 2 + 3
val slotWidth = (availableSize - spacing * 3) / totalSlots
return listOf(slotWidth, slotWidth, slotWidth * 2, slotWidth * 3)
}
}
)
var currentMode by remember { mutableStateOf(0) }
var horizontalSpacing by remember { mutableStateOf(8) }
var horizontalSpacingExpanded by remember { mutableStateOf(false) }
var verticalSpacing by remember { mutableStateOf(8) }
var verticalSpacingExpanded by remember { mutableStateOf(false) }
Column {
Row {
Button(
modifier = Modifier.wrapContentSize(),
onClick = {
currentMode = (currentMode + 1) % columnModes.size
}
) {
Text("Switch mode")
}
Box {
OutlinedButton(
modifier = Modifier.wrapContentSize(),
onClick = { verticalSpacingExpanded = true }
) {
Text("Vertical:\n$verticalSpacing dp")
Icon(imageVector = Icons.Default.ArrowDropDown, contentDescription = "expand")
}
DropdownMenu(
expanded = verticalSpacingExpanded,
onDismissRequest = { verticalSpacingExpanded = false }
) {
DropdownMenuItem(
onClick = {
verticalSpacing = 0
verticalSpacingExpanded = false
}
) {
Text("None")
}
DropdownMenuItem(
onClick = {
verticalSpacing = 8
verticalSpacingExpanded = false
}
) {
Text("8 dp")
}
DropdownMenuItem(
onClick = {
verticalSpacing = 16
verticalSpacingExpanded = false
}
) {
Text("16 dp")
}
DropdownMenuItem(
onClick = {
verticalSpacing = 32
verticalSpacingExpanded = false
}
) {
Text("32 dp")
}
}
}
Box {
OutlinedButton(
modifier = Modifier.wrapContentSize(),
onClick = { horizontalSpacingExpanded = true }
) {
Text("Horizontal:\n$horizontalSpacing dp")
Icon(imageVector = Icons.Default.ArrowDropDown, contentDescription = "expand")
}
DropdownMenu(
expanded = horizontalSpacingExpanded,
onDismissRequest = { horizontalSpacingExpanded = false }
) {
DropdownMenuItem(
onClick = {
horizontalSpacing = 0
horizontalSpacingExpanded = false
}
) {
Text("None")
}
DropdownMenuItem(
onClick = {
horizontalSpacing = 8
horizontalSpacingExpanded = false
}
) {
Text("8 dp")
}
DropdownMenuItem(
onClick = {
horizontalSpacing = 16
horizontalSpacingExpanded = false
}
) {
Text("16 dp")
}
DropdownMenuItem(
onClick = {
horizontalSpacing = 32
horizontalSpacingExpanded = false
}
) {
Text("32 dp")
}
}
}
}
LazyGridWithSpacingForMode(
mode = columnModes[currentMode],
horizontalSpacing = horizontalSpacing.dp,
verticalSpacing = verticalSpacing.dp
)
}
}
@Composable
private fun LazyGridWithSpacingForMode(
mode: GridCells,
horizontalSpacing: Dp,
verticalSpacing: Dp
) {
LazyVerticalGrid(
columns = mode,
horizontalArrangement = Arrangement.spacedBy(horizontalSpacing),
verticalArrangement = Arrangement.spacedBy(verticalSpacing)
) {
items(100) {
Text(
text = "$it",
fontSize = 20.sp,
modifier = Modifier
.background(Color.Gray.copy(alpha = (it % 10) / 10f))
.padding(8.dp)
)
}
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun ReorderWithCustomKeys() {
var names by remember { mutableStateOf(listOf("John", "Sara", "Dan")) }
Column {
Button(onClick = { names = names.shuffled() }) {
Text("Shuffle")
}
LazyColumn {
item {
var counter by rememberSaveable { mutableStateOf(0) }
Button(onClick = { counter++ }) {
Text("Header has $counter")
}
}
items(
items = names,
key = { it }
) {
var counter by rememberSaveable { mutableStateOf(0) }
Button(onClick = { counter++ }, modifier = Modifier.animateItemPlacement()) {
Text("$it has $counter")
}
}
}
}
}
@Composable
private fun LazyWithFlingConfig() {
Column {
Text(
"Custom fling config will dance back and forth when you fling",
modifier = Modifier.padding(16.dp)
)
val defaultDecay = rememberSplineBasedDecay<Float>()
val flingConfig = remember {
object : FlingBehavior {
override suspend fun ScrollScope.performFling(initialVelocity: Float): Float {
val unspecifiedFrame = AnimationConstants.UnspecifiedTime
val target = defaultDecay.calculateTargetValue(0f, initialVelocity)
val perDance = target / 3
var velocityLeft = initialVelocity
var lastLeft = 0f
var lastFrameTime = unspecifiedFrame
while (abs(lastLeft) < 1f && abs(perDance) > 0) {
listOf(perDance * 3 / 4, -perDance * 1 / 4).forEach { toGo ->
if (abs(lastLeft) > 1f) return@forEach
var lastValue = 0f
AnimationState(
initialValue = 0f,
lastFrameTimeNanos = lastFrameTime
).animateTo(
targetValue = toGo,
sequentialAnimation = lastFrameTime != unspecifiedFrame
) {
val delta = value - lastValue
lastLeft = delta - scrollBy(delta)
lastValue = value
velocityLeft = this.velocity
lastFrameTime = this.lastFrameTimeNanos
if (abs(lastLeft) > 0.5f) this.cancelAnimation()
}
}
}
return velocityLeft
}
}
}
LazyColumn(
modifier = Modifier.fillMaxSize(),
flingBehavior = flingConfig
) {
items(100) {
Text(
text = "$it",
fontSize = 20.sp,
modifier = Modifier
.fillParentMaxWidth()
.background(Color.Gray.copy(alpha = it / 100f))
.border(1.dp, Color.Gray)
.padding(16.dp)
)
}
}
}
}
@OptIn(ExperimentalFoundationApi::class)
@Preview
@Composable
private fun LazyStaggeredGridDemo() {
val heights = remember {
List(100) {
(Random.nextInt(100) + 100).dp
}
}
val colors = remember {
List(100) {
Color.hsl(
Random.nextFloat() * 360,
.5f,
.65f
)
}
}
val indices = remember {
mutableStateOf(List(100) { it })
}
var count by remember { mutableStateOf(100) }
Column(Modifier.fillMaxSize()) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Button(onClick = { count++ }) { Text(text = "++") }
Button(onClick = {
indices.value = indices.value.toMutableList().apply { shuffle() }
}) { Text(text = "shuffle") }
Button(onClick = { if (count != 0) count-- }) { Text(text = "--") }
}
val state = rememberLazyStaggeredGridState(initialFirstVisibleItemIndex = 29)
LazyVerticalStaggeredGrid(
columns = StaggeredGridCells.Fixed(3),
modifier = Modifier.fillMaxSize(),
state = state,
contentPadding = PaddingValues(vertical = 30.dp, horizontal = 20.dp),
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalArrangement = Arrangement.spacedBy(10.dp),
content = {
items(
count,
span = {
if (it % 30 == 0)
StaggeredGridItemSpan.FullLine
else
StaggeredGridItemSpan.SingleLane
}
) {
var expanded by remember { mutableStateOf(false) }
val index = indices.value[it % indices.value.size]
val color = colors[index]
Box(
modifier = Modifier
.height(if (!expanded) heights[index] else heights[index] * 2)
.border(2.dp, color, RoundedCornerShape(5.dp))
.clickable {
expanded = !expanded
}
) {
Text(
"$it",
modifier = Modifier.align(Alignment.Center),
color = color,
fontSize = 36.sp
)
}
}
}
)
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun AnimateItemPlacementDemo() {
val items = remember { mutableStateListOf<Int>().apply {
repeat(20) { add(it) }
} }
val selectedIndexes = remember { mutableStateMapOf<Int, Boolean>() }
var reverse by remember { mutableStateOf(false) }
Column {
Row {
Button(onClick = {
selectedIndexes.entries.reversed().forEach { entry ->
if (entry.value) {
items.remove(entry.key)
items.add(items.size - 3, entry.key)
}
}
}) {
Text("MoveToEnd")
}
Button(onClick = {
selectedIndexes.clear()
}) {
Text("RmvSelected")
}
Button(onClick = {
reverse = !reverse
}) {
Text("Reverse=$reverse")
}
}
LazyColumn(
Modifier
.fillMaxWidth()
.weight(1f), reverseLayout = reverse) {
items(items, key = { it }) { item ->
val selected = selectedIndexes.getOrDefault(item, false)
val modifier = if (selected) Modifier.animateItemPlacement() else Modifier
var height by remember { mutableStateOf(40.dp) }
Row(
modifier
.padding(8.dp)
.fillMaxWidth()
.border(1.dp, Color.Black)
.clickable {
height = if (height == 40.dp) 120.dp else 40.dp
},
verticalAlignment = Alignment.CenterVertically
) {
Checkbox(checked = selected, onCheckedChange = {
selectedIndexes[item] = it
})
Spacer(
Modifier
.width(16.dp)
.height(height))
Text("Item $item")
}
}
}
var size by remember { mutableStateOf(40.dp) }
Box(
Modifier
.height(size)
.fillMaxWidth()
.border(1.dp, Color.DarkGray)
.clickable {
size = if (size == 40.dp) 350.dp else 40.dp
})
}
}