Accessibility - Animations

Speeding Car

Purpose

In my previous blog post on pausable animated backgrounds, I mentioned hiding animations from people sensitive to movement.

Well, we can actually detect if the user set that as a preference on their device.

Our goal is to ensure user motion preferences are observed.

Detection

fun Context.prefersReducedMotionFlow() = callbackFlow {
val uri = Settings.Global.getUriFor(
Settings.Global.ANIMATOR_DURATION_SCALE
)
fun isReduced() = Settings.Global.getFloat(
contentResolver,
Settings.Global.ANIMATOR_DURATION_SCALE,
1f
) == 0f
val observer = object :
ContentObserver(
Handler(Looper.getMainLooper())
) {
override fun onChange(selfChange: Boolean) {
trySend(isReduced())
}
}
contentResolver.registerContentObserver(
uri, false, observer
)
send(isReduced())
awaitClose {
contentResolver.unregisterContentObserver(observer)
}
}

Remembering the Value

@Composable
fun rememberPrefersReducedMotion(): Boolean {
val context = LocalContext.current
val initial = remember(context) {
Settings.Global.getFloat(
context.contentResolver,
Settings.Global.ANIMATOR_DURATION_SCALE,
1f) == 0f
}
return remember(context) {
context.prefersReducedMotionFlow()
}.collectAsStateWithLifecycle(
initialValue = initial
).value
}

Testing the Display

Using the code above, we can make a quick test to ensure we are detecting the user preference:

@Composable
private fun CheckMotionState() {
val userPrefersReducedMotion =
rememberPrefersReducedMotion()
Column(
Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
if(userPrefersReducedMotion) {
Text("Avoid Motion, please.")
} else {
Text("Animate Away!")
Spacer(Modifier.height(8.dp))
CircularProgressIndicator(
modifier = Modifier.size(64.dp)
)
}
}
}

Animations normal / disabled (see notes below for settings).

 

Note: If you use rememberInfiniteTransition for your animation, this will automatically observe user preferences. You can test this by leaving the CircularProgressIndicator enabled in the avoid motion state. It will draw the first frame of the animation and nothing more.

Summary

If your app uses a lot of custom animations, consider blocking these for users who may be sensitive to movement. You don't want to bounce users for such a small simple reason!

Settings for Testing

Simply go to Settings -> Accessibility -> Colors & Motion -> then select Remove animations. These screenshots are taken from Android API 36.

Accessibility (near the bottom) / Colors and Motion (third from top)

Remove animations (bottom)


Full Code Listing

GitHub Gist

Previous Blog

Comments

Popular Posts