Created
June 2, 2025 18:28
-
-
Save kokeroulis/c7757c205fe3260274a08932b2ed58d0 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import androidx.compose.foundation.background | |
import androidx.compose.foundation.gestures.AnchoredDraggableDefaults | |
import androidx.compose.foundation.gestures.AnchoredDraggableState | |
import androidx.compose.foundation.gestures.DraggableAnchors | |
import androidx.compose.foundation.gestures.Orientation | |
import androidx.compose.foundation.gestures.anchoredDraggable | |
import androidx.compose.foundation.gestures.forEach | |
import androidx.compose.foundation.layout.Box | |
import androidx.compose.foundation.layout.Column | |
import androidx.compose.foundation.layout.fillMaxWidth | |
import androidx.compose.foundation.layout.height | |
import androidx.compose.foundation.layout.offset | |
import androidx.compose.foundation.layout.size | |
import androidx.compose.foundation.lazy.LazyColumn | |
import androidx.compose.foundation.lazy.items | |
import androidx.compose.material3.Text | |
import androidx.compose.runtime.Composable | |
import androidx.compose.runtime.SideEffect | |
import androidx.compose.runtime.remember | |
import androidx.compose.runtime.saveable.rememberSaveable | |
import androidx.compose.ui.Modifier | |
import androidx.compose.ui.draw.drawWithContent | |
import androidx.compose.ui.geometry.Offset | |
import androidx.compose.ui.graphics.Color | |
import androidx.compose.ui.graphics.PathEffect | |
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 | |
enum class ExpansionState { | |
COLLAPSED, | |
HALF_COLLAPSE, | |
EXPANDED | |
} | |
@Preview | |
@Composable | |
fun DraggableAnchorsSample() { | |
val state = rememberSaveable(saver = AnchoredDraggableState.Saver()) { | |
AnchoredDraggableState(initialValue = ExpansionState.EXPANDED) | |
} | |
val singleDpToPx = with(LocalDensity.current) { 1.dp.toPx() } | |
SideEffect { | |
state.updateAnchors( | |
DraggableAnchors { | |
ExpansionState.EXPANDED at 300 * singleDpToPx | |
ExpansionState.HALF_COLLAPSE at 225 * singleDpToPx | |
ExpansionState.COLLAPSED at 150 * singleDpToPx | |
}, | |
) | |
} | |
Box( | |
Modifier | |
.fillMaxWidth() | |
.visualizeDraggableAnchors(state, Orientation.Vertical), | |
) { | |
// Header( | |
// anchorModifier = Modifier.anchoredDraggable( | |
// state = state, | |
// orientation = Orientation.Vertical, | |
// flingBehavior = | |
// AnchoredDraggableDefaults.flingBehavior( | |
// state, | |
// positionalThreshold = { distance -> distance * 0.25f }, | |
// ), | |
// ), | |
// offsetModifier = Modifier | |
// .offset { | |
// IntOffset( | |
// x = 0, y = state.requireOffset().roundToInt(), | |
// ) | |
// }, | |
// ) { | |
// val dp = with(LocalDensity.current) { | |
// state.offset.toDp() | |
// } | |
// Text("state $dp ${state.offset} ${state.targetValue} ${state.currentValue}") | |
// } | |
val connection = remember { DraggableConnection(state) } | |
val containerHeight = with(LocalDensity.current) { 150.dp.toPx().roundToInt() } | |
SampleList( | |
Modifier | |
// this is breaking it | |
.offset { IntOffset(0, 100.dp.roundToPx()) } | |
//.offset { IntOffset(0, y = containerHeight + state.requireOffset().roundToInt()) } | |
//.nestedScroll(connection), | |
) | |
} | |
} | |
@Composable | |
fun Header(anchorModifier: Modifier, offsetModifier: Modifier, content: @Composable () -> Unit) { | |
Box( | |
Modifier | |
.fillMaxWidth() | |
.height(450.dp) | |
// .then(anchorModifier), | |
) | |
{ | |
Box( | |
Modifier | |
.fillMaxWidth() | |
// .then(offsetModifier) | |
.height(150.dp) | |
.background(Color.Red), | |
) | |
Box( | |
Modifier | |
.fillMaxWidth() | |
.height(300.dp) | |
.background(Color.Blue), | |
) { | |
content() | |
} | |
} | |
} | |
class DraggableConnection( | |
private val anchorState: AnchoredDraggableState<ExpansionState> | |
) : NestedScrollConnection { | |
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { | |
val newFloat = anchorState.dispatchRawDelta(available.y) | |
return available.copy(y = newFloat) | |
} | |
} | |
@Composable | |
fun SampleList(modifier: Modifier = Modifier) { | |
val sampleItems = List(20) { "Item #$it" } | |
LazyColumn(modifier) { | |
items(sampleItems) { item -> | |
Box( | |
Modifier | |
.fillMaxWidth() | |
.height(50.dp) | |
.background(Color.Cyan), | |
) { | |
Text("ITEM: $item") | |
} | |
} | |
} | |
} | |
private fun Modifier.visualizeDraggableAnchors( | |
state: AnchoredDraggableState<*>, | |
orientation: Orientation, | |
lineColor: Color = Color.Black, | |
lineStrokeWidth: Float = 10f, | |
linePathEffect: PathEffect = PathEffect.dashPathEffect(floatArrayOf(20f, 30f)), | |
) = drawWithContent { | |
drawContent() | |
state.anchors.forEach { _, position -> | |
val startOffset = | |
Offset( | |
x = if (orientation == Orientation.Horizontal) position else 0f, | |
y = if (orientation == Orientation.Vertical) position else 0f, | |
) | |
val endOffset = | |
Offset( | |
x = if (orientation == Orientation.Horizontal) startOffset.x else size.height, | |
y = if (orientation == Orientation.Vertical) startOffset.y else size.width, | |
) | |
drawLine( | |
color = lineColor, | |
start = startOffset, | |
end = endOffset, | |
strokeWidth = lineStrokeWidth, | |
pathEffect = linePathEffect, | |
) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment