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:
Andrei Coman 2016-09-27 04:22:59 -07:00 committed by Facebook Github Bot 2
parent cfae3e376d
commit 30989dd24a
1 changed files with 45 additions and 4 deletions

View File

@ -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;
}
}