Accessibility - Animations
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)






Comments
Post a Comment