From 211f3145db8bfa5161032442ffb0a8422d05459e Mon Sep 17 00:00:00 2001 From: Jesse Watts-Russell Date: Tue, 22 Aug 2023 07:30:44 -0700 Subject: [PATCH] Fixing nested horizontal scrollview scrolling on Android (#39097) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/39097 Changelog: [Internal][Added] - Created mechanism to have nested horizontal scrollviews (or flatlists/sectionlists) by prioritizing scrolling on deeper nested scrollviews which have the enableNestedScroll prop equal to true. Reviewed By: NickGerleman Differential Revision: D48504762 fbshipit-source-id: 1a6d01f68e142d99ea494e2b5630979c7be2ecce --- .../scroll/ReactHorizontalScrollView.java | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java index 63b7989376a..cfac976347e 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java @@ -471,12 +471,65 @@ public class ReactHorizontalScrollView extends HorizontalScrollView } } + @Nullable + private static HorizontalScrollView findDeepestScrollViewForMotionEvent( + View view, MotionEvent ev) { + return findDeepestScrollViewForMotionEvent(view, ev, true); + } + + @Nullable + private static HorizontalScrollView findDeepestScrollViewForMotionEvent( + View view, MotionEvent ev, boolean skipInitialView) { + if (view == null) { + return null; + } + + Rect rectOnScreen = new Rect(); + view.getGlobalVisibleRect(rectOnScreen); + if (!rectOnScreen.contains((int) ev.getRawX(), (int) ev.getRawY())) { + return null; + } + + // Only consider the current view if it's not the initial view. We check the + // current view first to bail out of recursion. Essentially if there's any + // nested horizontal scrollview with nested scrolling enabled, the parent + // scroll view shouldn't pick up the down event. + if (!skipInitialView + && view instanceof HorizontalScrollView + && ViewCompat.isNestedScrollingEnabled(view) + && (view instanceof ReactHorizontalScrollView + && ((ReactHorizontalScrollView) view).mScrollEnabled)) { + return (HorizontalScrollView) view; + } + + // First, check child views recursively before considering this view. + if (view instanceof ViewGroup) { + for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) { + HorizontalScrollView foundScrollView = + findDeepestScrollViewForMotionEvent(((ViewGroup) view).getChildAt(i), ev, false); + + if (foundScrollView != null) { + // If a deeper HorizontalScrollView is found in child views, return it. + return foundScrollView; + } + } + } + + // Return null if no matching view is found. + return null; + } + @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (!mScrollEnabled) { return false; } + if ((ev.getAction() == MotionEvent.ACTION_DOWN) + && findDeepestScrollViewForMotionEvent(this, ev) != null) { + return false; + } + // We intercept the touch event if the children are not supposed to receive it. if (!PointerEvents.canChildrenBeTouchTarget(mPointerEvents)) { return true;