Skip to content
This repository has been archived by the owner on Nov 27, 2022. It is now read-only.

Commit

Permalink
fix: fix tapping on tab sometimes didn't switch
Browse files Browse the repository at this point in the history
  • Loading branch information
satya164 committed Feb 20, 2019
1 parent 1bca863 commit e91a5b2
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 41 deletions.
81 changes: 48 additions & 33 deletions src/Pager.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const {
max,
min,
multiply,
neq,
or,
round,
set,
Expand All @@ -39,13 +40,15 @@ const TRUE = 1;
const FALSE = 0;
const NOOP = 0;

const UNSET = -1;

const SWIPE_DISTANCE_MINIMUM = 20;
const SWIPE_DISTANCE_MULTIPLIER = 1 / 1.75;

const SPRING_CONFIG = {
damping: 30,
mass: 1,
stiffness: 100,
stiffness: 200,
overshootClamping: true,
restSpeedThreshold: 0.001,
restDisplacementThreshold: 0.001,
Expand All @@ -60,14 +63,15 @@ type Props<T: Route> = {
swipeEnabled: boolean,
swipeDistanceThreshold?: number,
swipeVelocityThreshold: number,
jumpTo: (key: string) => mixed,
jumpToIndex: (index: number) => mixed,
navigationState: NavigationState<T>,
layout: Layout,
children: (props: {
position: Animated.Node<number>,
render: (children: React.Node) => React.Node,
addListener: (listener: Listener) => void,
removeListener: (listener: Listener) => void,
jumpToIndex: (index: number) => void,
}) => React.Node,
};

Expand All @@ -79,17 +83,10 @@ export default class Pager<T: Route> extends React.Component<Props<T>> {
componentDidUpdate(prevProps: Props<T>) {
const { index } = this.props.navigationState;

if (
index !== prevProps.navigationState.index &&
index !== this._pendingIndex
) {
// If the index changed, we need to trigger a tab switch
this._isSwipeGesture.setValue(FALSE);
this._nextIndex.setValue(index);
if (index !== this._currentIndex) {
this._jumpToIndex(index);
}

this._pendingIndex = undefined;

// Update our mappings of animated nodes when props change
if (
prevProps.navigationState.routes.length !==
Expand Down Expand Up @@ -131,7 +128,7 @@ export default class Pager<T: Route> extends React.Component<Props<T>> {
// Current state of the gesture
_velocityX = new Value(0);
_gestureX = new Value(0);
_gestureState = new Value(-1);
_gestureState = new Value(State.UNDETERMINED);
_offsetX = new Value(0);

// Current position of the page (translateX value)
Expand All @@ -144,7 +141,7 @@ export default class Pager<T: Route> extends React.Component<Props<T>> {
_index = new Value(this.props.navigationState.index);

// Next index of the tabs, updated for navigation from outside (tab press, state update)
_nextIndex = new Value(this.props.navigationState.index);
_nextIndex = new Value(UNSET);

// Whether the user is currently dragging the screen
_isSwiping = new Value(FALSE);
Expand All @@ -162,16 +159,27 @@ export default class Pager<T: Route> extends React.Component<Props<T>> {
_swipeDistanceThreshold = new Value(this.props.swipeDistanceThreshold || 180);
_swipeVelocityThreshold = new Value(this.props.swipeVelocityThreshold);

// Pending index change caused by the pager's animation end
// Whether we need to add a listener for position change
// To avoid unnecessary traffic through the bridge, don't add listeners unless needed
_isListening = new Value(FALSE);

// The current index change caused by the pager's animation end
// We store this to skip triggering another transition after state update
// Otherwise there can be weird glitches when tabs switch quickly
_pendingIndex: ?number = undefined;
_currentIndex = this.props.navigationState.index;

// Listeners for the animated value
_positionListeners: Listener[] = [];

_jumpToIndex = (index: number) => {
// If the index changed, we need to trigger a tab switch
this._isSwipeGesture.setValue(FALSE);
this._nextIndex.setValue(index);
};

_addListener = (listener: Listener) => {
this._positionListeners.push(listener);
this._isListening.setValue(TRUE);
};

_removeListener = (listener: Listener) => {
Expand All @@ -180,6 +188,10 @@ export default class Pager<T: Route> extends React.Component<Props<T>> {
if (index > -1) {
this._positionListeners.splice(index, 1);
}

if (this._positionListeners.length === 0) {
this._isListening.setValue(FALSE);
}
};

_handlePositionChange = ([translateX]: [number]) => {
Expand Down Expand Up @@ -230,18 +242,11 @@ export default class Pager<T: Route> extends React.Component<Props<T>> {
// Reset gesture and velocity from previous gesture
set(this._gestureX, 0),
set(this._velocityX, 0),
// Sync the next index value with index
// This makes sure state updates aren't missed
set(this._nextIndex, this._index),
// When spring animation finishes, stop the clock
stopClock(this._clock),
call([this._index], ([value]) => {
this._pendingIndex = value;

// If the index changed, and previous spring was finished, update state
const route = this.props.navigationState.routes[Math.round(value)];

this.props.jumpTo(route.key);
this.props.jumpToIndex(value);
}),
]),
]);
Expand All @@ -258,16 +263,25 @@ export default class Pager<T: Route> extends React.Component<Props<T>> {
]);

_translateX = block([
call([this._position], this._handlePositionChange),
onChange(
// Index changed from outside
this._nextIndex,
cond(or(eq(this._nextIndex, this._index), this._isSwipeGesture), NOOP, [
// Stop any running animations
stopClock(this._clock),
// Update the index to trigger the transition
set(this._index, this._nextIndex),
])
call([this._index], ([value]) => {
this._currentIndex = value;
}),
cond(this._isListening, call([this._position], this._handlePositionChange)),
cond(
this._isSwipeGesture,
NOOP,
onChange(
// Index changed from outside
this._nextIndex,
cond(neq(this._nextIndex, UNSET), [
// Stop any running animations
cond(clockRunning(this._clock), stopClock(this._clock)),
// Update the index to trigger the transition
set(this._index, this._nextIndex),
// Unset next index
set(this._nextIndex, UNSET),
])
)
),
cond(
eq(this._gestureState, State.ACTIVE),
Expand Down Expand Up @@ -350,6 +364,7 @@ export default class Pager<T: Route> extends React.Component<Props<T>> {
position,
addListener: this._addListener,
removeListener: this._removeListener,
jumpToIndex: this._jumpToIndex,
render: children => (
<PanGestureHandler
enabled={layout.width !== 0 && swipeEnabled}
Expand Down
27 changes: 19 additions & 8 deletions src/TabView.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,7 @@ export default class TabView<T: Route> extends React.Component<
setTimeout(() => this.setState({ renderUnfocusedScenes: true }), 0);
}

_jumpTo = (key: string) => {
const index = this.props.navigationState.routes.findIndex(
route => route.key === key
);

_jumpToIndex = (index: number) => {
if (index !== this.props.navigationState.index) {
this.props.onIndexChange(index);
}
Expand Down Expand Up @@ -111,14 +107,29 @@ export default class TabView<T: Route> extends React.Component<
swipeEnabled={swipeEnabled}
swipeDistanceThreshold={swipeDistanceThreshold}
swipeVelocityThreshold={swipeVelocityThreshold}
jumpTo={this._jumpTo}
jumpToIndex={this._jumpToIndex}
>
{({ position, render, addListener, removeListener }) => {
{({ position, render, addListener, removeListener, jumpToIndex }) => {
const jumpTo = (key: string) => {
const index = navigationState.routes.findIndex(
route => route.key === key
);

// A tab switch might occur when we're in the middle of a transition
// In that case, the index might be same as before
// So we conditionally make the pager to update the position
if (navigationState.index === index) {
jumpToIndex(index);
} else {
this._jumpToIndex(index);
}
};

const sceneRendererProps = {
position,
layout,
navigationState,
jumpTo: this._jumpTo,
jumpTo,
addListener,
removeListener,
};
Expand Down

0 comments on commit e91a5b2

Please sign in to comment.