mirror of
https://github.com/status-im/react-native.git
synced 2025-02-26 16:10:58 +00:00
Add pagingEnabled to HorizontalScrollView
Summary: This adds support for pagingEnabled to the HorizontalScrollView. This is an initial implementation. Because Android doesn't provide great details about what is happening with a scroll view after you are done touching it, we have some post touch handling. This is kicked off either by touch up or a fling call. Once we are doing that handling, we start a runnable that basically checks if we are still scrolling. If we are, we just schedule that runnable again and check a frame later. If we are done scrolling (no onScrollChanged since we last fired), we could be in one of two states, the fling is done or we are done snapping to the page boundary. If we are in the fling done case, we then check if we need to scroll to a page boundary. If so, we call smoothScrollTo and schedule ourself to check onScroll events again until done with that scroll. If we are done with both (either we only did momentum scroll or we did that and then snapped to page), we can then fire the final event and stop checking. This logic is all in handlePostTouchScrolling. Because of the decision to only do page scrolling after momentum ends, we do allow you to scroll through with momentum a number of pages and the transition can be a little strange where it stops a sec and then slides to be page aligned. As a follow up, we can probably smooth that up by changing the value we pass to super.fling() that would adjust it to be let momentum carry it to the page boundary. Reviewed By: weicool Differential Revision: D3207608 fb-gh-sync-id: 02f62970ed9a5e3a5f9c0d959402756bc4b3699e fbshipit-source-id: 02f62970ed9a5e3a5f9c0d959402756bc4b3699e
This commit is contained in:
parent
a26afd2d73
commit
a3146e41a2
@ -222,7 +222,6 @@ var ScrollView = React.createClass({
|
|||||||
* When true, the scroll view stops on multiples of the scroll view's size
|
* When true, the scroll view stops on multiples of the scroll view's size
|
||||||
* when scrolling. This can be used for horizontal pagination. The default
|
* when scrolling. This can be used for horizontal pagination. The default
|
||||||
* value is false.
|
* value is false.
|
||||||
* @platform ios
|
|
||||||
*/
|
*/
|
||||||
pagingEnabled: PropTypes.bool,
|
pagingEnabled: PropTypes.bool,
|
||||||
/**
|
/**
|
||||||
|
@ -11,6 +11,7 @@ package com.facebook.react.views.scroll;
|
|||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
@ -35,10 +36,11 @@ public class ReactHorizontalScrollView extends HorizontalScrollView implements
|
|||||||
|
|
||||||
private final OnScrollDispatchHelper mOnScrollDispatchHelper = new OnScrollDispatchHelper();
|
private final OnScrollDispatchHelper mOnScrollDispatchHelper = new OnScrollDispatchHelper();
|
||||||
|
|
||||||
|
private boolean mActivelyScrolling;
|
||||||
private @Nullable Rect mClippingRect;
|
private @Nullable Rect mClippingRect;
|
||||||
private boolean mDoneFlinging;
|
|
||||||
private boolean mDragging;
|
private boolean mDragging;
|
||||||
private boolean mFlinging;
|
private boolean mPagingEnabled = false;
|
||||||
|
private @Nullable Runnable mPostTouchRunnable;
|
||||||
private boolean mRemoveClippedSubviews;
|
private boolean mRemoveClippedSubviews;
|
||||||
private boolean mScrollEnabled = true;
|
private boolean mScrollEnabled = true;
|
||||||
private boolean mSendMomentumEvents;
|
private boolean mSendMomentumEvents;
|
||||||
@ -71,6 +73,10 @@ public class ReactHorizontalScrollView extends HorizontalScrollView implements
|
|||||||
mScrollEnabled = scrollEnabled;
|
mScrollEnabled = scrollEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setPagingEnabled(boolean pagingEnabled) {
|
||||||
|
mPagingEnabled = pagingEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||||
MeasureSpecAssertions.assertExplicitMeasureSpec(widthMeasureSpec, heightMeasureSpec);
|
MeasureSpecAssertions.assertExplicitMeasureSpec(widthMeasureSpec, heightMeasureSpec);
|
||||||
@ -95,9 +101,7 @@ public class ReactHorizontalScrollView extends HorizontalScrollView implements
|
|||||||
updateClippingRect();
|
updateClippingRect();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mFlinging) {
|
mActivelyScrolling = true;
|
||||||
mDoneFlinging = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ReactScrollViewHelper.emitScrollEvent(this);
|
ReactScrollViewHelper.emitScrollEvent(this);
|
||||||
}
|
}
|
||||||
@ -129,32 +133,22 @@ public class ReactHorizontalScrollView extends HorizontalScrollView implements
|
|||||||
if (action == MotionEvent.ACTION_UP && mDragging) {
|
if (action == MotionEvent.ACTION_UP && mDragging) {
|
||||||
ReactScrollViewHelper.emitScrollEndDragEvent(this);
|
ReactScrollViewHelper.emitScrollEndDragEvent(this);
|
||||||
mDragging = false;
|
mDragging = false;
|
||||||
|
// After the touch finishes, we may need to do some scrolling afterwards either as a result
|
||||||
|
// of a fling or because we need to page align the content
|
||||||
|
handlePostTouchScrolling();
|
||||||
}
|
}
|
||||||
return super.onTouchEvent(ev);
|
return super.onTouchEvent(ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void fling(int velocityX) {
|
public void fling(int velocityX) {
|
||||||
super.fling(velocityX);
|
if (mPagingEnabled) {
|
||||||
if (mSendMomentumEvents) {
|
smoothScrollToPage(velocityX);
|
||||||
mFlinging = true;
|
|
||||||
ReactScrollViewHelper.emitScrollMomentumBeginEvent(this);
|
|
||||||
Runnable r = new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
if (mDoneFlinging) {
|
|
||||||
mFlinging = false;
|
|
||||||
ReactScrollViewHelper.emitScrollMomentumEndEvent(ReactHorizontalScrollView.this);
|
|
||||||
} else {
|
} else {
|
||||||
mDoneFlinging = true;
|
super.fling(velocityX);
|
||||||
ReactHorizontalScrollView.this.postOnAnimationDelayed(this, ReactScrollViewHelper.MOMENTUM_DELAY);
|
|
||||||
}
|
}
|
||||||
|
handlePostTouchScrolling();
|
||||||
}
|
}
|
||||||
};
|
|
||||||
postOnAnimationDelayed(r, ReactScrollViewHelper.MOMENTUM_DELAY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||||
@ -210,4 +204,79 @@ public class ReactHorizontalScrollView extends HorizontalScrollView implements
|
|||||||
}
|
}
|
||||||
super.draw(canvas);
|
super.draw(canvas);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This handles any sort of scrolling that may occur after a touch is finished. This may be
|
||||||
|
* momentum scrolling (fling) or because you have pagingEnabled on the scroll view. Because we
|
||||||
|
* don't get any events from Android about this lifecycle, we do all our detection by creating a
|
||||||
|
* runnable that checks if we scrolled in the last frame and if so assumes we are still scrolling.
|
||||||
|
*/
|
||||||
|
@TargetApi(16)
|
||||||
|
private void handlePostTouchScrolling() {
|
||||||
|
// If we aren't going to do anything (send events or snap to page), we can early out.
|
||||||
|
if (!mSendMomentumEvents && !mPagingEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we are already handling this which may occur if this is called by both the touch up
|
||||||
|
// and a fling call
|
||||||
|
if (mPostTouchRunnable != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mSendMomentumEvents) {
|
||||||
|
ReactScrollViewHelper.emitScrollMomentumBeginEvent(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
mActivelyScrolling = false;
|
||||||
|
mPostTouchRunnable = new Runnable() {
|
||||||
|
|
||||||
|
private boolean mSnappingToPage = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (mActivelyScrolling) {
|
||||||
|
// We are still scrolling so we just post to check again a frame later
|
||||||
|
mActivelyScrolling = false;
|
||||||
|
ReactHorizontalScrollView.this.postOnAnimationDelayed(this, ReactScrollViewHelper.MOMENTUM_DELAY);
|
||||||
|
} else {
|
||||||
|
boolean doneWithAllScrolling = true;
|
||||||
|
if (mPagingEnabled && !mSnappingToPage) {
|
||||||
|
// Only if we have pagingEnabled and we have not snapped to the page do we
|
||||||
|
// need to continue checking for the scroll. And we cause that scroll by asking for it
|
||||||
|
mSnappingToPage = true;
|
||||||
|
smoothScrollToPage(0);
|
||||||
|
doneWithAllScrolling = false;
|
||||||
|
}
|
||||||
|
if (doneWithAllScrolling) {
|
||||||
|
if (mSendMomentumEvents) {
|
||||||
|
ReactScrollViewHelper.emitScrollMomentumEndEvent(ReactHorizontalScrollView.this);
|
||||||
|
}
|
||||||
|
ReactHorizontalScrollView.this.mPostTouchRunnable = null;
|
||||||
|
} else {
|
||||||
|
ReactHorizontalScrollView.this.postOnAnimationDelayed(this, ReactScrollViewHelper.MOMENTUM_DELAY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
postOnAnimationDelayed(mPostTouchRunnable, ReactScrollViewHelper.MOMENTUM_DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will smooth scroll us to the nearest page boundary
|
||||||
|
* It currently just looks at where the content is relative to the page and slides to the nearest
|
||||||
|
* page. It is intended to be run after we are done scrolling, and handling any momentum
|
||||||
|
* scrolling.
|
||||||
|
*/
|
||||||
|
private void smoothScrollToPage(int velocity) {
|
||||||
|
int width = getWidth();
|
||||||
|
int currentX = getScrollX();
|
||||||
|
// TODO (t11123799) - Should we do anything beyond linear accounting of the velocity
|
||||||
|
int predictedX = currentX + velocity;
|
||||||
|
int page = currentX / width;
|
||||||
|
if (predictedX > page * width + width / 2) {
|
||||||
|
page = page + 1;
|
||||||
|
}
|
||||||
|
smoothScrollTo(page * width, getScrollY());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,6 +69,11 @@ public class ReactHorizontalScrollViewManager
|
|||||||
view.setSendMomentumEvents(sendMomentumEvents);
|
view.setSendMomentumEvents(sendMomentumEvents);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "pagingEnabled")
|
||||||
|
public void setPagingEnabled(ReactHorizontalScrollView view, boolean pagingEnabled) {
|
||||||
|
view.setPagingEnabled(pagingEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void receiveCommand(
|
public void receiveCommand(
|
||||||
ReactHorizontalScrollView scrollView,
|
ReactHorizontalScrollView scrollView,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user