实现左右滑动切换图片的功能

This commit is contained in:
amos
2025-12-17 14:14:02 +08:00
parent 422d1d0b2d
commit d972f9140f

View File

@@ -4,7 +4,9 @@ import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.spring
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectTransformGestures
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
@@ -21,6 +23,7 @@ import androidx.compose.material.icons.filled.Verified
import androidx.compose.foundation.layout.heightIn
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.snapshots.SnapshotStateMap
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@@ -454,6 +457,12 @@ fun FullScreenImageViewer(
initialPage = initialIndex,
pageCount = { imageUrls.size }
)
// 跟踪当前页面是否处于缩放状态
val zoomStates = remember { mutableStateMapOf<Int, Float>() }
val isCurrentPageZoomed by remember {
derivedStateOf { (zoomStates[pagerState.currentPage] ?: 1f) > 1.1f }
}
Dialog(
onDismissRequest = onDismiss,
@@ -467,11 +476,14 @@ fun FullScreenImageViewer(
// 图片分页器
HorizontalPager(
state = pagerState,
modifier = Modifier.fillMaxSize()
modifier = Modifier.fillMaxSize(),
userScrollEnabled = !isCurrentPageZoomed,
key = { it }
) { page ->
ZoomableImage(
imageUrl = imageUrls[page],
onTap = onDismiss
onTap = onDismiss,
onScaleChanged = { scale -> zoomStates[page] = scale }
)
}
@@ -493,19 +505,27 @@ fun FullScreenImageViewer(
// 页码指示器 (多张图片时显示)
if (imageUrls.size > 1) {
Surface(
// 底部圆点指示器
Row(
modifier = Modifier
.align(Alignment.BottomCenter)
.padding(bottom = 32.dp),
shape = RoundedCornerShape(16.dp),
color = Color.Black.copy(alpha = 0.6f)
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = "${pagerState.currentPage + 1} / ${imageUrls.size}",
color = Color.White,
fontSize = 14.sp,
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)
)
repeat(imageUrls.size) { index ->
val isSelected = pagerState.currentPage == index
val color by animateColorAsState(
targetValue = if (isSelected) Color.White else Color.White.copy(alpha = 0.4f),
animationSpec = spring(),
label = "indicator"
)
Box(
modifier = Modifier
.size(if (isSelected) 8.dp else 6.dp)
.clip(CircleShape)
.background(color)
)
}
}
}
}
@@ -515,51 +535,87 @@ fun FullScreenImageViewer(
@Composable
private fun ZoomableImage(
imageUrl: String,
onTap: () -> Unit
onTap: () -> Unit,
onScaleChanged: (Float) -> Unit = {}
) {
var scale by remember { mutableFloatStateOf(1f) }
var offsetX by remember { mutableFloatStateOf(0f) }
var offsetY by remember { mutableFloatStateOf(0f) }
// 通知父组件缩放状态变化
LaunchedEffect(scale) {
onScaleChanged(scale)
}
Box(
modifier = Modifier
.fillMaxSize()
.then(
// 只有在放大状态下才启用手势检测,否则让 HorizontalPager 处理滑动
if (scale > 1f) {
Modifier.pointerInput(Unit) {
detectTransformGestures { _, pan, zoom, _ ->
val newScale = (scale * zoom).coerceIn(1f, 4f)
scale = newScale
if (scale > 1f) {
offsetX += pan.x
offsetY += pan.y
} else {
offsetX = 0f
offsetY = 0f
}
}
}
} else {
Modifier.pointerInput(Unit) {
detectTransformGestures { _, _, zoom, _ ->
// 未放大时只处理缩放手势(双指捏合)
.pointerInput(Unit) {
awaitEachGesture {
// 等待第一个手指按下
val firstDown = awaitFirstDown(requireUnconsumed = false)
var pointerId = firstDown.id
var isTap = true
var isMultiTouch = false
while (true) {
val event = awaitPointerEvent()
val changes = event.changes
// 检测是否变成多指触控
if (changes.size > 1) {
isMultiTouch = true
isTap = false
// 处理双指缩放
val zoom = calculateZoom(changes)
if (zoom != 1f) {
scale = (scale * zoom).coerceIn(1f, 4f)
val newScale = (scale * zoom).coerceIn(1f, 4f)
scale = newScale
changes.forEach { it.consume() }
}
}
// 检查是否所有手指都抬起
if (changes.all { !it.pressed }) {
// 单指点击且没有移动太多
if (isTap && !isMultiTouch) {
if (scale > 1f) {
scale = 1f
offsetX = 0f
offsetY = 0f
} else {
onTap()
}
}
break
}
// 单指移动时检查是否移动太多(判断是否为点击)
if (changes.size == 1 && !isMultiTouch) {
val change = changes.first()
val moved = (change.position - change.previousPosition).getDistance()
if (moved > 10f) {
isTap = false
// 单指滑动不消费事件,让 Pager 处理
}
}
}
}
)
.clickable {
if (scale == 1f) {
onTap()
} else {
// 点击时重置缩放
scale = 1f
offsetX = 0f
offsetY = 0f
}
}
.pointerInput(Unit) {
detectTapGestures(
onDoubleTap = {
if (scale > 1f) {
scale = 1f
offsetX = 0f
offsetY = 0f
} else {
scale = 2.5f
}
}
)
},
contentAlignment = Alignment.Center
) {
@@ -578,3 +634,12 @@ private fun ZoomableImage(
)
}
}
private fun calculateZoom(changes: List<androidx.compose.ui.input.pointer.PointerInputChange>): Float {
if (changes.size < 2) return 1f
val current = (changes[0].position - changes[1].position).getDistance()
val previous = (changes[0].previousPosition - changes[1].previousPosition).getDistance()
return if (previous > 0f) current / previous else 1f
}