欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 房产 > 建筑 > telophoto源码查看记录 二

telophoto源码查看记录 二

2025/4/18 16:58:04 来源:https://blog.csdn.net/archko/article/details/147071241  浏览:    关键词:telophoto源码查看记录 二

目录

TransformableElement和TransformableNode

事件方法:

TransformableState

contentTransformation:

梳理一下流程:


平移,缩放功能分析

TransformableElement和TransformableNode

TransformableElement是一个data class.具体的操作是在TransformableNode中的,它是一个DelegatingNode,实现CompositionLocalConsumerModifierNode.实现手势检测逻辑的核心节点,负责处理手势事件并将其转换为变换操作。

两个关键属性:

  • channel: 一个 Channel<TransformEvent>,用于在手势检测线程和主逻辑线程之间传递事件。
  • pointerInputNode: 一个 SuspendingPointerInputModifierNode,用于捕获手势事件。
  • 关键方法:
    • update(): 更新节点的状态和配置。
    • detectZoom(): 检测缩放、旋转和平移手势,并根据触控阈值触发相应的事件。

这里用SuspendingPointerInputModifierNode,它处理手势,管理手势的生命周期.因为手势的处理比较复杂,如果全部放在TransformableElement,显然它的复杂度上去了.所以代理 给SuspendingPointerInputModifierNode,它处理协程,管理生命周期.

channel用于传递事件,开启一个协程launch(start = CoroutineStart.UNDISPATCHED) 监听事件var event = channel.receive(),只要事件不是event !is TransformStopped停止状态,不断监听并更新状态.

state.transform(MutatePriority.UserInput)

最后,如果事件停止了,处理停止的状态:

(event as? TransformStopped)?.let { event ->

updatedOnTransformStopped(event.velocity)

}

事件方法:
awaitEachGesture {val velocityTracker = VelocityTracker()var wasCancelled = falsetry {detectZoom(lockRotationOnZoomPan, channel, updatedCanPan, velocityTracker)} catch (exception: CancellationException) {wasCancelled = trueif (!isActive) throw exception} finally {val maximumVelocity = currentValueOf(LocalViewConfiguration).let {Velocity(it.maximumFlingVelocity, it.maximumFlingVelocity)}val velocity = if (wasCancelled) Velocity.Zero else velocityTracker.calculateFiniteVelocity(maximumVelocity)channel.trySend(TransformStopped(velocity))}}

这是一个扩展方法:AwaitPointerEventScope.detectZoom

这个方法的代码非常常见了.就是do/while,直到事件结束.

