如何在非Composable类中用Animatable实现Rect回边界动画?
问题描述
我正在开发一款图片裁剪器,使用Rect绘制动态遮罩层。当遮罩层超出图片边界时,希望在指针抬起时让它动画过渡回有效边界。
已实现内容
- 定义
overlayRect变量:
open var overlayRect: Rect = Rect(offset = Offset.Zero, size = Size(size.width.toFloat(), size.height.toFloat()))
- 通过
moveIntoBounds函数计算遮罩层的有效边界位置:
internal fun moveIntoBounds(rectBounds: Rect, rectCurrent: Rect): Rect { var width = rectCurrent.width var height = rectCurrent.height if (width > rectBounds.width) { width = rectBounds.width } if (height > rectBounds.height) { height = rectBounds.height } var rect = Rect(offset = rectCurrent.topLeft, size = Size(width, height)) if (rect.left < rectBounds.left) { rect = rect.translate(rectBounds.left - rect.left, 0f) } if (rect.top < rectBounds.top) { rect = rect.translate(0f, rectBounds.top - rect.top) } if (rect.right > rectBounds.right) { rect = rect.translate(rectBounds.right - rect.right, 0f) } if (rect.bottom > rectBounds.bottom) { rect = rect.translate(0f, rectBounds.bottom - rect.bottom) } return rect }
- 指针抬起时直接赋值:
override fun onUp(change: PointerInputChange) { touchRegion = TouchRegion.None overlayRect = moveIntoBounds(rectBounds, overlayRect) // Calculate crop rectangle cropRect = calculateRectBounds() rectTemp = overlayRect.copy() }
疑问
如何为这个Rect添加动画过渡到有效边界?能否使用Animatable来实现Rect动画?
我查阅了官方动画文档,文档建议用transition及transition.animateRect在状态间过渡,但我的场景没有固定状态,是从动态当前值过渡到动态目标值,且代码位于非Composable类DynamicCropState中(该类继承自类似ZoomStateImpl的类),需要使用Animatable或非Composable动画API实现。
解决方案
可以使用Animatable实现Rect的动画过渡,只需为Rect实现对应的TwoWayConverter,将其转换为动画系统可处理的向量类型即可。具体实现步骤如下:
1. 为Rect创建双向转换器
Rect由left、top、right、bottom四个浮点值决定,我们需要将它与AnimationVector4D做双向转换:
private val rectToVectorConverter = TwoWayConverter<Rect, AnimationVector4D>( convertToVector = { rect -> AnimationVector4D(rect.left, rect.top, rect.right, rect.bottom) }, convertFromVector = { vector -> Rect( left = vector.v1, top = vector.v2, right = vector.v3, bottom = vector.v4 ) } )
2. 在DynamicCropState中初始化Animatable
在你的状态类中添加Animatable实例,初始值与overlayRect保持一致:
private val coroutineScope = MainScope() private val animatableRect = Animatable(overlayRect, rectToVectorConverter)
3. 修改onUp方法启动动画
在指针抬起时,计算目标Rect后启动动画,并监听动画过程更新overlayRect:
override fun onUp(change: PointerInputChange) { touchRegion = TouchRegion.None val targetRect = moveIntoBounds(rectBounds, overlayRect) // 启动动画,这里使用tween动画,可替换为spring实现弹性效果 coroutineScope.launch { animatableRect.animateTo( targetValue = targetRect, animationSpec = tween(durationMillis = 300, easing = FastOutSlowInEasing) ) } // 监听动画值变化,同步更新UI相关变量 animatableRect.addUpdateListener { overlayRect = it.value cropRect = calculateRectBounds() rectTemp = overlayRect.copy() // 如果需要通知Compose重组,调用对应状态更新方法(如notifyStateChanged()) } }
4. 同步拖拽过程中的Rect值
在拖拽修改overlayRect时,同步更新animatableRect的当前值,避免动画起点错误:
// 示例:拖拽时更新overlayRect的方法 fun updateOverlayRect(newRect: Rect) { overlayRect = newRect animatableRect.snapTo(newRect) }
关键说明
Animatable依赖协程作用域,这里使用MainScope()确保在主线程执行动画;- 如果需要Composable自动感知
overlayRect变化,建议将其改为MutableState<Rect>类型; - 动画时长、缓动效果可根据需求调整,比如用
spring(dampingRatio = 0.8f)实现更自然的弹性回弹效果。
内容的提问来源于stack exchange,提问作者Thracian
相关产品推荐
相关产品推荐

