记录:RN 实现侧滑删除
这段时间 RN 项目里有个侧滑删除的需求,本文对实现过程做个记录。
代码采用纯 RN 编写,总体来说实现起来还是比较简单的,核心部分不超过 100 行代码。
效果图
核心代码
import React, {Component} from 'react';
import {Animated, Easing, PanResponder, View, ViewPropTypes} from 'react-native';
type Props = {
style?: ViewPropTypes.style,
containerStyle: ViewPropTypes.style,
// 最大滑动距离
maxSlide?: number,
};
export default class SlideView extends Component<Props> {
static defaultProps = {};
/** 侧滑动画 @private */
_slideAnim = {
// 右边的滑动距离
right: new Animated.Value(0)
};
constructor(props) {
super(props);
this._initGesture()
}
componentDidMount() {
this._initAnim()
}
componentWillUnmount() {
this._allAnim.forEach(value => value.stop());
}
render() {
const {children, style, containerStyle} = this.props;
return (
<View {...this._gesture.panHandlers} style={[{overflow: 'hidden'}, style]}>
<Animated.View style={[{right: this._slideAnim.right}, containerStyle]}>
{children}
</Animated.View>
</View>
);
}
/** @private */
_switchSlide() {
const slideRight = this._slideAnim.right.__getValue();
if (slideRight !== this.props.maxSlide && slideRight > this.props.maxSlide / 2) {
// 超过一半,展开
this._animShow.start();
} else {
// 没超过一半,收起
this._animHide.start();
}
}
/** @private */
_initAnim() {
this._allAnim = [
this._animShow = Animated.timing(
this._slideAnim.right,
{
toValue: this.props.maxSlide,
duration: 100,
easing: Easing.in,
}
),
this._animHide = Animated.timing(
this._slideAnim.right,
{
toValue: 0,
duration: 100,
easing: Easing.in,
}
),
];
}
/** @private */
_initGesture() {
let slideRightDistance = 0;
this._gesture = PanResponder.create({
onPanResponderTerminationRequest: () => true,
// 如果 x 轴的位移大于 y 轴位移则需要消费 move 事件
onMoveShouldSetPanResponder: (e, gs) => Math.abs(gs.dx) > Math.abs(gs.dy),
// 如果成功申请了事件,就记录当前滑动的距离
onPanResponderGrant: () => slideRightDistance = this._slideAnim.right.__getValue(),
onPanResponderMove: (e, gs) => {
// 当前向左滑动距离
const slideRight = this._slideAnim.right.__getValue();
if (gs.dx < 0) {
// 如果向左滑动距离小于最大滑动值则可以左滑
this.props.maxSlide > slideRight && this._slideAnim.right.setValue(-gs.dx)
} else if (slideRight > 0) {
this._slideAnim.right.setValue(slideRightDistance - gs.dx)
}
},
onPanResponderRelease: () => this._switchSlide(),
onPanResponderTerminate: () => this._switchSlide(),
})
}
}
关于手势处理部分有几个需要注意的点:
- 不能申请 down 事件,即 onStartShouldSetPanResponder。若消费了 down 事件会造成外层列表无法滑动等问题。
- 在事件被终止时也需要调用 _switchSlide 方法还原 view 状态。
侧滑组件封装
class CollectionItem extends Component {
render() {
const {style, contentView, slideMenuWidth, slideMenu} = this.props;
return (
<SlideView
style={style}
containerStyle={{flex: 1, flexDirection: 'row'}}
maxSlide={slideMenuWidth}>
<View style={{width: '100%', padding: 20}}>{contentView}</View>
{slideMenu}
</SlideView>
);
}
}