feat(Android, Stack v5): expose statusBarScrimColor for the header - #4533
Conversation
There was a problem hiding this comment.
Pull request overview
Adds configurable status-bar scrims to Android Stack v5 headers for edge-to-edge layouts.
Changes:
- Exposes and documents
statusBarScrimColor. - Implements scrims for small and collapsing headers.
- Adds a manual single-feature test scenario.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
src/fabric/stack/StackHeaderConfigAndroidNativeComponent.ts |
Adds the native prop specification. |
src/components/stack/header/StackHeaderConfig.android.types.ts |
Documents the public Android API. |
apps/.../scenario.md |
Defines manual visual checks. |
apps/.../scenario-description.ts |
Registers scenario metadata. |
apps/.../index.tsx |
Implements the interactive test screen. |
apps/src/tests/single-feature-tests/stack-v5/index.ts |
Registers the new scenario. |
android/.../StackHeaderApplicator.kt |
Resolves and applies scrim colors. |
android/.../StackHeaderConfigViewManager.kt |
Receives the Fabric prop. |
android/.../StackHeaderConfigurationProviding.kt |
Extends the configuration contract. |
android/.../StackHeaderConfig.kt |
Stores and invalidates the prop. |
android/.../StackHeaderContentScrimDrawable.kt |
Prevents overlapping scrim rendering. |
android/.../StackHeaderAppBarLayout.kt |
Adds scrim synchronization and inset tracking. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
6e0cd9f to
f70e89b
Compare
f70e89b to
262b0ac
Compare
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
262b0ac to
5d816ac
Compare
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5d816ac to
efba05d
Compare
Description
In edge-to-edge apps the stack header draws under the status bar. Whenever
scroll flags let header content leave the screen, the title and buttons pass
straight through the status-bar area with nothing masking them. This PR exposes
statusBarScrimColorprop and enables a sensible scrim by default, so statusbar is always displayed on a solid background instead of obstructing the
toolbar.
Closes https://github.com/software-mansion/react-native-screens-labs/issues/905.
Details
Material provides two independent status-bar scrim mechanisms, depending on
header type.
Small header —
AppBarLayout.statusBarForegroundThe small header is an
AppBarLayout(ABL) hosting aMaterialToolbar. ABL candraw a
statusBarForegrounddrawable after (above) all of its children, sizedto the full width × the top window inset and translated against the scroll
offset — so the strip stays glued to the top of the window while the toolbar
scrolls away underneath it.
We want the default strip to always match the bar's effective background
color, including mid-flight during the lift-on-scroll animation (the background
animates between
backgroundColorand its blend withscrolledBackgroundColor). Material has a built-in sync for this, but it isnarrowly keyed: when a drawable is set as
statusBarForeground, ABL capturesits color (possible only for
ColorDrawable,MaterialShapeDrawableandColorStateListDrawable) and re-tints the strip on each animation frame onlyif the captured color equals the theme's
colorSurface. Any custom backgroundcolor breaks the sync (the strip freezes while the bar animates).
So instead we install a
PaintDrawableas the strip — it is notcolor-extractable, the captured color is
null, and the built-in sync ispermanently inert regardless of theme — and drive the tint ourselves from the
public
AppBarLayout.LiftOnScrollProgressListener. That listener is invokedfrom the same
ValueAnimatorframe with the same per-frame mixed color thebuilt-in sync would apply, so the result is pixel-identical to the native effect
while working for arbitrary colors. The lift animation only runs on lifted-state
changes, so when color props change while the bar is already lifted, the
applicator jumps the strip straight to the blended end color (same pattern as
the existing background jump).
Medium / large headers —
CollapsingToolbarLayout.statusBarScrimCollapsing headers pair the ABL with a
CollapsingToolbarLayout(CTL), whichhas its own mechanism:
statusBarScrim. It shares the alpha animation of thecontent scrim (fades in when the header collapses, out when it expands) and is
drawn after all children and after the collapsing title — unlike the content
scrim, which is drawn below the toolbar child and therefore cannot mask
toolbar content.
The default matters here: with scroll-only flags (no
exitUntilCollapsed) theCTL exits the screen but leaves the toolbar behind the status-bar area.
statusBarScrimproperty can be used to mask it. We default it to the contentscrim color (
scrolledBackgroundColor), which makes the masking seamless and isa visual no-op in the common opaque
exitUntilCollapsedconfiguration.One artifact needed a workaround: both CTL scrims share the same
scrimAlphaand overlap in the status-bar strip, so mid-fade the strip composites two layers
at partial alpha — visibly darker than either scrim duration of the fade. The
fix removes the overlap: our content scrim (the bottom layer) is a small
ColorDrawablesubclass that skips the strip band whenever a status bar scrimis installed. The band is exactly
[-offset, -offset + topInset]in CTLcoordinates — the same rect CTL assigns to the status bar scrim — and the
content scrim's visible top edge always aligns with the strip top, so the
exclusion is a single clip. The offset is tracked with an
OnOffsetChangedListenerand the inset by observingdispatchApplyWindowInsets(both ABL and CTL install their own
OnApplyWindowInsetsListener, so settingours would silently replace Material's).
Defaults and policy
statusBarScrimColorunsetsmallmedium/largefully opaque color — translucent headers stay see-through instead of
getting a double-composited band. An explicit color is always honored.
'transparent'disables the scrim entirely.smallstrip is constant while themedium/largescrim shows onlywhen collapsed — this asymmetry mirrors the native Material widgets.
Changes
statusBarScrimColorprop (fabric spec, public Android types,config plumbing, view manager) reusing the
BACKGROUND_COLORSinvalidation flag.
StackHeaderAppBarLayout.Small: installed aPaintDrawablestatus barstrip and a
LiftOnScrollProgressListenerkeeping it in sync with thebar's effective color; the listener is detached when an explicit color is
set.
StackHeaderApplicator: newapplyStatusBarScrimstep dispatching perheader type, with the opacity policy in a single
resolveStatusBarScrimColorhelper; invisible collapsing scrims arenormalized to
null.StackHeaderContentScrimDrawable+ exclusion plumbing inStackHeaderAppBarLayout.Collapsing: the content scrim skips thestatus-bar strip while a status bar scrim is installed, so the two fading
scrims never stack.
test-stack-header-status-bar-scrim-androidSFT.Visual documentation
4533.mp4
Test plan
Run
test-stack-header-status-bar-scrim-android,test-stack-header-background-android.Checklist
Stack created with GitHub Stacks CLI • Give Feedback 💬