Add pointerEvents prop to RN Android scroll views

Summary:
Per discussion in the previous diff D33672110, it's ok to add the `pointerEvents` prop to scrollview. This will help prevent scrolling on the ScrollView if pointerEvents is set to `box-none`, or `none`.

Corresponding doc changes are in https://github.com/facebook/react-native-website/pull/2936

Changelog:
[Android][Added] - Add new API in ScrollView and HorizontalScrollView to process pointerEvents prop.

Reviewed By: javache

Differential Revision: D33699223

fbshipit-source-id: 1cae5113e9e7d988fc4c4765c41d817a321804c4
This commit is contained in:
Xin Chen
2022-01-21 13:40:09 -08:00
committed by Facebook GitHub Bot
parent d0f0234656
commit 48f6967ae8
5 changed files with 74 additions and 0 deletions
@@ -7,6 +7,8 @@
package com.facebook.react.uimanager;
import java.util.Locale;
/**
* Possible values for pointer events that a view and its descendants should receive. See
* https://developer.mozilla.org/en-US/docs/Web/CSS/pointer-events for more info.
@@ -25,4 +27,16 @@ public enum PointerEvents {
/** Container and all of its children receive touch events (like pointerEvents is unspecified). */
AUTO,
;
public static PointerEvents parsePointerEvents(String pointerEventsStr) {
return PointerEvents.valueOf(pointerEventsStr.toUpperCase(Locale.US).replace("-", "_"));
}
public static boolean canBeTouchTarget(PointerEvents pointerEvents) {
return pointerEvents == AUTO || pointerEvents == PointerEvents.BOX_ONLY;
}
public static boolean canChildrenBeTouchTarget(PointerEvents pointerEvents) {
return pointerEvents == PointerEvents.AUTO || pointerEvents == PointerEvents.BOX_NONE;
}
}
@@ -40,6 +40,7 @@ import com.facebook.react.common.build.ReactBuildConfig;
import com.facebook.react.modules.i18nmanager.I18nUtil;
import com.facebook.react.uimanager.FabricViewStateManager;
import com.facebook.react.uimanager.MeasureSpecAssertions;
import com.facebook.react.uimanager.PointerEvents;
import com.facebook.react.uimanager.ReactClippingViewGroup;
import com.facebook.react.uimanager.ReactClippingViewGroupHelper;
import com.facebook.react.uimanager.ReactOverflowViewWithInset;
@@ -105,6 +106,7 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
private final FabricViewStateManager mFabricViewStateManager = new FabricViewStateManager();
private final ReactScrollViewScrollState mReactScrollViewScrollState;
private final ValueAnimator DEFAULT_FLING_ANIMATOR = ObjectAnimator.ofInt(this, "scrollX", 0, 0);
private PointerEvents mPointerEvents = PointerEvents.AUTO;
private final Rect mTempRect = new Rect();
@@ -452,6 +454,11 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
return false;
}
// We intercept the touch event if the children are not supposed to receive it.
if (!PointerEvents.canChildrenBeTouchTarget(mPointerEvents)) {
return true;
}
try {
if (super.onInterceptTouchEvent(ev)) {
NativeGestureUtil.notifyNativeGestureStarted(this, ev);
@@ -519,6 +526,11 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
return false;
}
// We do not accept the touch event if this view is not supposed to receive it.
if (!PointerEvents.canBeTouchTarget(mPointerEvents)) {
return false;
}
mVelocityHelper.calculateVelocity(ev);
int action = ev.getAction() & MotionEvent.ACTION_MASK;
if (action == MotionEvent.ACTION_UP && mDragging) {
@@ -1253,4 +1265,12 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
this, velocityX, 0, Math.max(0, computeHorizontalScrollRange() - getWidth()), 0)
.x;
}
public void setPointerEvents(PointerEvents pointerEvents) {
mPointerEvents = pointerEvents;
}
public PointerEvents getPointerEvents() {
return mPointerEvents;
}
}
@@ -16,6 +16,7 @@ import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.DisplayMetricsHolder;
import com.facebook.react.uimanager.PixelUtil;
import com.facebook.react.uimanager.PointerEvents;
import com.facebook.react.uimanager.ReactClippingViewGroupHelper;
import com.facebook.react.uimanager.ReactStylesDiffMap;
import com.facebook.react.uimanager.Spacing;
@@ -321,4 +322,13 @@ public class ReactHorizontalScrollViewManager extends ViewGroupManager<ReactHori
view.scrollTo(0, 0);
}
}
@ReactProp(name = ViewProps.POINTER_EVENTS)
public void setPointerEvents(ReactHorizontalScrollView view, @Nullable String pointerEventsStr) {
if (pointerEventsStr == null) {
view.setPointerEvents(PointerEvents.AUTO);
} else {
view.setPointerEvents(PointerEvents.parsePointerEvents(pointerEventsStr));
}
}
}
@@ -36,6 +36,7 @@ import com.facebook.react.R;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.uimanager.FabricViewStateManager;
import com.facebook.react.uimanager.MeasureSpecAssertions;
import com.facebook.react.uimanager.PointerEvents;
import com.facebook.react.uimanager.ReactClippingViewGroup;
import com.facebook.react.uimanager.ReactClippingViewGroupHelper;
import com.facebook.react.uimanager.ReactOverflowViewWithInset;
@@ -102,6 +103,7 @@ public class ReactScrollView extends ScrollView
private final ReactScrollViewScrollState mReactScrollViewScrollState =
new ReactScrollViewScrollState(ViewCompat.LAYOUT_DIRECTION_LTR);
private final ValueAnimator DEFAULT_FLING_ANIMATOR = ObjectAnimator.ofInt(this, "scrollY", 0, 0);
private PointerEvents mPointerEvents = PointerEvents.AUTO;
public ReactScrollView(Context context) {
this(context, null);
@@ -332,6 +334,11 @@ public class ReactScrollView extends ScrollView
return false;
}
// We intercept the touch event if the children are not supposed to receive it.
if (!PointerEvents.canChildrenBeTouchTarget(mPointerEvents)) {
return true;
}
try {
if (super.onInterceptTouchEvent(ev)) {
NativeGestureUtil.notifyNativeGestureStarted(this, ev);
@@ -357,6 +364,11 @@ public class ReactScrollView extends ScrollView
return false;
}
// We do not accept the touch event if this view is not supposed to receive it.
if (!PointerEvents.canBeTouchTarget(mPointerEvents)) {
return false;
}
mVelocityHelper.calculateVelocity(ev);
int action = ev.getAction() & MotionEvent.ACTION_MASK;
if (action == MotionEvent.ACTION_UP && mDragging) {
@@ -1115,4 +1127,12 @@ public class ReactScrollView extends ScrollView
return ReactScrollViewHelper.predictFinalScrollPosition(this, 0, velocityY, 0, getMaxScrollY())
.y;
}
public void setPointerEvents(PointerEvents pointerEvents) {
mPointerEvents = pointerEvents;
}
public PointerEvents getPointerEvents() {
return mPointerEvents;
}
}
@@ -19,6 +19,7 @@ import com.facebook.react.common.MapBuilder;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.DisplayMetricsHolder;
import com.facebook.react.uimanager.PixelUtil;
import com.facebook.react.uimanager.PointerEvents;
import com.facebook.react.uimanager.ReactClippingViewGroupHelper;
import com.facebook.react.uimanager.ReactStylesDiffMap;
import com.facebook.react.uimanager.Spacing;
@@ -363,4 +364,13 @@ public class ReactScrollViewManager extends ViewGroupManager<ReactScrollView>
MapBuilder.of("registrationName", "onMomentumScrollEnd"))
.build();
}
@ReactProp(name = ViewProps.POINTER_EVENTS)
public void setPointerEvents(ReactScrollView view, @Nullable String pointerEventsStr) {
if (pointerEventsStr == null) {
view.setPointerEvents(PointerEvents.AUTO);
} else {
view.setPointerEvents(PointerEvents.parsePointerEvents(pointerEventsStr));
}
}
}