Skip to content

Commit 151c561

Browse files
authored
Implement look ahead camera options hints for high-level animations (#3042)
1 parent 819446e commit 151c561

File tree

9 files changed

+150
-9
lines changed

9 files changed

+150
-9
lines changed

maps-sdk/api/Release/metalava.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@ package com.mapbox.maps {
292292
method public com.mapbox.bindgen.Expected<java.lang.String,com.mapbox.bindgen.None> setBounds(com.mapbox.maps.CameraBoundsOptions options);
293293
method public void setCamera(com.mapbox.maps.CameraOptions cameraOptions);
294294
method public void setCamera(com.mapbox.maps.FreeCameraOptions freeCameraOptions);
295+
method public void setCameraAnimationHint(com.mapbox.maps.CameraAnimationHint cameraAnimationHint);
295296
method public void setCenterAltitudeMode(com.mapbox.maps.MapCenterAltitudeMode mode);
296297
method public void setConstrainMode(com.mapbox.maps.ConstrainMode constrainMode);
297298
method @Deprecated public void setDebug(java.util.List<? extends com.mapbox.maps.MapDebugOptions> debugOptions, boolean enabled);

maps-sdk/api/maps-sdk.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,7 @@ public final class com/mapbox/maps/MapboxMap : com/mapbox/maps/MapboxStyleManage
295295
public fun setBounds (Lcom/mapbox/maps/CameraBoundsOptions;)Lcom/mapbox/bindgen/Expected;
296296
public fun setCamera (Lcom/mapbox/maps/CameraOptions;)V
297297
public fun setCamera (Lcom/mapbox/maps/FreeCameraOptions;)V
298+
public fun setCameraAnimationHint (Lcom/mapbox/maps/CameraAnimationHint;)V
298299
public fun setCenterAltitudeMode (Lcom/mapbox/maps/MapCenterAltitudeMode;)V
299300
public fun setConstrainMode (Lcom/mapbox/maps/ConstrainMode;)V
300301
public final fun setDebug (Ljava/util/List;Z)V

maps-sdk/src/main/java/com/mapbox/maps/MapboxMap.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,22 @@ class MapboxMap :
618618
nativeMap.setUserAnimationInProgress(inProgress)
619619
}
620620

621+
/**
622+
* This method provides hints for animations, enabling the rendering engine to pre-process animation
623+
* frames and apply performance optimizations.
624+
*
625+
* The provided data is taken into action on the next
626+
* [setUserAnimationInProgress(true)][setUserAnimationInProgress] call.
627+
*
628+
* @param cameraAnimationHint the camera animation hint
629+
*/
630+
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
631+
@MapboxExperimental
632+
override fun setCameraAnimationHint(cameraAnimationHint: CameraAnimationHint) {
633+
checkNativeMap("setCameraAnimationHint")
634+
nativeMap.setCameraAnimationHint(cameraAnimationHint)
635+
}
636+
621637
/**
622638
* Returns if user animation is currently in progress.
623639
*

maps-sdk/src/main/java/com/mapbox/maps/NativeMapImpl.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,12 @@ internal class NativeMapImpl(val map: Map) {
111111
map.isUserAnimationInProgress = inProgress
112112
}
113113

114+
@MapboxExperimental
115+
@OptIn(com.mapbox.annotation.MapboxExperimental::class)
116+
fun setCameraAnimationHint(cameraAnimationHint: CameraAnimationHint) {
117+
map.setCameraAnimationHint(cameraAnimationHint)
118+
}
119+
114120
fun invalidateStyleCustomGeometrySourceRegion(
115121
sourceId: String,
116122
coordinateBounds: CoordinateBounds

plugin-animation/src/main/java/com/mapbox/maps/plugin/animation/CameraAnimationsPluginImpl.kt

Lines changed: 96 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import androidx.annotation.VisibleForTesting
99
import androidx.annotation.VisibleForTesting.Companion.PRIVATE
1010
import com.mapbox.common.Cancelable
1111
import com.mapbox.geojson.Point
12+
import com.mapbox.maps.CameraAnimationHint
13+
import com.mapbox.maps.CameraAnimationHintStage
1214
import com.mapbox.maps.CameraOptions
1315
import com.mapbox.maps.EdgeInsets
1416
import 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() }

plugin-animation/src/main/java/com/mapbox/maps/plugin/animation/animator/CameraAnimator.kt

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ abstract class CameraAnimator<out T> (
2020
/**
2121
* [TypeEvaluator] for generic type
2222
*/
23-
evaluator: TypeEvaluator<T>,
23+
private val evaluator: TypeEvaluator<T>,
2424
/**
2525
* Camera animator options.
2626
*/
@@ -123,6 +123,19 @@ abstract class CameraAnimator<out T> (
123123
return super.getAnimatedValue()
124124
}
125125

126+
/**
127+
* @throws UnsupportedOperationException if this animator does not support this method
128+
*/
129+
internal fun getAnimatedValueAt(fraction: Float): T {
130+
if (startValue == null || targets.size > 1) {
131+
throw UnsupportedOperationException(
132+
"getAnimatedValueAt() is only supported for single target animations with a start value."
133+
)
134+
}
135+
val interpolation = interpolator.getInterpolation(fraction)
136+
return evaluator.evaluate(interpolation, startValue, targets.last())
137+
}
138+
126139
/**
127140
* Handle immediate animation(when duration and startDelay of the animation is 0)
128141
* we trigger animator listeners directly without triggering [ValueAnimator]'s start().

sdk-base/api/Release/metalava.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2236,6 +2236,7 @@ package com.mapbox.maps.plugin.delegates {
22362236
method public com.mapbox.maps.Size getSize();
22372237
method public boolean isGestureInProgress();
22382238
method public boolean isUserAnimationInProgress();
2239+
method public void setCameraAnimationHint(com.mapbox.maps.CameraAnimationHint cameraAnimationHint);
22392240
method public void setConstrainMode(com.mapbox.maps.ConstrainMode constrainMode);
22402241
method public void setGestureInProgress(boolean inProgress);
22412242
method public void setNorthOrientation(com.mapbox.maps.NorthOrientation northOrientation);

sdk-base/api/sdk-base.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2265,6 +2265,7 @@ public abstract interface class com/mapbox/maps/plugin/delegates/MapTransformDel
22652265
public abstract fun getSize ()Lcom/mapbox/maps/Size;
22662266
public abstract fun isGestureInProgress ()Z
22672267
public abstract fun isUserAnimationInProgress ()Z
2268+
public abstract fun setCameraAnimationHint (Lcom/mapbox/maps/CameraAnimationHint;)V
22682269
public abstract fun setConstrainMode (Lcom/mapbox/maps/ConstrainMode;)V
22692270
public abstract fun setGestureInProgress (Z)V
22702271
public abstract fun setNorthOrientation (Lcom/mapbox/maps/NorthOrientation;)V

sdk-base/src/main/java/com/mapbox/maps/plugin/delegates/MapTransformDelegate.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.mapbox.maps.plugin.delegates
22

3+
import androidx.annotation.RestrictTo
34
import com.mapbox.maps.*
45

56
/**
@@ -52,6 +53,19 @@ interface MapTransformDelegate {
5253
*/
5354
fun setUserAnimationInProgress(inProgress: Boolean)
5455

56+
/**
57+
* This method provides hints for animations, enabling the rendering engine to pre-process animation
58+
* frames and apply performance optimizations.
59+
*
60+
* The provided data is taken into action on the next
61+
* [setUserAnimationInProgress(true)][setUserAnimationInProgress] call.
62+
*
63+
* @param cameraAnimationHint the camera animation hint
64+
*/
65+
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
66+
@MapboxExperimental
67+
fun setCameraAnimationHint(cameraAnimationHint: CameraAnimationHint)
68+
5569
/**
5670
* Returns if user animation is currently in progress.
5771
*

0 commit comments

Comments
 (0)