Skip to content

[Android] SafeAreaEdges stops working when Scale < 1 is set on Grid and then animated #33544

@paulober

Description

@paulober

Description

When a Grid has SafeAreaEdges enabled (e.g., SafeAreaRegions.Container) and is initialized with a Scale value other than 1 (e.g., Scale = 0.8 for an entry animation), the SafeAreaEdges configuration stops working correctly on Android.

Unlike on iOS, where the Safe Area constraints are respected regardless of the view's scale transformation, Android seems to ignore the safe area insets once scaling is applied. When the element is animated back to Scale = 1, the content ends up positioned under the camera notch/status bar.

Visual Symptoms on Android:

  • Content expands UNDER the camera notch/cutout (overlapping) when scaling up.
  • The SafeAreaEdges constraints seem to get "lost" during the scale transformation.

Root Cause Analysis:
It appears Android fails to invalidate the layout or recalculate insets after a Scale transformation.

Steps to Reproduce

  1. Setup a Grid with SafeAreaEdges="Container", Opacity="0" and Scale="0.8" (simulating an entrance state).
  2. In OnAppearing, animate both properties:
await Task.WhenAll(
    ContentLayer.FadeToAsync(1, 200),
    ContentLayer.ScaleToAsync(1, 200)
);
  1. Observe that on Android, the content now overlaps the Status Bar/Notch.

Link to public reproduction project repository

https://github.com/paulober/maui-android-safearea-opacity-bug

Version with bug

10.0.20

Affected platforms

Android

Did you find any workaround?

Yes, but it is extremely "hacky" and requires platform-specific code.

We found that we must manually force a layout invalidation after the animation finishes. However, simply re-setting SafeAreaEdges to the desired value (e.g., Container) does not work because the internal property setter probably checks for equality because then it skips the update if the value hasn't changed.

To make this work on Android, we have to "toggle" the value to something else to force the system to react.

The "Dirty" Workaround:

await ContentLayer.ScaleToAsync(1, 200);

#if ANDROID
// HACK: Force layout invalidation on Android.
// We must change the value to something DIFFERENT than the current state 
// to bypass the property equality check and trigger an update.
ContentLayer.SafeAreaEdges = new SafeAreaEdges(_initialRegions == SafeAreaRegions.Container
                                                      ? SafeAreaRegions.SoftInput : SafeAreaRegions.Container); 
ContentLayer.SafeAreaEdges = new SafeAreaEdges(_initialRegions); 
#endif

Why this is problematic:

  1. iOS behavior: On iOS, we want the correct SafeAreaRegions.Container set right from the start (initialization).
  2. Android behavior: On Android, keeping that correct value causes the bug. We are forced to write platform-specific logic to toggle the property back and forth just to wake up the layout engine.

We shouldn't need to toggle properties to dummy values to get the Safe Area to respect the notch after a simple Scale animation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-safeareaIssues/PRs that have to do with the SafeArea functionalityplatform/androids/triagedIssue has been revieweds/verifiedVerified / Reproducible Issue ready for Engineering Triaget/bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions