Skip to content

feat(Android, Tabs): integrate with ScrollViewMarker for special effects#4192

Open
kkafar wants to merge 3 commits into
@kkafar/svm-tabs-integrationfrom
@kkafar/svm-tabs-integration-2
Open

feat(Android, Tabs): integrate with ScrollViewMarker for special effects#4192
kkafar wants to merge 3 commits into
@kkafar/svm-tabs-integrationfrom
@kkafar/svm-tabs-integration-2

Conversation

@kkafar

@kkafar kkafar commented Jun 18, 2026

Copy link
Copy Markdown
Member

Description

Repeated selection of the already-active tab runs "special effects" — pop the
stack to root and/or scroll the content to the top. Until now the scroll view
backing the scroll-to-top effect was located purely by walking the first
descendant of the tab content, which is fragile and only ever matched
android.widget.ScrollView.

This PR integrates that lookup with the ScrollViewMarker mechanism: a marker
mounted around the content's scroll view registers it with the nearest seeking
ancestor, giving the tab a reliable handle to the correct scroll view instead
of relying solely on tree traversal. It also adds NestedScrollView support.

Closes https://github.com/software-mansion/react-native-screens-labs/issues/1538
Closes https://github.com/software-mansion/react-native-screens-labs/issues/1423

Note

The pop-to-root is not-yet-implemented due to modal constraints. It requires changes in the tabs model (simplifying here).

Changes

  • TabsScreen now implements ScrollViewSeeking; it stores the scroll view
    registered by a ScrollViewMarker in a WeakReference and exposes it via
    contentScrollView().
  • SpecialEffectsHandler resolves the content scroll view from the registered
    marker first, falling back to the descendant-chain search. Both
    repeated-selection effects (pop-to-root and scroll-to-top) now anchor on the
    selected TabsScreen rather than the container content view.
  • Scroll-to-top now handles both ScrollView and NestedScrollView.
  • ViewFinder.findScrollViewInFirstDescendantChain widened to return
    ViewGroup? and match NestedScrollView, so the fallback path can resolve
    it too.

Visual docs

before after
android-tabs-special-effects-before.mov
android-tabs-special-effects-after.mov

Test plan

Tested on Android via the example app scenario:
apps/src/tests/component-integration-tests/scroll-view-marker/test-stack-svm-tabs-special-effects/

Steps:

  1. Open the scenario, select a tab, scroll its content down and/or push screens
    onto the stack.
  2. Tap the already-selected tab again.
  3. Verify the stack pops to root and the content smoothly scrolls to the top,
    for both ScrollView- and NestedScrollView-backed content.

Checklist

  • Included code example that can be used to test this change.
  • For visual changes, included screenshots / GIFs / recordings documenting the change.
  • For API changes, updated relevant public types.
  • Ensured that CI passes

@kkafar kkafar marked this pull request as draft June 18, 2026 16:18
kkafar added 2 commits June 22, 2026 15:48
I'm not happy with how detection currently works. I think instead of
relying on UIManager callbacks, we should just rely on
`onAttachedToWindow` & `onDetachedFromWindow` callbacks. We should
carefully consider that option to simplify the code.
@kkafar kkafar force-pushed the @kkafar/svm-tabs-integration-2 branch from 738c715 to f95ff89 Compare June 22, 2026 13:48
Anchor both repeated-tab-selection effects on the selected TabsScreen
instead of the container content view, so pop-to-root and scroll-to-top
search from the same, tighter-scoped root.

Widen findScrollViewInFirstDescendantChain to also match NestedScrollView
so the fallback path can resolve it, and collapse the duplicated
ScrollView/NestedScrollView branches in trySmoothScrollToTop into a
single when over the shared scroll check.
@kkafar kkafar marked this pull request as ready for review June 22, 2026 14:49
@kkafar kkafar requested review from Copilot, kligarski, kmichalikk and t0maboro and removed request for Copilot and kligarski June 22, 2026 14:49

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves Android Tabs “repeated selection” special effects by integrating tab content scroll-to-top behavior with the ScrollViewMarker registration mechanism, making the scroll view lookup more reliable and adding NestedScrollView support.

Changes:

  • TabsScreen now participates in the ScrollViewMarker mechanism by implementing ScrollViewSeeking and storing a weak reference to the registered content scroll view.
  • Repeated-selection special effects now resolve the scroll view via marker registration first, then fall back to the existing descendant-chain search.
  • The fallback scroll-view search and smooth-scroll behavior now support both ScrollView and NestedScrollView.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
android/src/main/java/com/swmansion/rnscreens/gamma/tabs/screen/TabsScreen.kt Makes the selected tab screen capable of receiving a registered scroll view reference from ScrollViewMarker.
android/src/main/java/com/swmansion/rnscreens/gamma/tabs/container/TabsContainer.kt Updates repeated-selection effects to target the selected TabsScreen and adds scroll view resolution + NestedScrollView scrolling support.
android/src/main/java/com/swmansion/rnscreens/gamma/helpers/ViewFinder.kt Expands the fallback “first descendant chain” lookup to match NestedScrollView and returns ViewGroup?.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 20 to +24
class TabsScreen(
val reactContext: ThemedReactContext,
) : ViewGroup(reactContext),
FragmentProviding {
override fun onLayout(
changed: Boolean,
l: Int,
t: Int,
r: Int,
b: Int,
) = Unit

FragmentProviding,
ScrollViewSeeking {
/**
* Attempts to scroll to top if the passed view is a `ScrollView` or `NestedScrollView`
*/
private fun trySmoothScrollToTop(maybeScrollView: ViewGroup): Boolean {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of ViewGroup, you could use FrameLayout which is closer to the actual classes

Also, gemini came up with sth like that

interface ScrollableContainer {
    fun scrollToTop()
}

// Extension functions to easily wrap them
fun ScrollView.asScrollable() = object : ScrollableContainer {
    override fun scrollToTop() = smoothScrollTo(0, 0)
}

fun NestedScrollView.asScrollable() = object : ScrollableContainer {
    override fun scrollToTop() = smoothScrollTo(0, 0)
}

this is probably overkill, but you could make the typing more strict this way

tabsScreenDelegate.get()?.onFragmentConfigurationChange(this, config)
}

// ScrollViewSeeking

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe region / endregion instead?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants