Jetpack Compose:实现图片的 Pinch Zoom 从未如此简单

Jetpack Compose 实现图片的 Pinch Zoom 效果,代码非常简单,我们看一下:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposePinchZoomRotateTheme {
                var scale by remember { mutableStateOf(1f) }
                var rotation by remember { mutableStateOf(1f) }
                var offset by remember { mutableStateOf(Offset.Zero) }

                BoxWithConstraints(
                    modifier = Modifier.fillMaxWidth().aspectRatio(1280f / 959f)
                ) {
                    val state = rememberTransformableState { zoomChange, panChange, rotationChange ->
                        scale = (scale * zoomChange).coerceIn(1f, 5f)
						rotation += rotationChange 
						
                        val extraWidth = (scale - 1) * constraints.maxWidth
                        val extraHeight = (scale - 1) * constraints.maxHeight

                        val maxX = extraWidth / 2
                        val maxY = extraHeight / 2

                        offset = Offset(
                            x = (offset.x + scale * panChange.x).coerceIn(-maxX, maxX),
                            y = (offset.y + scale * panChange.y).coerceIn(-maxY, maxY),
                        )
                    }
                    Image(
                        painter = painterResource(R.drawable.kermit),
                        contentDescription = null,
                        modifier = Modifier.fillMaxWidth()
                            .graphicsLayer {
                                scaleX = scale
                                scaleY = scale
                                rotationZ = rotation
                                translationX = offset.x
                                translationY = offset.y
                            }
                            .transformable(state)
                    )
                }
            }
        }
    }
}

同样的功能还可以使用 detectTransformGestures API 来实现,这里就不详细介绍了,具体可以参考我的 Jetpack Compose 专栏中的另一篇文章: Jetpack Compose中的手势操作和事件处理

下面尝试解释一下上面代码中 rememberTransformableState 的回调函数中所做的事情,为了简化,我们可以先将上面 rememberTransformableState 的回调代码写成下面这样:

val state = rememberTransformableState { zoomChange, panChange, rotationChange ->
	scale = (scale * zoomChange).coerceIn(1f, 5f)
	rotation += rotationChange 
	offset += panChange
}

这样其实就可以实现缩放、旋转、平移效果,只不过有一点点小瑕疵:

在这里插入图片描述

我们发现图片平移时周围会出现空白,所以 rememberTransformableState 回调函数中相比上面简化版本多余的代码就是为了处理这一点点瑕疵所做的修正工作,也就是下面代码:

val state = rememberTransformableState { zoomChange, panChange, rotationChange ->
	scale = (scale * zoomChange).coerceIn(1f, 5f)
	rotation += rotationChange 
	
	val extraWidth = (scale - 1) * constraints.maxWidth
	val extraHeight = (scale - 1) * constraints.maxHeight
	
	val maxX = extraWidth / 2
	val maxY = extraHeight / 2
	
	offset = Offset(
	    x = (offset.x + scale * panChange.x).coerceIn(-maxX, maxX),
	    y = (offset.y + scale * panChange.y).coerceIn(-maxY, maxY),
	)
}

我们先看下面这两行:

val extraWidth = (scale - 1) * constraints.maxWidth
val extraHeight = (scale - 1) * constraints.maxHeight

这两行是干嘛呢,请看图:

在这里插入图片描述

由于初始 scale1f,当我们放大之后,用 scale - 1f 去乘以 constraints.maxWidth 得到的extraWidth 其实就是放大后的矩形宽度比原始矩形宽度多出的部分,显然此时左右移动的时候,左边和右边不应该超出 extraWidth 的一半,因为再多一点的话就会看到空白区域了。对于高度也是同理。

因此剩余的代码就好理解了:

val maxX = extraWidth / 2
val maxY = extraHeight / 2

offset = Offset(
   x = (offset.x + scale * panChange.x).coerceIn(-maxX, maxX),
   y = (offset.y + scale * panChange.y).coerceIn(-maxY, maxY),
)

这里使用coerceIn限制了偏移量的移动范围,在往左边拖动的时候,不能超过左边的extraWidth 的一半,往右边拖动的时候,同样不能超过右边的extraWidth 的一半,上下拖同理。

Compose 实现图片的手势操作比传统 View 简便太多了,在传统 View 体系中,如果要对 ImageView 控件做同样的事情,那可要费老劲了。。往往我们需要自定义 View,然后还要封装成一个工具库来使用,例如下面列出两个开源库,你可以查看它们的源码的数量级来对比一下Jetpack Compose:

可见 Jetpack Compose 短短几十行代码就实现了传统 View 体系中动辄需要上千行的代码,简直不要太简洁,不仅清晰易懂,而且还易维护。Compose 的简洁性让我们可以有时间和精力挑战更加艰巨的任务。所以还没有用 Compose 的千万不要换 Compose ,因为换了你就会发现:TM再也不想用 View 控件来开发界面了😂

  • 27
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

川峰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值