mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-04-24 04:16:00 +08:00
Remove createReactClass from SwipeableRow (#21876)
Summary: Related to #21581 Remove createReactClass from SwipeableRow. - All flow tests succeed. -RNTester > SwipeableFlatList, scroll, swipe left, swipe right -RNTester > SwipeableListView, scroll, swipe left, swipe right [GENERAL] [ENHANCEMENT] [SwipeableRow.js] - rm createReactClass Pull Request resolved: https://github.com/facebook/react-native/pull/21876 Reviewed By: TheSavior Differential Revision: D12828034 Pulled By: RSNara fbshipit-source-id: df02ed14e72bf6b0ad3cff33076a6f40870266a8
This commit is contained in:
committed by
Facebook Github Bot
parent
adc8a33fcf
commit
14e1628ba8
@@ -14,11 +14,9 @@ const Animated = require('Animated');
|
||||
const I18nManager = require('I18nManager');
|
||||
const PanResponder = require('PanResponder');
|
||||
const React = require('React');
|
||||
const PropTypes = require('prop-types');
|
||||
const StyleSheet = require('StyleSheet');
|
||||
const View = require('View');
|
||||
|
||||
const createReactClass = require('create-react-class');
|
||||
const emptyFunction = require('fbjs/lib/emptyFunction');
|
||||
|
||||
import type {LayoutEvent, PressEvent} from 'CoreEventTypes';
|
||||
@@ -55,21 +53,28 @@ const RIGHT_SWIPE_BOUNCE_BACK_DURATION = 300;
|
||||
* how far the finger swipes, and not the actual animation distance.
|
||||
*/
|
||||
const RIGHT_SWIPE_THRESHOLD = 30 * SLOW_SPEED_SWIPE_FACTOR;
|
||||
const DEFAULT_SWIPE_THRESHOLD = 30;
|
||||
|
||||
type Props = $ReadOnly<{|
|
||||
children?: ?React.Node,
|
||||
isOpen?: ?boolean,
|
||||
maxSwipeDistance?: ?number,
|
||||
onClose?: ?Function,
|
||||
onOpen?: ?Function,
|
||||
onSwipeEnd?: ?Function,
|
||||
onSwipeStart?: ?Function,
|
||||
onClose?: ?() => void,
|
||||
onOpen?: ?() => void,
|
||||
onSwipeEnd?: ?() => void,
|
||||
onSwipeStart?: ?() => void,
|
||||
preventSwipeRight?: ?boolean,
|
||||
shouldBounceOnMount?: ?boolean,
|
||||
slideoutView?: ?React.Node,
|
||||
swipeThreshold?: ?number,
|
||||
|}>;
|
||||
|
||||
type State = {
|
||||
currentLeft: Animated.Value,
|
||||
isSwipeableViewRendered: boolean,
|
||||
rowHeight: ?number,
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a swipable row that allows taps on the main item and a custom View
|
||||
* on the item hidden behind the row. Typically this should be used in
|
||||
@@ -77,74 +82,98 @@ type Props = $ReadOnly<{|
|
||||
* used in a normal ListView. See the renderRow for SwipeableListView to see how
|
||||
* to use this component separately.
|
||||
*/
|
||||
const SwipeableRow = createReactClass({
|
||||
displayName: 'SwipeableRow',
|
||||
_panResponder: {},
|
||||
_previousLeft: CLOSED_LEFT_POSITION,
|
||||
_timeoutID: (null: ?TimeoutID),
|
||||
class SwipeableRow extends React.Component<Props, State> {
|
||||
_handleMoveShouldSetPanResponderCapture = (
|
||||
event: PressEvent,
|
||||
gestureState: GestureState,
|
||||
): boolean => {
|
||||
// Decides whether a swipe is responded to by this component or its child
|
||||
return gestureState.dy < 10 && this._isValidSwipe(gestureState);
|
||||
};
|
||||
|
||||
propTypes: {
|
||||
children: PropTypes.any,
|
||||
isOpen: PropTypes.bool,
|
||||
preventSwipeRight: PropTypes.bool,
|
||||
maxSwipeDistance: PropTypes.number.isRequired,
|
||||
onOpen: PropTypes.func.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
onSwipeEnd: PropTypes.func.isRequired,
|
||||
onSwipeStart: PropTypes.func.isRequired,
|
||||
// Should bounce the row on mount
|
||||
shouldBounceOnMount: PropTypes.bool,
|
||||
_handlePanResponderGrant = (
|
||||
event: PressEvent,
|
||||
gestureState: GestureState,
|
||||
): void => {};
|
||||
|
||||
_handlePanResponderMove = (
|
||||
event: PressEvent,
|
||||
gestureState: GestureState,
|
||||
): void => {
|
||||
if (this._isSwipingExcessivelyRightFromClosedPosition(gestureState)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.onSwipeStart && this.props.onSwipeStart();
|
||||
|
||||
if (this._isSwipingRightFromClosed(gestureState)) {
|
||||
this._swipeSlowSpeed(gestureState);
|
||||
} else {
|
||||
this._swipeFullSpeed(gestureState);
|
||||
}
|
||||
};
|
||||
|
||||
_onPanResponderTerminationRequest = (
|
||||
event: PressEvent,
|
||||
gestureState: GestureState,
|
||||
): boolean => {
|
||||
return false;
|
||||
};
|
||||
|
||||
_handlePanResponderEnd = (
|
||||
event: PressEvent,
|
||||
gestureState: GestureState,
|
||||
): void => {
|
||||
const horizontalDistance = IS_RTL ? -gestureState.dx : gestureState.dx;
|
||||
if (this._isSwipingRightFromClosed(gestureState)) {
|
||||
this.props.onOpen && this.props.onOpen();
|
||||
this._animateBounceBack(RIGHT_SWIPE_BOUNCE_BACK_DURATION);
|
||||
} else if (this._shouldAnimateRemainder(gestureState)) {
|
||||
if (horizontalDistance < 0) {
|
||||
// Swiped left
|
||||
this.props.onOpen && this.props.onOpen();
|
||||
this._animateToOpenPositionWith(gestureState.vx, horizontalDistance);
|
||||
} else {
|
||||
// Swiped right
|
||||
this.props.onClose && this.props.onClose();
|
||||
this._animateToClosedPosition();
|
||||
}
|
||||
} else {
|
||||
if (this._previousLeft === CLOSED_LEFT_POSITION) {
|
||||
this._animateToClosedPosition();
|
||||
} else {
|
||||
this._animateToOpenPosition();
|
||||
}
|
||||
}
|
||||
|
||||
this.props.onSwipeEnd && this.props.onSwipeEnd();
|
||||
};
|
||||
|
||||
_panResponder = PanResponder.create({
|
||||
onMoveShouldSetPanResponderCapture: this
|
||||
._handleMoveShouldSetPanResponderCapture,
|
||||
onPanResponderGrant: this._handlePanResponderGrant,
|
||||
onPanResponderMove: this._handlePanResponderMove,
|
||||
onPanResponderRelease: this._handlePanResponderEnd,
|
||||
onPanResponderTerminationRequest: this._onPanResponderTerminationRequest,
|
||||
onPanResponderTerminate: this._handlePanResponderEnd,
|
||||
onShouldBlockNativeResponder: (event, gestureState) => false,
|
||||
});
|
||||
|
||||
_previousLeft = CLOSED_LEFT_POSITION;
|
||||
_timeoutID: ?TimeoutID = null;
|
||||
|
||||
state = {
|
||||
currentLeft: new Animated.Value(this._previousLeft),
|
||||
/**
|
||||
* A ReactElement that is unveiled when the user swipes
|
||||
* In order to render component A beneath component B, A must be rendered
|
||||
* before B. However, this will cause "flickering", aka we see A briefly
|
||||
* then B. To counter this, _isSwipeableViewRendered flag is used to set
|
||||
* component A to be transparent until component B is loaded.
|
||||
*/
|
||||
slideoutView: PropTypes.node.isRequired,
|
||||
/**
|
||||
* The minimum swipe distance required before fully animating the swipe. If
|
||||
* the user swipes less than this distance, the item will return to its
|
||||
* previous (open/close) position.
|
||||
*/
|
||||
swipeThreshold: PropTypes.number.isRequired,
|
||||
},
|
||||
|
||||
getInitialState(): Object {
|
||||
return {
|
||||
currentLeft: new Animated.Value(this._previousLeft),
|
||||
/**
|
||||
* In order to render component A beneath component B, A must be rendered
|
||||
* before B. However, this will cause "flickering", aka we see A briefly
|
||||
* then B. To counter this, _isSwipeableViewRendered flag is used to set
|
||||
* component A to be transparent until component B is loaded.
|
||||
*/
|
||||
isSwipeableViewRendered: false,
|
||||
rowHeight: (null: ?number),
|
||||
};
|
||||
},
|
||||
|
||||
getDefaultProps(): Object {
|
||||
return {
|
||||
isOpen: false,
|
||||
preventSwipeRight: false,
|
||||
maxSwipeDistance: 0,
|
||||
onOpen: emptyFunction,
|
||||
onClose: emptyFunction,
|
||||
onSwipeEnd: emptyFunction,
|
||||
onSwipeStart: emptyFunction,
|
||||
swipeThreshold: 30,
|
||||
};
|
||||
},
|
||||
|
||||
UNSAFE_componentWillMount(): void {
|
||||
this._panResponder = PanResponder.create({
|
||||
onMoveShouldSetPanResponderCapture: this
|
||||
._handleMoveShouldSetPanResponderCapture,
|
||||
onPanResponderGrant: this._handlePanResponderGrant,
|
||||
onPanResponderMove: this._handlePanResponderMove,
|
||||
onPanResponderRelease: this._handlePanResponderEnd,
|
||||
onPanResponderTerminationRequest: this._onPanResponderTerminationRequest,
|
||||
onPanResponderTerminate: this._handlePanResponderEnd,
|
||||
onShouldBlockNativeResponder: (event, gestureState) => false,
|
||||
});
|
||||
},
|
||||
isSwipeableViewRendered: false,
|
||||
rowHeight: null,
|
||||
};
|
||||
|
||||
componentDidMount(): void {
|
||||
if (this.props.shouldBounceOnMount) {
|
||||
@@ -156,23 +185,26 @@ const SwipeableRow = createReactClass({
|
||||
this._animateBounceBack(ON_MOUNT_BOUNCE_DURATION);
|
||||
}, ON_MOUNT_BOUNCE_DELAY);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps(nextProps: Object): void {
|
||||
UNSAFE_componentWillReceiveProps(nextProps: $Shape<Props>): void {
|
||||
/**
|
||||
* We do not need an "animateOpen(noCallback)" because this animation is
|
||||
* handled internally by this component.
|
||||
*/
|
||||
if (this.props.isOpen && !nextProps.isOpen) {
|
||||
const isOpen = this.props.isOpen ?? false;
|
||||
const nextIsOpen = nextProps.isOpen ?? false;
|
||||
|
||||
if (isOpen && !nextIsOpen) {
|
||||
this._animateToClosedPosition();
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this._timeoutID != null) {
|
||||
clearTimeout(this._timeoutID);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
render(): React.Element<any> {
|
||||
// The view hidden behind the main view
|
||||
@@ -201,61 +233,34 @@ const SwipeableRow = createReactClass({
|
||||
{swipeableView}
|
||||
</View>
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
close(): void {
|
||||
this.props.onClose();
|
||||
this.props.onClose && this.props.onClose();
|
||||
this._animateToClosedPosition();
|
||||
},
|
||||
}
|
||||
|
||||
_onSwipeableViewLayout(event: LayoutEvent): void {
|
||||
_onSwipeableViewLayout = (event: LayoutEvent): void => {
|
||||
this.setState({
|
||||
isSwipeableViewRendered: true,
|
||||
rowHeight: event.nativeEvent.layout.height,
|
||||
});
|
||||
},
|
||||
|
||||
_handleMoveShouldSetPanResponderCapture(
|
||||
event: PressEvent,
|
||||
gestureState: GestureState,
|
||||
): boolean {
|
||||
// Decides whether a swipe is responded to by this component or its child
|
||||
return gestureState.dy < 10 && this._isValidSwipe(gestureState);
|
||||
},
|
||||
|
||||
_handlePanResponderGrant(
|
||||
event: PressEvent,
|
||||
gestureState: GestureState,
|
||||
): void {},
|
||||
|
||||
_handlePanResponderMove(event: PressEvent, gestureState: GestureState): void {
|
||||
if (this._isSwipingExcessivelyRightFromClosedPosition(gestureState)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.onSwipeStart();
|
||||
|
||||
if (this._isSwipingRightFromClosed(gestureState)) {
|
||||
this._swipeSlowSpeed(gestureState);
|
||||
} else {
|
||||
this._swipeFullSpeed(gestureState);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
_isSwipingRightFromClosed(gestureState: GestureState): boolean {
|
||||
const gestureStateDx = IS_RTL ? -gestureState.dx : gestureState.dx;
|
||||
return this._previousLeft === CLOSED_LEFT_POSITION && gestureStateDx > 0;
|
||||
},
|
||||
}
|
||||
|
||||
_swipeFullSpeed(gestureState: GestureState): void {
|
||||
this.state.currentLeft.setValue(this._previousLeft + gestureState.dx);
|
||||
},
|
||||
}
|
||||
|
||||
_swipeSlowSpeed(gestureState: GestureState): void {
|
||||
this.state.currentLeft.setValue(
|
||||
this._previousLeft + gestureState.dx / SLOW_SPEED_SWIPE_FACTOR,
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
_isSwipingExcessivelyRightFromClosedPosition(
|
||||
gestureState: GestureState,
|
||||
@@ -270,14 +275,7 @@ const SwipeableRow = createReactClass({
|
||||
this._isSwipingRightFromClosed(gestureState) &&
|
||||
gestureStateDx > RIGHT_SWIPE_THRESHOLD
|
||||
);
|
||||
},
|
||||
|
||||
_onPanResponderTerminationRequest(
|
||||
event: PressEvent,
|
||||
gestureState: GestureState,
|
||||
): boolean {
|
||||
return false;
|
||||
},
|
||||
}
|
||||
|
||||
_animateTo(
|
||||
toValue: number,
|
||||
@@ -292,14 +290,15 @@ const SwipeableRow = createReactClass({
|
||||
this._previousLeft = toValue;
|
||||
callback();
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
_animateToOpenPosition(): void {
|
||||
const maxSwipeDistance = IS_RTL
|
||||
? -this.props.maxSwipeDistance
|
||||
: this.props.maxSwipeDistance;
|
||||
this._animateTo(-maxSwipeDistance);
|
||||
},
|
||||
const maxSwipeDistance = this.props.maxSwipeDistance ?? 0;
|
||||
const directionAwareMaxSwipeDistance = IS_RTL
|
||||
? -maxSwipeDistance
|
||||
: maxSwipeDistance;
|
||||
this._animateTo(-directionAwareMaxSwipeDistance);
|
||||
}
|
||||
|
||||
_animateToOpenPositionWith(speed: number, distMoved: number): void {
|
||||
/**
|
||||
@@ -310,26 +309,25 @@ const SwipeableRow = createReactClass({
|
||||
speed > HORIZONTAL_FULL_SWIPE_SPEED_THRESHOLD
|
||||
? speed
|
||||
: HORIZONTAL_FULL_SWIPE_SPEED_THRESHOLD;
|
||||
const maxSwipeDistance = this.props.maxSwipeDistance ?? 0;
|
||||
/**
|
||||
* Calculate the duration the row should take to swipe the remaining distance
|
||||
* at the same speed the user swiped (or the speed threshold)
|
||||
*/
|
||||
const duration = Math.abs(
|
||||
(this.props.maxSwipeDistance - Math.abs(distMoved)) / speed,
|
||||
);
|
||||
const maxSwipeDistance = IS_RTL
|
||||
? -this.props.maxSwipeDistance
|
||||
: this.props.maxSwipeDistance;
|
||||
this._animateTo(-maxSwipeDistance, duration);
|
||||
},
|
||||
const duration = Math.abs((maxSwipeDistance - Math.abs(distMoved)) / speed);
|
||||
const directionAwareMaxSwipeDistance = IS_RTL
|
||||
? -maxSwipeDistance
|
||||
: maxSwipeDistance;
|
||||
this._animateTo(-directionAwareMaxSwipeDistance, duration);
|
||||
}
|
||||
|
||||
_animateToClosedPosition(duration: number = SWIPE_DURATION): void {
|
||||
this._animateTo(CLOSED_LEFT_POSITION, duration);
|
||||
},
|
||||
}
|
||||
|
||||
_animateToClosedPositionDuringBounce(): void {
|
||||
_animateToClosedPositionDuringBounce = (): void => {
|
||||
this._animateToClosedPosition(RIGHT_SWIPE_BOUNCE_BACK_DURATION);
|
||||
},
|
||||
};
|
||||
|
||||
_animateBounceBack(duration: number): void {
|
||||
/**
|
||||
@@ -344,12 +342,13 @@ const SwipeableRow = createReactClass({
|
||||
duration,
|
||||
this._animateToClosedPositionDuringBounce,
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
// Ignore swipes due to user's finger moving slightly when tapping
|
||||
_isValidSwipe(gestureState: GestureState): boolean {
|
||||
const preventSwipeRight = this.props.preventSwipeRight ?? false;
|
||||
if (
|
||||
this.props.preventSwipeRight &&
|
||||
preventSwipeRight &&
|
||||
this._previousLeft === CLOSED_LEFT_POSITION &&
|
||||
gestureState.dx > 0
|
||||
) {
|
||||
@@ -357,49 +356,19 @@ const SwipeableRow = createReactClass({
|
||||
}
|
||||
|
||||
return Math.abs(gestureState.dx) > HORIZONTAL_SWIPE_DISTANCE_THRESHOLD;
|
||||
},
|
||||
}
|
||||
|
||||
_shouldAnimateRemainder(gestureState: GestureState): boolean {
|
||||
/**
|
||||
* If user has swiped past a certain distance, animate the rest of the way
|
||||
* if they let go
|
||||
*/
|
||||
const swipeThreshold = this.props.swipeThreshold ?? DEFAULT_SWIPE_THRESHOLD;
|
||||
return (
|
||||
Math.abs(gestureState.dx) > this.props.swipeThreshold ||
|
||||
Math.abs(gestureState.dx) > swipeThreshold ||
|
||||
gestureState.vx > HORIZONTAL_FULL_SWIPE_SPEED_THRESHOLD
|
||||
);
|
||||
},
|
||||
|
||||
_handlePanResponderEnd(event: PressEvent, gestureState: GestureState): void {
|
||||
const horizontalDistance = IS_RTL ? -gestureState.dx : gestureState.dx;
|
||||
if (this._isSwipingRightFromClosed(gestureState)) {
|
||||
this.props.onOpen();
|
||||
this._animateBounceBack(RIGHT_SWIPE_BOUNCE_BACK_DURATION);
|
||||
} else if (this._shouldAnimateRemainder(gestureState)) {
|
||||
if (horizontalDistance < 0) {
|
||||
// Swiped left
|
||||
this.props.onOpen();
|
||||
this._animateToOpenPositionWith(gestureState.vx, horizontalDistance);
|
||||
} else {
|
||||
// Swiped right
|
||||
this.props.onClose();
|
||||
this._animateToClosedPosition();
|
||||
}
|
||||
} else {
|
||||
if (this._previousLeft === CLOSED_LEFT_POSITION) {
|
||||
this._animateToClosedPosition();
|
||||
} else {
|
||||
this._animateToOpenPosition();
|
||||
}
|
||||
}
|
||||
|
||||
this.props.onSwipeEnd();
|
||||
},
|
||||
});
|
||||
|
||||
// TODO: Delete this when `SwipeableRow` uses class syntax.
|
||||
class TypedSwipeableRow extends React.Component<Props> {
|
||||
close() {}
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
@@ -412,4 +381,4 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = ((SwipeableRow: any): Class<TypedSwipeableRow>);
|
||||
module.exports = SwipeableRow;
|
||||
|
||||
Reference in New Issue
Block a user