ReactNative实现左滑删除列表
背景:
需求中要开发一个可以侧滑删除的列表,发现ReactNative并未供这种组件。ReactNative本身其实封装了具有侧滑功能的FlatList在以下目录,但已经被弃用了,研究了一下没有搞懂
./node_modules/react-native/Libraries/Experimental/SwipeableRow/SwipeableFlatList
之后尝试寻找一些第三方库,一个比较好用的是react-native-swipe-list-view
,可以实现组件左右滑动、控制滑动的距离、删除动画以及预置的动画演示,符合要求的话建议用这个
react-native-swipe-list-view GitHub地址
但发现这种滑动实际上是两个组件,一个组件放在下面固定,上面的组件滑动,把下面隐藏的内容露出了,并且不能控制最大滑动的距离,只能禁用一侧的滑动,如果滑动超过了下面隐藏组件的范围,就会露出空白,虽然手势释放会回到设置的openValue,但这两点不符合UI的要求,只能开始研究自己实现.
实现效果
代码
SwipeRow
- 模仿react-native-swipe-list-view,实现一个可以滑动的SwipeRow,用PanResponder手势处理以及Animated库来实现滑动、删除的动画
- 实际上就是一个超过屏幕宽度的view,将隐藏元素放在屏幕外的部分,因为需求只用到左滑,实现的比较简单,利用view的高度绑定AnimatedValue,当删除行时就让view的高度变为0,达到视觉上的删除效果
- 行的高度和隐藏组件的宽带必须传入来实现动画,如果行高或隐藏内容的宽度是不确定的,无法使用该组件
- 如果点击隐藏内容后需要删除该行,可以通过ref调用delete功能,并在delete回调中移除列表中的数据,若在动画前就删除数据,不会触发动画,因为列表重新渲染了(可以在Example中看到示例)
import React, {
PureComponent } from 'react';
import {
Animated,
PanResponder,
TouchableOpacity,
ViewStyle,
GestureResponderEvent,
PanResponderGestureState,
} from 'react-native';
import {
WINDOW_WIDTH } from '../util';
type Props = {
rightContent?: JSX.Element; //右侧屏幕外组件
rightContanierStyle?: ViewStyle;
onRightPress?: () => void; //右侧组件点击事件,若要关闭/删除行,可通过ref调用closwRow/deleteRow方法
rightContentWidth: number; //右侧组件宽度,即最大左滑距离
lineHeight: number; //行高,用来设置删除行动画
onTouchStart?: () => void; //当行响应触摸事件时触发,可用来处理其他SwipeRow
directionalDistanceChangeThreshold?: number; //接管事件的横向滑动距离
//setScrollEnabled: (isEnabled: boolean) => void;
};
type State = {
};
export class SwipeRow extends PureComponent<Props, State> {
lineHeigh: Animated.Value;
directionalDistanceChangeThreshold: number;
constructor(props: any) {
super(props);
this.rightContentWidth = this.props.rightContentWidth || 0;
this.lineHeigh = new Animated.Value(this.props.lineHeight);
this.directionalDistanceChangeThreshold =
this.props.directionalDistanceChangeThreshold || 2;
}
closeRow = () => {
this._startAnimated(0);
};
deleteRow = (callbackFn?: () => void) => {
Animated.timing(this.lineHeigh, {
toValue: 0,
duration: 600,
useNativeDriver: false,
}).start(callbackFn);
};
// x偏移
pan = new Animated.Value(0);
rightContentWidth = 0;
//滑动超过指定距离时接管事件
_onMoveShouldSet = (
e: GestureResponderEvent,
gestureState: PanResponderGestureState,
) => {
const {
dx } = gestureState;
return Math.abs(dx) > this.directionalDistanceChangeThreshold;
};
//接管事件后触发函数
_onPanResponderGrant = () => {
this.props.onTouchStart && this.props.onTouchStart();
};
//滑动
_onPanResponderMove = (
e: GestureResponderEvent,
gestureState: PanResponderGestureState,
) => {
const {
dx, dy } = gestureState;
const absDx = Math.abs(dx);
const absDy = Math.abs(dy);
if (
absDx > this.directionalDistanceChangeThreshold ||
absDy > this.directionalDistanceChangeThreshold
) {
if (absDy > absDx) {
return;
} // moving vertically
//左滑,直接关闭行
if (dx > 0) {
this._startAnimated(0);
}
//右滑,最大距离为隐藏内容宽度
else {
if (dx < -this.rightContentWidth) {
this._startAnimated(-this.