Permalink
Browse files

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
  • Loading branch information...
1 parent cfae3e3 commit 30989dd24ac8a7754b4ecd5c3d976065da478cf7 @andreicoman11 andreicoman11 committed with Facebook Github Bot 2 Sep 27, 2016
@@ -11,10 +11,11 @@
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 @@
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 void onLayout(boolean changed, int left, int top, int right, int bottom)
}
}
+ /**
+ * {@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;
+ }
}

4 comments on commit 30989dd

@fdnhkj
fdnhkj commented on 30989dd Dec 2, 2016

@andreicoman11 when will this fix be released (in non latest tag) ?
I need it for my app and after each release it keeps being in the latest tag (rc versions).

@andreicoman11
Contributor

cc @bestander @mkonicek do you guys know why this hasn't been released yet?

@bestander
Contributor

The commit is present in in 0.36

@fdnhkj
fdnhkj commented on 30989dd Dec 3, 2016

Oh right, my bad! I was looking at the tag specified in the commit description 🙄
Thanks @andreicoman11 @bestander !

Please sign in to comment.