mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-02-11 22:32:38 +08:00
Add support for more Scroll Events to Android
Summary: public This adds support for onScrollBeginDrag/End onMomentumScrolBegin/End Reviewed By: astreet Differential Revision: D2739035 fb-gh-sync-id: 2a49d1df54e5f5cd82008bdb0ffde0881ba39aff
This commit is contained in:
committed by
facebook-github-bot-7
parent
1fabd86048
commit
fcf0431d25
@@ -19,6 +19,7 @@ import com.facebook.react.common.annotations.VisibleForTesting;
|
||||
import com.facebook.react.uimanager.UIManagerModule;
|
||||
import com.facebook.react.uimanager.events.NativeGestureUtil;
|
||||
import com.facebook.react.views.scroll.ScrollEvent;
|
||||
import com.facebook.react.views.scroll.ScrollEventType;
|
||||
|
||||
/**
|
||||
* Wraps {@link RecyclerView} providing interface similar to `ScrollView.js` where each children
|
||||
@@ -311,6 +312,7 @@ public class RecyclerViewBackedScrollView extends RecyclerView {
|
||||
.dispatchEvent(ScrollEvent.obtain(
|
||||
getId(),
|
||||
SystemClock.uptimeMillis(),
|
||||
ScrollEventType.SCROLL,
|
||||
0, /* offsetX = 0, horizontal scrolling only */
|
||||
calculateAbsoluteOffset(),
|
||||
getWidth(),
|
||||
|
||||
@@ -14,7 +14,7 @@ import com.facebook.react.uimanager.ReactProp;
|
||||
import com.facebook.react.uimanager.ThemedReactContext;
|
||||
import com.facebook.react.uimanager.ViewGroupManager;
|
||||
import com.facebook.react.views.scroll.ReactScrollViewCommandHelper;
|
||||
import com.facebook.react.views.scroll.ScrollEvent;
|
||||
import com.facebook.react.views.scroll.ScrollEventType;
|
||||
|
||||
/**
|
||||
* View manager for {@link RecyclerViewBackedScrollView}.
|
||||
@@ -91,7 +91,7 @@ public class RecyclerViewBackedScrollViewManager extends
|
||||
public @Nullable
|
||||
Map getExportedCustomDirectEventTypeConstants() {
|
||||
return MapBuilder.builder()
|
||||
.put(ScrollEvent.EVENT_NAME, MapBuilder.of("registrationName", "onScroll"))
|
||||
.put(ScrollEventType.SCROLL.getJSEventName(), MapBuilder.of("registrationName", "onScroll"))
|
||||
.put(
|
||||
ContentSizeChangeEvent.EVENT_NAME,
|
||||
MapBuilder.of("registrationName", "onContentSizeChange"))
|
||||
|
||||
@@ -23,10 +23,19 @@ public class ReactHorizontalScrollView extends HorizontalScrollView {
|
||||
|
||||
private final OnScrollDispatchHelper mOnScrollDispatchHelper = new OnScrollDispatchHelper();
|
||||
|
||||
private boolean mSendMomentumEvents;
|
||||
private boolean mDragging;
|
||||
private boolean mFlinging;
|
||||
private boolean mDoneFlinging;
|
||||
|
||||
public ReactHorizontalScrollView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public void setSendMomentumEvents(boolean sendMomentumEvents) {
|
||||
mSendMomentumEvents = sendMomentumEvents;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
MeasureSpecAssertions.assertExplicitMeasureSpec(widthMeasureSpec, heightMeasureSpec);
|
||||
@@ -47,7 +56,10 @@ public class ReactHorizontalScrollView extends HorizontalScrollView {
|
||||
super.onScrollChanged(x, y, oldX, oldY);
|
||||
|
||||
if (mOnScrollDispatchHelper.onScrollChanged(x, y)) {
|
||||
ReactScrollViewHelper.emitScrollEvent(this, x, y);
|
||||
if (mFlinging) {
|
||||
mDoneFlinging = false;
|
||||
}
|
||||
ReactScrollViewHelper.emitScrollEvent(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,9 +67,43 @@ public class ReactHorizontalScrollView extends HorizontalScrollView {
|
||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||
if (super.onInterceptTouchEvent(ev)) {
|
||||
NativeGestureUtil.notifyNativeGestureStarted(this, ev);
|
||||
ReactScrollViewHelper.emitScrollBeginDragEvent(this);
|
||||
mDragging = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent ev) {
|
||||
int action = ev.getAction() & MotionEvent.ACTION_MASK;
|
||||
if (action == MotionEvent.ACTION_UP && mDragging) {
|
||||
ReactScrollViewHelper.emitScrollEndDragEvent(this);
|
||||
mDragging = false;
|
||||
}
|
||||
return super.onTouchEvent(ev);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fling(int velocityX) {
|
||||
super.fling(velocityX);
|
||||
if (mSendMomentumEvents) {
|
||||
mFlinging = true;
|
||||
ReactScrollViewHelper.emitScrollMomentumBeginEvent(this);
|
||||
Runnable r = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (mDoneFlinging) {
|
||||
mFlinging = false;
|
||||
ReactScrollViewHelper.emitScrollMomentumEndEvent(ReactHorizontalScrollView.this);
|
||||
} else {
|
||||
mDoneFlinging = true;
|
||||
ReactHorizontalScrollView.this.postOnAnimationDelayed(this, ReactScrollViewHelper.MOMENTUM_DELAY);
|
||||
}
|
||||
}
|
||||
};
|
||||
postOnAnimationDelayed(r, ReactScrollViewHelper.MOMENTUM_DELAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,19 @@ public class ReactHorizontalScrollViewManager
|
||||
view.setHorizontalScrollBarEnabled(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computing momentum events is potentially expensive since we post a runnable on the UI thread
|
||||
* to see when it is done. We only do that if {@param sendMomentumEvents} is set to true. This
|
||||
* is handled automatically in js by checking if there is a listener on the momentum events.
|
||||
*
|
||||
* @param view
|
||||
* @param sendMomentumEvents
|
||||
*/
|
||||
@ReactProp(name = "sendMomentumEvents")
|
||||
public void setSendMomentumEvents(ReactHorizontalScrollView view, boolean sendMomentumEvents) {
|
||||
view.setSendMomentumEvents(sendMomentumEvents);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveCommand(
|
||||
ReactHorizontalScrollView scrollView,
|
||||
|
||||
@@ -36,11 +36,19 @@ public class ReactScrollView extends ScrollView implements ReactClippingViewGrou
|
||||
|
||||
private boolean mRemoveClippedSubviews;
|
||||
private @Nullable Rect mClippingRect;
|
||||
private boolean mSendMomentumEvents;
|
||||
private boolean mDragging;
|
||||
private boolean mFlinging;
|
||||
private boolean mDoneFlinging;
|
||||
|
||||
public ReactScrollView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public void setSendMomentumEvents(boolean sendMomentumEvents) {
|
||||
mSendMomentumEvents = sendMomentumEvents;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
MeasureSpecAssertions.assertExplicitMeasureSpec(widthMeasureSpec, heightMeasureSpec);
|
||||
@@ -73,7 +81,11 @@ public class ReactScrollView extends ScrollView implements ReactClippingViewGrou
|
||||
updateClippingRect();
|
||||
}
|
||||
|
||||
ReactScrollViewHelper.emitScrollEvent(this, x, y);
|
||||
if (mFlinging) {
|
||||
mDoneFlinging = false;
|
||||
}
|
||||
|
||||
ReactScrollViewHelper.emitScrollEvent(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,12 +93,24 @@ public class ReactScrollView extends ScrollView implements ReactClippingViewGrou
|
||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||
if (super.onInterceptTouchEvent(ev)) {
|
||||
NativeGestureUtil.notifyNativeGestureStarted(this, ev);
|
||||
ReactScrollViewHelper.emitScrollBeginDragEvent(this);
|
||||
mDragging = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent ev) {
|
||||
int action = ev.getAction() & MotionEvent.ACTION_MASK;
|
||||
if (action == MotionEvent.ACTION_UP && mDragging) {
|
||||
ReactScrollViewHelper.emitScrollEndDragEvent(this);
|
||||
mDragging = false;
|
||||
}
|
||||
return super.onTouchEvent(ev);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRemoveClippedSubviews(boolean removeClippedSubviews) {
|
||||
if (removeClippedSubviews && mClippingRect == null) {
|
||||
@@ -120,4 +144,26 @@ public class ReactScrollView extends ScrollView implements ReactClippingViewGrou
|
||||
public void getClippingRect(Rect outClippingRect) {
|
||||
outClippingRect.set(Assertions.assertNotNull(mClippingRect));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fling(int velocityY) {
|
||||
super.fling(velocityY);
|
||||
if (mSendMomentumEvents) {
|
||||
mFlinging = true;
|
||||
ReactScrollViewHelper.emitScrollMomentumBeginEvent(this);
|
||||
Runnable r = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (mDoneFlinging) {
|
||||
mFlinging = false;
|
||||
ReactScrollViewHelper.emitScrollMomentumEndEvent(ReactScrollView.this);
|
||||
} else {
|
||||
mDoneFlinging = true;
|
||||
ReactScrollView.this.postOnAnimationDelayed(this, ReactScrollViewHelper.MOMENTUM_DELAY);
|
||||
}
|
||||
}
|
||||
};
|
||||
postOnAnimationDelayed(r, ReactScrollViewHelper.MOMENTUM_DELAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,18 +21,41 @@ import com.facebook.react.uimanager.UIManagerModule;
|
||||
*/
|
||||
public class ReactScrollViewHelper {
|
||||
|
||||
public static final long MOMENTUM_DELAY = 20;
|
||||
|
||||
/**
|
||||
* Shared by {@link ReactScrollView} and {@link ReactHorizontalScrollView}.
|
||||
*/
|
||||
public static void emitScrollEvent(ViewGroup scrollView, int scrollX, int scrollY) {
|
||||
public static void emitScrollEvent(ViewGroup scrollView) {
|
||||
emitScrollEvent(scrollView, ScrollEventType.SCROLL);
|
||||
}
|
||||
|
||||
public static void emitScrollBeginDragEvent(ViewGroup scrollView) {
|
||||
emitScrollEvent(scrollView, ScrollEventType.BEGIN_DRAG);
|
||||
}
|
||||
|
||||
public static void emitScrollEndDragEvent(ViewGroup scrollView) {
|
||||
emitScrollEvent(scrollView, ScrollEventType.END_DRAG);
|
||||
}
|
||||
|
||||
public static void emitScrollMomentumBeginEvent(ViewGroup scrollView) {
|
||||
emitScrollEvent(scrollView, ScrollEventType.MOMENTUM_BEGIN);
|
||||
}
|
||||
|
||||
public static void emitScrollMomentumEndEvent(ViewGroup scrollView) {
|
||||
emitScrollEvent(scrollView, ScrollEventType.MOMENTUM_END);
|
||||
}
|
||||
|
||||
private static void emitScrollEvent(ViewGroup scrollView, ScrollEventType scrollEventType) {
|
||||
View contentView = scrollView.getChildAt(0);
|
||||
ReactContext reactContext = (ReactContext) scrollView.getContext();
|
||||
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent(
|
||||
ScrollEvent.obtain(
|
||||
scrollView.getId(),
|
||||
SystemClock.uptimeMillis(),
|
||||
scrollX,
|
||||
scrollY,
|
||||
scrollEventType,
|
||||
scrollView.getScrollX(),
|
||||
scrollView.getScrollY(),
|
||||
contentView.getWidth(),
|
||||
contentView.getHeight(),
|
||||
scrollView.getWidth(),
|
||||
|
||||
@@ -52,6 +52,19 @@ public class ReactScrollViewManager
|
||||
view.setRemoveClippedSubviews(removeClippedSubviews);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computing momentum events is potentially expensive since we post a runnable on the UI thread
|
||||
* to see when it is done. We only do that if {@param sendMomentumEvents} is set to true. This
|
||||
* is handled automatically in js by checking if there is a listener on the momentum events.
|
||||
*
|
||||
* @param view
|
||||
* @param sendMomentumEvents
|
||||
*/
|
||||
@ReactProp(name = "sendMomentumEvents")
|
||||
public void setSendMomentumEvents(ReactScrollView view, boolean sendMomentumEvents) {
|
||||
view.setSendMomentumEvents(sendMomentumEvents);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Map<String, Integer> getCommandsMap() {
|
||||
return ReactScrollViewCommandHelper.getCommandsMap();
|
||||
@@ -86,12 +99,12 @@ public class ReactScrollViewManager
|
||||
|
||||
public static Map createExportedCustomDirectEventTypeConstants() {
|
||||
return MapBuilder.builder()
|
||||
.put(ScrollEvent.EVENT_NAME, MapBuilder.of("registrationName", "onScroll"))
|
||||
.put("topScrollBeginDrag", MapBuilder.of("registrationName", "onScrollBeginDrag"))
|
||||
.put("topScrollEndDrag", MapBuilder.of("registrationName", "onScrollEndDrag"))
|
||||
.put("topScrollAnimationEnd", MapBuilder.of("registrationName", "onScrollAnimationEnd"))
|
||||
.put("topMomentumScrollBegin", MapBuilder.of("registrationName", "onMomentumScrollBegin"))
|
||||
.put("topMomentumScrollEnd", MapBuilder.of("registrationName", "onMomentumScrollEnd"))
|
||||
.put(ScrollEventType.SCROLL.getJSEventName(), MapBuilder.of("registrationName", "onScroll"))
|
||||
.put(ScrollEventType.BEGIN_DRAG.getJSEventName(), MapBuilder.of("registrationName", "onScrollBeginDrag"))
|
||||
.put(ScrollEventType.END_DRAG.getJSEventName(), MapBuilder.of("registrationName", "onScrollEndDrag"))
|
||||
.put(ScrollEventType.ANIMATION_END.getJSEventName(), MapBuilder.of("registrationName", "onScrollAnimationEnd"))
|
||||
.put(ScrollEventType.MOMENTUM_BEGIN.getJSEventName(), MapBuilder.of("registrationName", "onMomentumScrollBegin"))
|
||||
.put(ScrollEventType.MOMENTUM_END.getJSEventName(), MapBuilder.of("registrationName", "onMomentumScrollEnd"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,13 @@
|
||||
|
||||
package com.facebook.react.views.scroll;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.lang.Override;
|
||||
|
||||
import android.support.v4.util.Pools;
|
||||
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.uimanager.PixelUtil;
|
||||
@@ -27,18 +30,18 @@ public class ScrollEvent extends Event<ScrollEvent> {
|
||||
private static final Pools.SynchronizedPool<ScrollEvent> EVENTS_POOL =
|
||||
new Pools.SynchronizedPool<>(3);
|
||||
|
||||
public static final String EVENT_NAME = "topScroll";
|
||||
|
||||
private int mScrollX;
|
||||
private int mScrollY;
|
||||
private int mContentWidth;
|
||||
private int mContentHeight;
|
||||
private int mScrollViewWidth;
|
||||
private int mScrollViewHeight;
|
||||
private @Nullable ScrollEventType mScrollEventType;
|
||||
|
||||
public static ScrollEvent obtain(
|
||||
int viewTag,
|
||||
long timestampMs,
|
||||
ScrollEventType scrollEventType,
|
||||
int scrollX,
|
||||
int scrollY,
|
||||
int contentWidth,
|
||||
@@ -52,6 +55,7 @@ public class ScrollEvent extends Event<ScrollEvent> {
|
||||
event.init(
|
||||
viewTag,
|
||||
timestampMs,
|
||||
scrollEventType,
|
||||
scrollX,
|
||||
scrollY,
|
||||
contentWidth,
|
||||
@@ -72,6 +76,7 @@ public class ScrollEvent extends Event<ScrollEvent> {
|
||||
private void init(
|
||||
int viewTag,
|
||||
long timestampMs,
|
||||
ScrollEventType scrollEventType,
|
||||
int scrollX,
|
||||
int scrollY,
|
||||
int contentWidth,
|
||||
@@ -79,6 +84,7 @@ public class ScrollEvent extends Event<ScrollEvent> {
|
||||
int scrollViewWidth,
|
||||
int scrollViewHeight) {
|
||||
super.init(viewTag, timestampMs);
|
||||
mScrollEventType = scrollEventType;
|
||||
mScrollX = scrollX;
|
||||
mScrollY = scrollY;
|
||||
mContentWidth = contentWidth;
|
||||
@@ -89,7 +95,7 @@ public class ScrollEvent extends Event<ScrollEvent> {
|
||||
|
||||
@Override
|
||||
public String getEventName() {
|
||||
return EVENT_NAME;
|
||||
return Assertions.assertNotNull(mScrollEventType).getJSEventName();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -98,6 +104,15 @@ public class ScrollEvent extends Event<ScrollEvent> {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canCoalesce() {
|
||||
// Only SCROLL events can be coalesced, all others can not be
|
||||
if (mScrollEventType == ScrollEventType.SCROLL) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatch(RCTEventEmitter rctEventEmitter) {
|
||||
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData());
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
package com.facebook.react.views.scroll;
|
||||
|
||||
/**
|
||||
* Scroll event types that JS module RCTEventEmitter can understand
|
||||
*/
|
||||
public enum ScrollEventType {
|
||||
BEGIN_DRAG("topScrollBeginDrag"),
|
||||
END_DRAG("topScrollEndDrag"),
|
||||
SCROLL("topScroll"),
|
||||
MOMENTUM_BEGIN("topMomentumScrollBegin"),
|
||||
MOMENTUM_END("topMomentumScrollEnd"),
|
||||
ANIMATION_END("topScrollAnimationEnd");
|
||||
|
||||
private final String mJSEventName;
|
||||
|
||||
ScrollEventType(String jsEventName) {
|
||||
mJSEventName = jsEventName;
|
||||
}
|
||||
|
||||
public String getJSEventName() {
|
||||
return mJSEventName;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user