do {val event = awaitPointerEvent()val canceled = event.changes.fastAny { it.isConsumed }if (!canceled) {
//把事件加入,好处理后面的滚动加速,事件不是取消状态才执行.event.changes.fastForEach {if (it.id == trackingPointerId) {velocityTracker.addPointerInputChange(it)}}//处理缩放,平移,旋转三种val zoomChange = event.calculateZoom()val rotationChange = event.calculateRotation()val panChange = event.calculatePan()if (!pastTouchSlop) {zoom *= zoomChangerotation += rotationChangepan += panChangeval centroidSize = event.calculateCentroidSize(useCurrent = false)val zoomMotion = abs(1 - zoom) * centroidSizeval rotationMotion = abs(rotation * PI.toFloat() * centroidSize / 180f)val panMotion = pan.getDistance()val touchSlop = viewConfiguration.pointerSlop(event.changes[0].type)//如果多于一个手指,或者有平移,旋转发生,先标记事件启动,发送TransformStartedif (event.changes.size > 1 ||zoomMotion > touchSlop ||rotationMotion > touchSlop ||(panMotion > touchSlop && canPan.invoke(panChange))) {pastTouchSlop = truelockedToPanZoom = panZoomLock && rotationMotion < touchSlopchannel.trySend(TransformStarted)}}//事件发生,并且标记为启动,这里发送TransformDelta事件的增量.if (pastTouchSlop) {val centroid = event.calculateCentroid(useCurrent = false)val effectiveRotation = if (lockedToPanZoom) 0f else rotationChangeif (effectiveRotation != 0f ||zoomChange != 1f ||(panChange != Offset.Zero && canPan.invoke(panChange))) {channel.trySend(TransformDelta(zoomChange, panChange, effectiveRotation, centroid))}
//消耗剩余的event.changes.fastForEach {if (it.positionChanged()) {it.consume()}}}} else {//事件取消了,发送结束标记TransformStoppedchannel.trySend(TransformStopped(Velocity.Zero))}val finalEvent = awaitPointerEvent(pass = PointerEventPass.Final)// someone consumed while we were waiting for touch slopval finallyCanceled = finalEvent.changes.fastAny { it.isConsumed } && !pastTouchSlop} while (!canceled && !finallyCanceled && event.changes.fastAny { it.pressed })

这段在官方一些示例手势项目都是有的,只是增加了channel.

这个方法主要的用处就是捕获系统的事件,然后往外发出事件的平移,旋转,缩放的状态,由外部去用这些状态更新view.

事件结束后,我们看一下:onTransformStopped,在zoomable类中.

coroutineScope.launch {if (state.isZoomOutsideRange()) {hapticFeedback.performHapticFeedback()state.animateSettlingOfZoomOnGestureEnd()} else {state.fling(velocity = velocity, density = requireDensity())}}

如果是范围内,则进入fling,惯性滑动.

TransformableState

其它的手势是在TransformableState类中,这又是一个接口.默认实现类是DefaultTransformableState.

DefaultTransformableState中由TransformScope的接口实现类来处理的

transformBy(it.zoomChange, it.panChange, it.rotationChange, it.centroid)

没有找到TransformScope的实现类,原因它只是过度的委托.

它最终是通过调用onTransformation,这是DefaultTransformableState构造函数里面的参数.这个构造方法的调用是在RealZoomableState中:

这个类里面定义了几个接口,它的主要作用我想是便于协程的应用.没有处理具体的事务.

internal val transformableState = TransformableState { zoomDelta, panDelta, _, centroid ->

看下具体的代码:

check(panDelta.isSpecifiedAndFinite() && zoomDelta.isFinite() && centroid.isSpecifiedAndFinite()) {"Can't transform with zoomDelta=$zoomDelta, panDelta=$panDelta, centroid=$centroid. ${collectDebugInfo()}"}val lastGestureState = calculateGestureState() ?: return@TransformableStategestureState = GestureStateCalculator { inputs ->val oldZoom = ContentZoomFactor(baseZoom = inputs.baseZoom,userZoom = lastGestureState.userZoom,)check(oldZoom.finalZoom().isPositiveAndFinite()) {"Old zoom is invalid/infinite. ${collectDebugInfo()}"}val isZoomingOut = zoomDelta < 1fval isZoomingIn = zoomDelta > 1fval isAtMaxZoom = oldZoom.isAtMaxZoom(zoomSpec.range)val isAtMinZoom = oldZoom.isAtMinZoom(zoomSpec.range)//它的缩放如果超过最大或最小值时,会调整缩放值.然后再计算最终的缩放值newZoom// Apply overzoom effect if content is being over/under-zoomed.val zoomDelta = if (isZoomingIn && isAtMaxZoom || isZoomingOut && isAtMinZoom) {zoomSpec.maximum.overzoomEffect.adjust(zoomDelta)} else {zoomDelta}val newZoom = ContentZoomFactor(baseZoom = inputs.baseZoom,userZoom = oldZoom.userZoom * zoomDelta,).let {// Disable overzooms after a certain extent.if ((isAtMaxZoom && zoomSpec.maximum.overzoomEffect != OverzoomEffect.NoLimits)|| (isAtMinZoom && zoomSpec.minimum.overzoomEffect != OverzoomEffect.NoLimits)) {it.coerceUserZoomIn(range = zoomSpec.range,leewayPercentForMinZoom = 0.1f,leewayPercentForMaxZoom = 0.4f)} else {it}}check(newZoom.finalZoom().let { it.isPositiveAndFinite() && it.minScale > 0f }) {"New zoom is invalid/infinite = $newZoom. ${collectDebugInfo("zoomDelta" to zoomDelta)}"}
//重新计算偏移量val oldOffset = ContentOffset(baseOffset = inputs.baseOffset,userOffset = lastGestureState.userOffset,)GestureState(userOffset = oldOffset.retainCentroidPositionAfterZoom(centroid = centroid,panDelta = panDelta,oldZoom = oldZoom,newZoom = newZoom,).coerceWithinContentBounds(proposedZoom = newZoom, inputs = inputs).userOffset,userZoom = newZoom.userZoom,lastCentroid = centroid,)}

先是GestureStateCalculator创建这个对象,保存着变换的状态.在这个方法执行时,重新计算缩放,平移的状态.最后计算出GestureState.

private fun interface GestureStateCalculator,这是一个方法接口,可以转为lambda.是kotlin的语法,用lambda直接实现接口,省去了java的接口实现类那种复杂的形式.

它的偏移量注释中可以看出是从android的sample拿来的:

((currentOffset + centroid / oldZoom) - (centroid / newZoom + panDelta / oldZoom))
然后this.copy(userOffset = UserOffset(transformed - this.baseOffset)
)

整个手势下来是为了计算gestureState.它包含三个变量,偏移量,中心点,缩放值.

internal data class GestureState(val userOffset: UserOffset,// Note to self: Having ContentZoomFactor here would be convenient, but it complicates// state restoration. This class should not capture any layout-related values.val userZoom: UserZoomFactor,// Centroid in the viewport (and not the unscaled content bounds).val lastCentroid: Offset,
)

这些计算完成,它的应用我们看

private fun Modifier.zoomable()

return this.thenIf(clipToBounds) {Modifier.clipToBounds()}.onSizeChanged { state.viewportSize = it.toSize() }.then(ZoomableElement(state = state,pinchToZoomEnabled = pinchToZoomEnabled,quickZoomEnabled = quickZoomEnabled,onClick = onClick,onLongClick = onLongClick,onDoubleClick = onDoubleClick,)).thenIf(state.hardwareShortcutsSpec.enabled) {Modifier.then(HardwareShortcutsElement(state, state.hardwareShortcutsSpec)).focusable()}.thenIf(state.autoApplyTransformations) {Modifier.applyTransformation { state.contentTransformation }}

如果是自动应用转换,它就通过

Modifier.applyTransformation { state.contentTransformation }应用到控件中,实现缩放,平移等.
Modifier.applyTransformation(transformation: () -> ZoomableContentTransformation): Modifier {return graphicsLayer {@Suppress("NAME_SHADOWING")val transformation = transformation()scaleX = transformation.scale.scaleXscaleY = transformation.scale.scaleYrotationZ = transformation.rotationZtranslationX = transformation.offset.xtranslationY = transformation.offset.ytransformOrigin = transformation.transformOrigin}
}

最终它是作用于graphicsLayer上的.

它还添加了HardwareShortcutsElement,HardwareShortcutsNode,这两个可以支持键盘操作.

contentTransformation:
override val contentTransformation: ZoomableContentTransformation by derivedStateOf {val gestureStateInputs = currentGestureStateInputsif (gestureStateInputs != null) {RealZoomableContentTransformation.calculateFrom(gestureStateInputs = gestureStateInputs,gestureState = gestureState.calculate(gestureStateInputs),)} else {RealZoomableContentTransformation(isSpecified = false,contentSize = Size.Zero,scale = ScaleFactor.Zero,  // Effectively hide the content until an initial zoom value is calculated.scaleMetadata = RealZoomableContentTransformation.ScaleMetadata(initialScale = ScaleFactor.Zero,userZoom = 0f,),offset = Offset.Zero,centroid = null,)}}

它的变换是通过RealZoomableContentTransformation这个类.它需要的参数就是前面计算得到的gestureState: GestureState.

RealZoomableContentTransformation也不复杂,calculateFrom()就是把前面已经计算完成的数值赋值,没有多的逻辑.

这样整个变换就结束了.

梳理一下流程:

先建一个RealZoomableState,使用Modifier.zoomable扩展函数,放到对应的控件中,将state作为参数传入.

ZoomableElement也传入state,主要的变换计算,手势都是由它处理的.

最后Modifier.applyTransformation()应用到view中的graphicsLayer上面.

由于它是Modifier扩展的,所以可以针对任何的view

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词