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.support.v4.widget.SwipeRefreshLayout;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
|
import android.view.ViewConfiguration;
|
||||||
|
|
||||||
import com.facebook.react.bridge.ReactContext;
|
import com.facebook.react.bridge.ReactContext;
|
||||||
import com.facebook.react.uimanager.events.NativeGestureUtil;
|
|
||||||
import com.facebook.react.uimanager.PixelUtil;
|
import com.facebook.react.uimanager.PixelUtil;
|
||||||
|
import com.facebook.react.uimanager.events.NativeGestureUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Basic extension of {@link SwipeRefreshLayout} with ReactNative-specific functionality.
|
* 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 static final float DEFAULT_CIRCLE_TARGET = 64;
|
||||||
|
|
||||||
private boolean mDidLayout = false;
|
private boolean mDidLayout = false;
|
||||||
|
|
||||||
private boolean mRefreshing = false;
|
private boolean mRefreshing = false;
|
||||||
private float mProgressViewOffset = 0;
|
private float mProgressViewOffset = 0;
|
||||||
|
private int mTouchSlop;
|
||||||
|
private float mPrevTouchX;
|
||||||
|
private boolean mIntercepted;
|
||||||
|
|
||||||
public ReactSwipeRefreshLayout(ReactContext reactContext) {
|
public ReactSwipeRefreshLayout(ReactContext reactContext) {
|
||||||
super(reactContext);
|
super(reactContext);
|
||||||
|
mTouchSlop = ViewConfiguration.get(reactContext).getScaledTouchSlop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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
|
@Override
|
||||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||||
if (super.onInterceptTouchEvent(ev)) {
|
if (shouldInterceptTouchEvent(ev) && super.onInterceptTouchEvent(ev)) {
|
||||||
NativeGestureUtil.notifyNativeGestureStarted(this, ev);
|
NativeGestureUtil.notifyNativeGestureStarted(this, ev);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
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