Skip to content

Commit

Permalink
[a11y] Check for reduced motion frame names (#2451)
Browse files Browse the repository at this point in the history
Brings parity to the android for the changes in iOS airbnb/lottie-ios/pull/2110

This PR adds support for respecting the system "reduction motion" option.


### Animations Enabled


https://github.com/airbnb/lottie-android/assets/1218420/d29b62f6-db07-49f9-a7de-221949eef646


### Animations Disabled


https://github.com/airbnb/lottie-android/assets/1218420/2f346a78-d637-4c4a-8f60-8aa4e69e140d

Co-authored-by: Gabriel Peal <gpeal@users.noreply.github.com>
Co-authored-by: Steven Bassett <steven.bassett@airbnb.com>
  • Loading branch information
3 people committed Jan 23, 2024
1 parent f41f03a commit 6185eda
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 4 deletions.
49 changes: 47 additions & 2 deletions lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java
Expand Up @@ -51,6 +51,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
Expand Down Expand Up @@ -91,6 +92,21 @@ private enum OnVisibleAction {
*/
private static final boolean invalidateSelfOnMainThread = Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1;

/**
* The marker to use if "reduced motion" is enabled.
* Supported marker names are case insensitive, and include:
* - reduced motion
* - reducedMotion
* - reduced_motion
* - reduced-motion
*/
private static final List<String> ALLOWED_REDUCED_MOTION_MARKERS = Arrays.asList(
"reduced motion",
"reduced_motion",
"reduced-motion",
"reducedmotion"
);

private LottieComposition composition;
private final LottieValueAnimator animator = new LottieValueAnimator();

Expand Down Expand Up @@ -794,14 +810,38 @@ public void playAnimation() {
}
}
if (!animationsEnabled()) {
setFrame((int) (getSpeed() < 0 ? getMinFrame() : getMaxFrame()));
Marker markerForAnimationsDisabled = getMarkerForAnimationsDisabled();
if (markerForAnimationsDisabled != null) {
setFrame((int) markerForAnimationsDisabled.startFrame);
} else {
setFrame((int) (getSpeed() < 0 ? getMinFrame() : getMaxFrame()));
}
animator.endAnimation();
if (!isVisible()) {
onVisibleAction = OnVisibleAction.NONE;
}
}
}


/**
* This method is used to get the marker for animations when system animations are disabled.
* It iterates over the list of allowed reduced motion markers and returns the first non-null marker it finds.
* If no non-null marker is found, it returns null.
*
* @return The first non-null marker from the list of allowed reduced motion markers, or null if no such marker is found.
*/
private Marker getMarkerForAnimationsDisabled() {
Marker marker = null;
for (String markerName : ALLOWED_REDUCED_MOTION_MARKERS) {
marker = composition.getMarker(markerName);
if (marker != null) {
break;
}
}
return marker;
}

@MainThread
public void endAnimation() {
lazyCompositionTasks.clear();
Expand Down Expand Up @@ -1185,7 +1225,12 @@ private boolean animationsEnabled() {
/**
* Tell Lottie that system animations are disabled. When using {@link LottieAnimationView} or Compose {@code LottieAnimation}, this is done
* automatically. However, if you are using LottieDrawable on its own, you should set this to false when
* {@link com.airbnb.lottie.utils.Utils#getAnimationScale(Context)} is 0.
* {@link com.airbnb.lottie.utils.Utils#getAnimationScale(Context)} is 0. If the animation is provided a "reduced motion"
* marker name, they will be shown instead of the first or last frame. Supported marker names are case insensitive, and include:
* - reduced motion
* - reducedMotion
* - reduced_motion
* - reduced-motion
*/
public void setSystemAnimationsAreEnabled(Boolean areEnabled) {
systemAnimationsEnabled = areEnabled;
Expand Down
Expand Up @@ -24,6 +24,7 @@ import com.airbnb.lottie.snapshots.tests.ComposeDynamicPropertiesTestCase
import com.airbnb.lottie.snapshots.tests.ComposeScaleTypesTestCase
import com.airbnb.lottie.snapshots.tests.CompositionFrameRate
import com.airbnb.lottie.snapshots.tests.CustomBoundsTestCase
import com.airbnb.lottie.snapshots.tests.DisabledAnimationsTestCase
import com.airbnb.lottie.snapshots.tests.DynamicPropertiesTestCase
import com.airbnb.lottie.snapshots.tests.FailureTestCase
import com.airbnb.lottie.snapshots.tests.FrameBoundariesTestCase
Expand Down Expand Up @@ -166,6 +167,7 @@ class LottieSnapshotTest {
PolygonStrokeTestCase(),
CompositionFrameRate(),
ClipTextToBoundingBoxTestCase(),
DisabledAnimationsTestCase(),
)

withTimeout(TimeUnit.MINUTES.toMillis(45)) {
Expand Down
Expand Up @@ -56,7 +56,7 @@ suspend fun SnapshotTestCaseContext.withDrawable(
assetName: String,
snapshotName: String,
snapshotVariant: String,
callback: (LottieDrawable) -> Unit,
callback: suspend (LottieDrawable) -> Unit,
) {
val result = LottieCompositionFactory.fromAssetSync(context, assetName)
val composition = result.value ?: throw IllegalArgumentException("Unable to parse $assetName.", result.exception)
Expand Down Expand Up @@ -254,4 +254,4 @@ private suspend fun View.awaitFrame() {
cont.resume(Unit)
}
}
}
}
@@ -0,0 +1,25 @@
package com.airbnb.lottie.snapshots.tests

import com.airbnb.lottie.snapshots.SnapshotTestCase
import com.airbnb.lottie.snapshots.SnapshotTestCaseContext
import com.airbnb.lottie.snapshots.withDrawable
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

class DisabledAnimationsTestCase : SnapshotTestCase {
override suspend fun SnapshotTestCaseContext.run() {
withDrawable("Tests/ReducedMotion.json", "System Animations", "Disabled") { drawable ->
withContext(Dispatchers.Main) {
drawable.setSystemAnimationsAreEnabled(false)
drawable.playAnimation()
}
}

withDrawable("Tests/ReducedMotion.json", "System Animations", "Enabled") { drawable ->
withContext(Dispatchers.Main) {
drawable.setSystemAnimationsAreEnabled(false)
drawable.playAnimation()
}
}
}
}

0 comments on commit 6185eda

Please sign in to comment.