Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save andkulikov/c5cd7cd2b5b5bc2e74a9dc7255d2ebdf to your computer and use it in GitHub Desktop.
Save andkulikov/c5cd7cd2b5b5bc2e74a9dc7255d2ebdf to your computer and use it in GitHub Desktop.
Code from https://www.youtube.com/watch?v=GhAU3NJPl5E
/*
* Copyright 2021 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.
*/
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationEndReason
import androidx.compose.animation.core.VectorConverter
import androidx.compose.animation.core.exponentialDecay
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.drag
import androidx.compose.foundation.gestures.forEachGesture
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.input.pointer.util.VelocityTracker
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.round
import kotlinx.coroutines.launch
@Preview(showBackground = true)
@Composable
fun PointerInputDemo() {
val position = remember { Animatable(Offset(100f, 100f), Offset.VectorConverter) }
val scope = rememberCoroutineScope()
val smallBoxSize = 40.dp
Box(
Modifier
.fillMaxSize()
.pointerInput(position) {
val velocityTracker = VelocityTracker()
forEachGesture {
velocityTracker.resetTracking()
awaitPointerEventScope {
val down = awaitFirstDown()
scope.launch {
position.snapTo(down.position)
}
drag(down.id) {
velocityTracker.addPosition(it.uptimeMillis, it.position)
scope.launch {
position.snapTo(it.position)
}
}
val halfSizePx = smallBoxSize.toPx() / 2
position.updateBounds(
Offset(halfSizePx, halfSizePx),
Offset(size.width - halfSizePx, size.height - halfSizePx)
)
val velocity = velocityTracker.calculateVelocity()
scope.launch {
var initialVelocity = Offset(velocity.x, velocity.y)
do {
val result =
position.animateDecay(initialVelocity, exponentialDecay())
initialVelocity = result.endState.velocity
if (position.value.x == position.lowerBound?.x ||
position.value.x == position.upperBound?.x) {
initialVelocity = initialVelocity.copy(x = -initialVelocity.x)
}
if (position.value.y == position.lowerBound?.y ||
position.value.y == position.upperBound?.y) {
initialVelocity = initialVelocity.copy(y = -initialVelocity.y)
}
} while (result.endReason == AnimationEndReason.BoundReached)
}
}
}
}) {
Box(
Modifier
.offset {
val halfSizePx = smallBoxSize.roundToPx() / 2
position.value.round() - IntOffset(halfSizePx, halfSizePx)
}
.size(smallBoxSize)
.background(Color.DarkGray)
)
}
}
/*
* Copyright 2021 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.
*/
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.calculateTargetValue
import androidx.compose.animation.defaultDecayAnimationSpec
import androidx.compose.foundation.gestures.FlingBehavior
import androidx.compose.foundation.gestures.ScrollScope
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.Card
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@Preview(showBackground = true)
@Composable
fun SnappingLazyRowDemo() {
val state = rememberLazyListState()
LazyRow(
Modifier.fillMaxSize(),
state = state,
flingBehavior = rememberSnappingFlingBehavior(state)
) {
items(100) { index ->
Card(
Modifier
.padding(8.dp)
.size(200.dp)
) {
Text("Item $index")
}
}
}
}
@Composable
fun rememberSnappingFlingBehavior(state: LazyListState): FlingBehavior {
val flingSpec = defaultDecayAnimationSpec()
return remember {
object : FlingBehavior {
override suspend fun ScrollScope.performFling(initialVelocity: Float): Float {
val firstItem = state.layoutInfo.visibleItemsInfo.first()
val targetOffset = flingSpec.calculateTargetValue(
initialValue = -firstItem.offset.toFloat(),
initialVelocity = initialVelocity
)
val visibilityRate = targetOffset / firstItem.size
val scrollBy = if (visibilityRate > 0.5f) {
firstItem.size + firstItem.offset
} else {
firstItem.offset
}
var lastValue = 0f
Animatable(scrollBy.toFloat())
.animateTo(
scrollBy.toFloat(),
initialVelocity = initialVelocity
) {
val newDelta = value - lastValue
scrollBy(newDelta)
lastValue = value
}
return 0f
}
}
}
}
/*
* Copyright 2021 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.
*/
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.material.Card
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@Preview(showBackground = true)
@Composable
fun LazyRowInfinityItems() {
LazyRow(
Modifier.fillMaxSize(),
) {
infinityItems(100) { index ->
Card(
Modifier
.padding(8.dp)
.size(200.dp)
) {
Text("Item $index")
}
}
}
}
fun LazyListScope.infinityItems(
count: Int,
itemContent: @Composable LazyItemScope.(index: Int) -> Unit
) {
items(100000) { index ->
itemContent(index % count)
}
}
/*
* Copyright 2021 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.
*/
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import kotlin.math.roundToInt
@Preview(showBackground = true)
@Composable
fun CollapsingToolbar() {
MaterialTheme {
val toolbarHeight = 48.dp
val toolbarHeightPx = with(LocalDensity.current) { toolbarHeight.toPx() }
var toolbarOffsetPx by remember { mutableStateOf(0f) }
Box(
Modifier.nestedScroll(
remember {
object : NestedScrollConnection {
override fun onPreScroll(
available: Offset,
source: NestedScrollSource
): Offset {
val delta = available.y
val newOffset = toolbarOffsetPx + delta
toolbarOffsetPx = newOffset.coerceIn(-toolbarHeightPx, 0f)
return Offset.Zero
}
}
}
)
) {
LazyColumn(
Modifier.fillMaxSize(),
contentPadding = PaddingValues(top = toolbarHeight)
) {
items(100) {
Text("Item $it", Modifier.padding(16.dp))
}
}
TopAppBar(
title = { Text("Toolbar") },
Modifier
.height(toolbarHeight)
.offset { IntOffset(0, toolbarOffsetPx.roundToInt()) }
)
}
}
}
/*
* Copyright 2021 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.
*/
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@Preview(showBackground = true, widthDp = 800, name = "Tablet")
@Preview(showBackground = true, widthDp = 480, name = "Phone")
@Composable
fun AdaptiveLayoutDemo() {
var currentItem by remember { mutableStateOf(-1) }
BoxWithConstraints(Modifier.fillMaxSize()) {
if (maxWidth > 720.dp) {
Row {
ListScreen(
Modifier
.fillMaxSize()
.weight(1f), onItemClicked = { currentItem = it })
ItemScreen(
Modifier
.fillMaxSize()
.weight(1f), currentItem
)
}
} else {
if (currentItem == -1) {
ListScreen(Modifier.fillMaxSize(), onItemClicked = { currentItem = it })
} else {
ItemScreen(Modifier.fillMaxSize(), currentItem)
}
}
}
}
@Composable
fun ItemScreen(modifier: Modifier, currentItem: Int) {
Text("Details for $currentItem", modifier, fontSize = 20.sp)
}
@Composable
fun ListScreen(modifier: Modifier, onItemClicked: (Int) -> Unit) {
LazyColumn(modifier) {
items(100) {
Text(
"Item $it",
Modifier
.fillMaxWidth()
.clickable { onItemClicked(it) }
.padding(16.dp)
)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment