我想在撰写中复制 Google 周日历视图,但卡在滚动上。我使用
detectTransformGestures()
来缩放和翻译内容,但是一旦我松开手指,翻译就会停止。您将如何添加滚动行为,一旦您放手,该行为就会继续滚动(就像普通的 Recyclerview 或 compose 中的 .scrolling()
一样)。
我找到了https://github.com/alamkanak/Android-Week-View,但似乎过于复杂并且在xml中使用。我想要更干净、更容易使用的东西。
这是我当前的代码。内容还不限于顶部和底部,您可以根据需要缩放。
@Composable
fun WeekCalendar() {
var scaleY by remember {
mutableStateOf(1f)
}
var offset by remember {
mutableStateOf(0f)
}
var centroi by remember {
mutableStateOf(0f)
}
Canvas(modifier = Modifier
.pointerInput(Unit) {
this.detectTransformGestures() { centroid, pan, zoom, rotation ->
scaleY *= zoom
offset += pan.y * zoom
// center zooming around point location
offset += (zoom - 1) * (offset - centroid.y)
centroi = centroid.y
}
}
.fillMaxWidth()
.height(1200.dp)
) {
val bottom = size.height * scaleY + offset
val top = offset
drawLine(
color = Color.Red,
start = Offset(0f, top),
end = Offset(size.width, top),
)
drawLine(
color = Color.Red,
start = Offset(size.width, top),
end = Offset(size.width, bottom),
)
drawLine(
color = Color.Red,
start = Offset(size.width, bottom),
end = Offset(0f, bottom),
)
drawLine(
color = Color.Red,
start = Offset(0f, bottom),
end = Offset(0f, top),
)
drawLine(
color = Color.Red,
start = Offset(0f, top),
end = Offset(size.width, bottom),
)
drawPoints(listOf(Offset(size.width / 2, centroi)), PointMode.Points, color = Color.Blue, strokeWidth = 5f)
}
}
我不知道这是否仍然相关,但我找到了一种使用 2023.08.00 Compose BOM 和
remember()
来实现的方法。为了避免处理滚动逻辑,我使用了带有内置垂直滚动扩展的包装 Column
。缩放逻辑与您的相同,但在重组之间保持不变。
@Composable
fun CalendarScreenDemo(modifier: Modifier = Modifier) {
val baseHeight = 60.dp * 24
var scaleY by remember { mutableFloatStateOf(1f) }
val scrollState = rememberScrollState()
val coroutineScope = rememberCoroutineScope()
Column(
modifier = modifier.verticalScroll(scrollState)
) {
Canvas(
modifier = modifier
.height(baseHeight * scaleY)
.fillMaxWidth()
.pointerInput(Unit) {
detectZoomChanges zoom@{ centroid, zoom ->
val oldScaleY = scaleY
// Constrain min/max zoom
scaleY = (scaleY * zoom).coerceIn(0.75f..2f)
// Don't move scroll position if no effective zoom occured
val actualZoom = scaleY / oldScaleY
val scrollY = scrollState.value * actualZoom
// Center zooming around gesture origin
val scrollOffset = (zoom - 1) * (scrollY - centroid.y)
coroutineScope.launch {
scrollState.scrollTo(scrollY.roundToInt() - scrollOffset.roundToInt())
}
}
},
) {
val gradient = Brush.verticalGradient(
listOf(Color.Red, Color.Blue, Color.Green),
0.0f,
(baseHeight * scaleY).toPx(),
TileMode.Repeated
)
drawRect(gradient)
}
}
}
这与 Google 日历的缩放功能类似,例如当达到最大/最小缩放时,进一步的缩放手势将滚动屏幕。
detectZoomChanges()
函数基于内置函数detectTransformgestures()
,稍作修改以仅消耗缩放事件。我刚刚删除了会消耗平移和旋转事件的部分。这样,平移事件仍然可以向上冒泡到周围的列,并作为滚动手势进行处理。 (缩放时仍然需要手动调整滚动位置,但至少它可以工作。)
suspend fun PointerInputScope.detectZoomChanges(
onGesture: (centroid: Offset, zoom: Float) -> Unit
) {
awaitEachGesture {
var zoom = 1f
var pastTouchSlop = false
val touchSlop = viewConfiguration.touchSlop
awaitFirstDown(requireUnconsumed = false)
do {
val event = awaitPointerEvent()
val canceled = event.changes.fastAny { it.isConsumed }
if (!canceled) {
val zoomChange = event.calculateZoom()
if (!pastTouchSlop) {
zoom *= zoomChange
val centroidSize = event.calculateCentroidSize(useCurrent = false)
val zoomMotion = abs(1 - zoom) * centroidSize
if (zoomMotion > touchSlop) {
pastTouchSlop = true
}
}
if (pastTouchSlop) {
val centroid = event.calculateCentroid(useCurrent = false)
if (zoomChange != 1f) {
onGesture(centroid, zoomChange){
event.changes.fastForEach {
if (it.positionChanged()) {
it.consume()
}
}
}
}
}
} while (!canceled && event.changes.fastAny { it.pressed })
}
}