Fix ReactSwipeRefreshLayout
Summary: `ReactSwipeRefreshLayout` extends `SwipeRefreshLayout` which does not play nice with Android's touch handling system. There are two problems: 1. `SwipeRefreshLayout` overrides and swallows `requestDisallowInterceptTouchEvent`, which means that Views underneath the `SwipeRefreshLayout` will not interact correctly with parent Views of `SwipeRefreshLayout`. We've seen this in practice by H-ScrollViews having their touches intercepted by an enclosing ViewPager. This is fixed by passing `requestDisallowInterceptTouchEvent` up to the parents of `SwipeRefreshLayout`. 2. `SwipeRefreshLayout` overrides `onInterceptTouchEvent` and never calls `super.onInterceptTouchEvent`, therefore ignoring the value of `disallowIntercept`. That means that it will intercept some touches when it shouldn't. One such case is again the H-ScrollView, which should receive all horizontal scrolls and stop `SwipeRefreshLayout` from intercepting any touch events after scrolling. Currently, after the H-ScrollView starts scrolling, it is still possible to get the `SwipeRefreshLayout` to detect and emit refresh events. This is fixed by checking and blocking on horizontal scrolls. Reviewed By: foghina Differential Revision: D3929893 fbshipit-source-id: e6f8050fb554e53318a7ca564c49c20cb5137df9
This commit is contained in:
parent
cfae3e376d
commit
30989dd24a
|
@ -11,10 +11,11 @@ package com.facebook.react.views.swiperefresh;
|
|||
|
||||
import android.support.v4.widget.SwipeRefreshLayout;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ViewConfiguration;
|
||||
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.uimanager.events.NativeGestureUtil;
|
||||
import com.facebook.react.uimanager.PixelUtil;
|
||||
import com.facebook.react.uimanager.events.NativeGestureUtil;
|
||||
|
||||
/**
|
||||
* Basic extension of {@link SwipeRefreshLayout} with ReactNative-specific functionality.
|
||||
|
@ -24,13 +25,15 @@ public class ReactSwipeRefreshLayout extends SwipeRefreshLayout {
|
|||
private static final float DEFAULT_CIRCLE_TARGET = 64;
|
||||
|
||||
private boolean mDidLayout = false;
|
||||
|
||||
private boolean mRefreshing = false;
|
||||
private float mProgressViewOffset = 0;
|
||||
|
||||
private int mTouchSlop;
|
||||
private float mPrevTouchX;
|
||||
private boolean mIntercepted;
|
||||
|
||||
public ReactSwipeRefreshLayout(ReactContext reactContext) {
|
||||
super(reactContext);
|
||||
mTouchSlop = ViewConfiguration.get(reactContext).getScaledTouchSlop();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -71,12 +74,50 @@ public class ReactSwipeRefreshLayout extends SwipeRefreshLayout {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link SwipeRefreshLayout} overrides {@link ViewGroup#requestDisallowInterceptTouchEvent} and
|
||||
* swallows it. This means that any component underneath SwipeRefreshLayout will now interact
|
||||
* incorrectly with Views that are above SwipeRefreshLayout. We fix that by transmitting the call
|
||||
* to this View's parents.
|
||||
*/
|
||||
@Override
|
||||
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
|
||||
if (getParent() != null) {
|
||||
getParent().requestDisallowInterceptTouchEvent(disallowIntercept);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||
if (super.onInterceptTouchEvent(ev)) {
|
||||
if (shouldInterceptTouchEvent(ev) && super.onInterceptTouchEvent(ev)) {
|
||||
NativeGestureUtil.notifyNativeGestureStarted(this, ev);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link SwipeRefreshLayout} completely bypasses ViewGroup's "disallowIntercept" by overriding
|
||||
* {@link ViewGroup#onInterceptTouchEvent} and never calling super.onInterceptTouchEvent().
|
||||
* This means that horizontal scrolls will always be intercepted, even though they shouldn't, so
|
||||
* we have to check for that manually here.
|
||||
*/
|
||||
private boolean shouldInterceptTouchEvent(MotionEvent ev) {
|
||||
switch (ev.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
mPrevTouchX = ev.getX();
|
||||
mIntercepted = false;
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
final float eventX = ev.getX();
|
||||
final float xDiff = Math.abs(eventX - mPrevTouchX);
|
||||
|
||||
if (mIntercepted || xDiff > mTouchSlop) {
|
||||
mIntercepted = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue