如何使用 detectTransformGestures 但不消耗所有指针事件
How to use detectTransformGestures but not consuming all pointer event
我正在制作一个全屏照片查看器,其中包含一个寻呼机(使用 HorizontalPager
)和每个页面,用户可以缩放 in/out 和平移图像,但仍然可以滑动页面。
我的想法是当图像未放大(比例因子 = 1)时会发生滑动页面,如果放大(比例因子 > 1)则 dragging/swiping 将左右平移图像。
这是包含我自定义的可缩放图像的 HorizontalPager
的代码:
@ExperimentalPagerApi
@Composable
fun ViewPagerSlider(pagerState: PagerState, urls: List<String>) {
var scale = remember {
mutableStateOf(1f)
}
var transX = remember {
mutableStateOf(0f)
}
var transY = remember {
mutableStateOf(0f)
}
HorizontalPager(
count = urls.size,
state = pagerState,
modifier = Modifier
.padding(0.dp, 40.dp, 0.dp, 40.dp),
) { page ->
Image(
painter = rememberImagePainter(
data = urls[page],
emptyPlaceholder = R.drawable.img_default_post,
),
contentScale = ContentScale.FillHeight,
contentDescription = null,
modifier = Modifier
.fillMaxSize()
.graphicsLayer(
translationX = transX.value,
translationY = transY.value,
scaleX = scale.value,
scaleY = scale.value,
)
.pointerInput(scale.value) {
detectTransformGestures { _, pan, zoom, _ ->
scale.value = when {
scale.value < 1f -> 1f
scale.value > 3f -> 3f
else -> scale.value * zoom
}
if (scale.value > 1f) {
transX.value = transX.value + (pan.x * scale.value)
transY.value = transY.value + (pan.y * scale.value)
} else {
transX.value = 0f
transY.value = 0f
}
}
}
)
}
}
所以我的图片最大放大3f,缩小不能小于0。
如果 detectTransformGestures
在我的代码中,我无法滑动以更改到另一个页面。如果我根据因素放置 detectTransformGestures
(scale = 1,如果不放大,使其可滑动到另一个页面),那么它将是一个“死锁”,因为我无法放大,因为没有听众。
不知道有没有什么方法可以实现...
谢谢大家的宝贵时间!
如果您可以创建一个可变状态变量来跟踪缩放因子,则可以在缩放因子大于 1 时添加 pointerInput 修饰符,并在大于 1 时将其删除。像这样:
var zoomFactorGreaterThanOne by remember { mutableStateOf(false) }
Image(
painter = rememberImagePainter(
data = urls[page],
emptyPlaceholder = R.drawable.img_default_post,
),
contentScale = ContentScale.FillHeight,
contentDescription = null,
modifier = Modifier
.fillMaxSize()
.graphicsLayer(
translationX = transX.value,
translationY = transY.value,
scaleX = scale.value,
scaleY = scale.value,
)
.run {
if (zoomFactorGreaterThanOne != 1.0f) {
this.pointerInput(scale.value) {
detectTransformGestures { _, pan, zoom, _ ->
zoomFactorGreaterThanOne = scale.value > 1
scale.value = when {
scale.value < 1f -> 1f
scale.value > 3f -> 3f
else -> scale.value * zoom
}
if (scale.value > 1f) {
transX.value = transX.value + (pan.x * scale.value)
transY.value = transY.value + (pan.y * scale.value)
} else {
transX.value = 0f
transY.value = 0f
}
}
}
} else {
this
}
}
)
我不得不做类似的事情,想出了这个:
private fun ZoomableImage(
modifier: Modifier = Modifier,
bitmap: ImageBitmap,
maxScale: Float = 1f,
minScale: Float = 3f,
contentScale: ContentScale = ContentScale.Fit,
isRotation: Boolean = false,
isZoomable: Boolean = true,
lazyState: LazyListState
) {
val scale = remember { mutableStateOf(1f) }
val rotationState = remember { mutableStateOf(1f) }
val offsetX = remember { mutableStateOf(1f) }
val offsetY = remember { mutableStateOf(1f) }
val coroutineScope = rememberCoroutineScope()
Box(
modifier = Modifier
.clip(RectangleShape)
.background(Color.Transparent)
.combinedClickable(
interactionSource = remember { MutableInteractionSource() },
indication = null,
onClick = { /* NADA :) */ },
onDoubleClick = {
if (scale.value >= 2f) {
scale.value = 1f
offsetX.value = 1f
offsetY.value = 1f
} else scale.value = 3f
},
)
.pointerInput(Unit) {
if (isZoomable) {
forEachGesture {
awaitPointerEventScope {
awaitFirstDown()
do {
val event = awaitPointerEvent()
scale.value *= event.calculateZoom()
if (scale.value > 1) {
coroutineScope.launch {
lazyState.setScrolling(false)
}
val offset = event.calculatePan()
offsetX.value += offset.x
offsetY.value += offset.y
rotationState.value += event.calculateRotation()
coroutineScope.launch {
lazyState.setScrolling(true)
}
} else {
scale.value = 1f
offsetX.value = 1f
offsetY.value = 1f
}
} while (event.changes.any { it.pressed })
}
}
}
}
) {
Image(
bitmap = bitmap,
contentDescription = null,
contentScale = contentScale,
modifier = modifier
.align(Alignment.Center)
.graphicsLayer {
if (isZoomable) {
scaleX = maxOf(maxScale, minOf(minScale, scale.value))
scaleY = maxOf(maxScale, minOf(minScale, scale.value))
if (isRotation) {
rotationZ = rotationState.value
}
translationX = offsetX.value
translationY = offsetY.value
}
}
)
}
}
它是可缩放的,可旋转的(如果你想要它),如果图像放大则支持平移,支持 double-click zoom-in 和 zoom-out 并且还支持被使用在可滚动元素内。我还没有想出一个解决方案来限制用户可以平移图像的距离。
它使用 combinedClickable
,因此 double-click 缩放不会干扰其他手势,pointerInput
用于缩放、平移和旋转。
它使用此扩展函数来控制 LazyListState
,但如果您需要它用于 ScrollState
,修改它以满足您的需要应该不难:
suspend fun LazyListState.setScrolling(value: Boolean) {
scroll(scrollPriority = MutatePriority.PreventUserInput) {
when (value) {
true -> Unit
else -> awaitCancellation()
}
}
}
您可以根据需要随意修改它。
我正在制作一个全屏照片查看器,其中包含一个寻呼机(使用 HorizontalPager
)和每个页面,用户可以缩放 in/out 和平移图像,但仍然可以滑动页面。
我的想法是当图像未放大(比例因子 = 1)时会发生滑动页面,如果放大(比例因子 > 1)则 dragging/swiping 将左右平移图像。
这是包含我自定义的可缩放图像的 HorizontalPager
的代码:
@ExperimentalPagerApi
@Composable
fun ViewPagerSlider(pagerState: PagerState, urls: List<String>) {
var scale = remember {
mutableStateOf(1f)
}
var transX = remember {
mutableStateOf(0f)
}
var transY = remember {
mutableStateOf(0f)
}
HorizontalPager(
count = urls.size,
state = pagerState,
modifier = Modifier
.padding(0.dp, 40.dp, 0.dp, 40.dp),
) { page ->
Image(
painter = rememberImagePainter(
data = urls[page],
emptyPlaceholder = R.drawable.img_default_post,
),
contentScale = ContentScale.FillHeight,
contentDescription = null,
modifier = Modifier
.fillMaxSize()
.graphicsLayer(
translationX = transX.value,
translationY = transY.value,
scaleX = scale.value,
scaleY = scale.value,
)
.pointerInput(scale.value) {
detectTransformGestures { _, pan, zoom, _ ->
scale.value = when {
scale.value < 1f -> 1f
scale.value > 3f -> 3f
else -> scale.value * zoom
}
if (scale.value > 1f) {
transX.value = transX.value + (pan.x * scale.value)
transY.value = transY.value + (pan.y * scale.value)
} else {
transX.value = 0f
transY.value = 0f
}
}
}
)
}
}
所以我的图片最大放大3f,缩小不能小于0。
如果 detectTransformGestures
在我的代码中,我无法滑动以更改到另一个页面。如果我根据因素放置 detectTransformGestures
(scale = 1,如果不放大,使其可滑动到另一个页面),那么它将是一个“死锁”,因为我无法放大,因为没有听众。
不知道有没有什么方法可以实现...
谢谢大家的宝贵时间!
如果您可以创建一个可变状态变量来跟踪缩放因子,则可以在缩放因子大于 1 时添加 pointerInput 修饰符,并在大于 1 时将其删除。像这样:
var zoomFactorGreaterThanOne by remember { mutableStateOf(false) }
Image(
painter = rememberImagePainter(
data = urls[page],
emptyPlaceholder = R.drawable.img_default_post,
),
contentScale = ContentScale.FillHeight,
contentDescription = null,
modifier = Modifier
.fillMaxSize()
.graphicsLayer(
translationX = transX.value,
translationY = transY.value,
scaleX = scale.value,
scaleY = scale.value,
)
.run {
if (zoomFactorGreaterThanOne != 1.0f) {
this.pointerInput(scale.value) {
detectTransformGestures { _, pan, zoom, _ ->
zoomFactorGreaterThanOne = scale.value > 1
scale.value = when {
scale.value < 1f -> 1f
scale.value > 3f -> 3f
else -> scale.value * zoom
}
if (scale.value > 1f) {
transX.value = transX.value + (pan.x * scale.value)
transY.value = transY.value + (pan.y * scale.value)
} else {
transX.value = 0f
transY.value = 0f
}
}
}
} else {
this
}
}
)
我不得不做类似的事情,想出了这个:
private fun ZoomableImage(
modifier: Modifier = Modifier,
bitmap: ImageBitmap,
maxScale: Float = 1f,
minScale: Float = 3f,
contentScale: ContentScale = ContentScale.Fit,
isRotation: Boolean = false,
isZoomable: Boolean = true,
lazyState: LazyListState
) {
val scale = remember { mutableStateOf(1f) }
val rotationState = remember { mutableStateOf(1f) }
val offsetX = remember { mutableStateOf(1f) }
val offsetY = remember { mutableStateOf(1f) }
val coroutineScope = rememberCoroutineScope()
Box(
modifier = Modifier
.clip(RectangleShape)
.background(Color.Transparent)
.combinedClickable(
interactionSource = remember { MutableInteractionSource() },
indication = null,
onClick = { /* NADA :) */ },
onDoubleClick = {
if (scale.value >= 2f) {
scale.value = 1f
offsetX.value = 1f
offsetY.value = 1f
} else scale.value = 3f
},
)
.pointerInput(Unit) {
if (isZoomable) {
forEachGesture {
awaitPointerEventScope {
awaitFirstDown()
do {
val event = awaitPointerEvent()
scale.value *= event.calculateZoom()
if (scale.value > 1) {
coroutineScope.launch {
lazyState.setScrolling(false)
}
val offset = event.calculatePan()
offsetX.value += offset.x
offsetY.value += offset.y
rotationState.value += event.calculateRotation()
coroutineScope.launch {
lazyState.setScrolling(true)
}
} else {
scale.value = 1f
offsetX.value = 1f
offsetY.value = 1f
}
} while (event.changes.any { it.pressed })
}
}
}
}
) {
Image(
bitmap = bitmap,
contentDescription = null,
contentScale = contentScale,
modifier = modifier
.align(Alignment.Center)
.graphicsLayer {
if (isZoomable) {
scaleX = maxOf(maxScale, minOf(minScale, scale.value))
scaleY = maxOf(maxScale, minOf(minScale, scale.value))
if (isRotation) {
rotationZ = rotationState.value
}
translationX = offsetX.value
translationY = offsetY.value
}
}
)
}
}
它是可缩放的,可旋转的(如果你想要它),如果图像放大则支持平移,支持 double-click zoom-in 和 zoom-out 并且还支持被使用在可滚动元素内。我还没有想出一个解决方案来限制用户可以平移图像的距离。
它使用 combinedClickable
,因此 double-click 缩放不会干扰其他手势,pointerInput
用于缩放、平移和旋转。
它使用此扩展函数来控制 LazyListState
,但如果您需要它用于 ScrollState
,修改它以满足您的需要应该不难:
suspend fun LazyListState.setScrolling(value: Boolean) {
scroll(scrollPriority = MutatePriority.PreventUserInput) {
when (value) {
true -> Unit
else -> awaitCancellation()
}
}
}
您可以根据需要随意修改它。