@@ -9,6 +9,8 @@ import androidx.annotation.VisibleForTesting
99import androidx.annotation.VisibleForTesting.Companion.PRIVATE
1010import com.mapbox.common.Cancelable
1111import com.mapbox.geojson.Point
12+ import com.mapbox.maps.CameraAnimationHint
13+ import com.mapbox.maps.CameraAnimationHintStage
1214import com.mapbox.maps.CameraOptions
1315import com.mapbox.maps.EdgeInsets
1416import com.mapbox.maps.MapboxCameraAnimationException
@@ -353,14 +355,18 @@ internal class CameraAnimationsPluginImpl : CameraAnimationsPlugin, MapCameraPlu
353355 CameraAnimatorType .PITCH -> animationObjectValues.all { Objects .equals(pitch, it) }
354356 }
355357
356- private fun updateCameraValue (cameraAnimator : CameraAnimator <* >) {
358+ private fun updateCameraValue (
359+ cameraAnimator : CameraAnimator <* >,
360+ animatedValue : Any? ,
361+ cameraOptionsBuilder : CameraOptions .Builder
362+ ) {
357363 when (cameraAnimator) {
358- is CameraCenterAnimator -> cameraOptionsBuilder.center(cameraAnimator. animatedValue as ? Point )
359- is CameraZoomAnimator -> cameraOptionsBuilder.zoom(cameraAnimator. animatedValue as ? Double )
360- is CameraAnchorAnimator -> cameraOptionsBuilder.anchor(cameraAnimator. animatedValue as ? ScreenCoordinate )
361- is CameraPaddingAnimator -> cameraOptionsBuilder.padding(cameraAnimator. animatedValue as ? EdgeInsets )
362- is CameraBearingAnimator -> cameraOptionsBuilder.bearing(cameraAnimator. animatedValue as ? Double )
363- is CameraPitchAnimator -> cameraOptionsBuilder.pitch(cameraAnimator. animatedValue as ? Double )
364+ is CameraCenterAnimator -> cameraOptionsBuilder.center(animatedValue as ? Point )
365+ is CameraZoomAnimator -> cameraOptionsBuilder.zoom(animatedValue as ? Double )
366+ is CameraAnchorAnimator -> cameraOptionsBuilder.anchor(animatedValue as ? ScreenCoordinate )
367+ is CameraPaddingAnimator -> cameraOptionsBuilder.padding(animatedValue as ? EdgeInsets )
368+ is CameraBearingAnimator -> cameraOptionsBuilder.bearing(animatedValue as ? Double )
369+ is CameraPitchAnimator -> cameraOptionsBuilder.pitch(animatedValue as ? Double )
364370 }
365371 }
366372
@@ -482,6 +488,86 @@ internal class CameraAnimationsPluginImpl : CameraAnimationsPlugin, MapCameraPlu
482488 }
483489 }
484490
491+ /* *
492+ * Animation fraction at which the camera animation hints will be calculated.
493+ * Note: at least 1.0F should be present and entries should be in ascent order.
494+ */
495+ private val cameraAnimationHintFractions = listOf (0.25F , 0.5F , 0.75F , 1.0F )
496+ private fun calculateCameraAnimationHint (animatorSet : AnimatorSet ) {
497+ // this method requires that all animators have:
498+ // 1. same duration
499+ // 2. no delay
500+ // 3. start value
501+ // TODO: support different duration, start delay and no start value
502+
503+ // No need to calculate camera animation hints if the animation is instant
504+ if (animatorSet.duration == 0L ) {
505+ return
506+ }
507+
508+ // Make sure all animators in animatorSet are camera animators
509+ val cameraAnimators = animatorSet.childAnimations.map { it as CameraAnimator <* > }
510+ if (cameraAnimators.isEmpty() || cameraAnimators.size != animatorSet.childAnimations.size) {
511+ return
512+ }
513+
514+ val cameraOptionsBuilder = CameraOptions .Builder ()
515+ // Keep track of the duration to make sure all animators have the same duration
516+ val duration = cameraAnimators[0 ].duration
517+
518+ val stages = cameraAnimationHintFractions.map { fraction ->
519+ cameraOptionsBuilder.clear()
520+ cameraAnimators.map { cameraAnimator ->
521+ if (cameraAnimator.startDelay != 0L ) {
522+ logW(
523+ TAG ,
524+ " Unable to calculate animated value ahead of time for ${cameraAnimator.type.name} : startDelay != 0 is not supported"
525+ )
526+ return
527+ }
528+ if (cameraAnimator.duration != duration) {
529+ logW(
530+ TAG ,
531+ " Unable to calculate animated value ahead of time for ${cameraAnimator.type.name} : different duration is not supported"
532+ )
533+ return
534+ }
535+ try {
536+ val value = cameraAnimator.getAnimatedValueAt(fraction)
537+ updateCameraValue(cameraAnimator, value, cameraOptionsBuilder)
538+ } catch (e: UnsupportedOperationException ) {
539+ logW(
540+ TAG ,
541+ " Unable to calculate animated value ahead of time for ${cameraAnimator.type.name} : ${e.message} "
542+ )
543+ }
544+ }
545+ val camera = cameraOptionsBuilder.build()
546+ // We use animatorSet.duration to keep track of the progress because that's the total duration of the animation. That is,
547+ // the time between `setUserAnimationInProgress(true)` and `setUserAnimationInProgress(false)`
548+ val progress = (animatorSet.duration * fraction).toLong()
549+ CameraAnimationHintStage .Builder ()
550+ .camera(camera)
551+ .progress(progress)
552+ .build()
553+ }
554+ val cameraAnimationHint = CameraAnimationHint .Builder ().stages(stages).build()
555+ mapTransformDelegate.setCameraAnimationHint(cameraAnimationHint)
556+ }
557+
558+ /* *
559+ * Convenience method to clear camera options so it can be reused instead of creating
560+ * new [CameraOptions.Builder].
561+ */
562+ private fun CameraOptions.Builder.clear () {
563+ center(null )
564+ .padding(null )
565+ .anchor(null )
566+ .zoom(null )
567+ .bearing(null )
568+ .pitch(null )
569+ }
570+
485571 private fun registerInternalUpdateListener (animator : CameraAnimator <* >) {
486572 animator.addInternalUpdateListener {
487573 postOnMainThread { onAnimationUpdateInternal(animator, it) }
@@ -493,7 +579,7 @@ internal class CameraAnimationsPluginImpl : CameraAnimationsPlugin, MapCameraPlu
493579 runningAnimatorsQueue.add(animator)
494580
495581 // set current animator value in any case
496- updateCameraValue(animator)
582+ updateCameraValue(animator, animator.animatedValue, cameraOptionsBuilder )
497583
498584 if (animator.type == CameraAnimatorType .ANCHOR ) {
499585 anchor = valueAnimator.animatedValue as ScreenCoordinate
@@ -1054,6 +1140,8 @@ internal class CameraAnimationsPluginImpl : CameraAnimationsPlugin, MapCameraPlu
10541140 }
10551141 playTogether(* animators)
10561142 }
1143+ calculateCameraAnimationHint(animatorSet)
1144+
10571145 return HighLevelAnimatorSet (animationOptions?.owner, animatorSet).also {
10581146 highLevelAnimatorSet = it
10591147 postOnAnimatorThread { it.animatorSet.start() }
0 commit comments