实现左右滑动切换图片的功能
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user