SwipeableFlatList之源码解读

前言

话不多说,这篇文章就是记录我自己研读SwipeableFlatList源码的过程,有不正确的地方还望各位大神指出。

版本信息

  • react-native : 0.57.4
  • react : 16.6.0-alpha.8af6728

正文

一、 概览

目录路径:

./node_modules/react-native/Libraries/Experimental/SwipeableRow/
1900083-b4a2efd8244851ed.png
目录结构

从文件目录中我们可以看到,关于侧滑删除的封装只有六个文件,具体每个文件的封装功能特点我们从文件命名上大概能够猜出一二

以下功能仅为从命名中猜测

  • SwipeableRow.js 滑动item封装
  • SwipeableQuickActions.js item滑动封装
  • SwipeableQuickActionButton.js item滑动底部按钮(删除等)封装
  • SwipeableListViewDataSource.js 数据源封装
  • SwipeableListView.js listview侧滑封装
  • SwipeableFlatList.js flatlist侧滑封装

二、 解读

1. SwipeableRow.js解读
变量常量声明

打开SwipeableRow.js文件发现该文件代码和部分注释共有四百多行代码,其中前58行都是引入的文件以及变量常量声明和注释。其中的注释虽然是英文,但是稍微有点英语基础的都能够看得懂,所以这里的常量声明就直接略过了,不过还是要稍微看一下,省的在下面看到的时候不知道是干什么的,也免得上下来回翻动。

原子性设置

接下来的这段代码跟我们平时写的有很大的差别,这种写法很难懂,但是根据内容基本上能够猜到这部分代码时为了设置属性原子性(OC说法),在最前面有一个ReadOnly<>,从这里也就知道其中列出的属性均为只读的。

type Props = $ReadOnly<{|
  children?: ?React.Node,
  isOpen?: ?boolean,
  maxSwipeDistance?: ?number,
  onClose?: ?Function,
  onOpen?: ?Function,
  onSwipeEnd?: ?Function,
  onSwipeStart?: ?Function,
  preventSwipeRight?: ?boolean,
  shouldBounceOnMount?: ?boolean,
  slideoutView?: ?React.Node,
  swipeThreshold?: ?number,
|}>;
创建模块类以及导出

再之后的代码就分为两大块了,其一是声明一个SwipeableRow类,以及导出模块类。如下:

/**
 * 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
 * conjunction with SwipeableListView for additional functionality, but can be
 * used in a normal ListView. See the renderRow for SwipeableListView to see how
 * to use this component separately.
 */
const SwipeableRow = createReactClass({
  ...
});

module.exports = ((SwipeableRow: any): Class<TypedSwipeableRow>);

根据创建类的注释可以知道,createReactClass()方法创建的React类主要就是一个listview的item,该item支持自定义item以及自定义滑动底部显示自定义视图,并且支持轻点item隐藏底部自定义视图。

以上的创建和导出模块类的写法是不是感觉很不一样,或者说有那么点晦涩难懂的意思?不要担心,这种是ES5中的写法,我们现在写的基本上都是按照ES6的规范来的,把上面的创建和导出翻译为ES6标准就类似下面的:

export default class SwipeableRow extends Component {
    ...
};

这下就能够看的懂了吧 这个说是导出React类,其实就是搞出来个组件嘛 哼...

组件声明

createReactClass();中就是具体的实现过程,其中前五行先声明了三个变量,代码如下:

  displayName: 'SwipeableRow',
  _panResponder: {},
  _previousLeft: CLOSED_LEFT_POSITION,

  mixins: [TimerMixin],

其中需要说明的是变量mixins

  • React Native应用中很多闪退都与计时器有关,具体说是在某个组件被卸载(unmount)之后,计时器却仍然被激活。为了解决这个问题就引入了TimerMixin
  • 如果再组件中引入TimerMixin,就可以把原本的setTimeout(fn, 500)改为this.setTimeout(fn, 500)(只需要在前面加上this.),然后当组件卸载时,所有的计时器事件也会被正确的清除
  • 译注:Mixin属于ES5语法,对于ES6代码来说,无法直接使用Mixin
    如果你的项目是使用ES6代码编写,同时又使用了计时器,那么你就需要铭记在unmount组件时清除(clearTimeout/clearInterval)所有的定时器。
属性检查
 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,
    /**
     * A ReactElement that is unveiled when the user swipes
     */
    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,
  },

以上的代码主要是做属性的类型检查,属性(props)的值来源于父组件

状态(State)初始化、属性(Prop)默认值设置

再往下的代码就都是方法声明以及组件创建了,先简单来看几个函数(这里的函数书写跟我们平常写的不大相同,具体是个什么原理我暂时也没有搞懂,只知道函数结构为functionName() : Object {}Object是返回值类型,其余的跟平常的函数都雷同了),除此之外,还可以看看React/React Native 的ES5 ES6写法对照表这篇文章,这里列举了ES5ES6的不同写法等内容。

  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,
    };
  },

函数getInitialState顾名思义,该函数就是初始化状态值的函数,其中分别为currentLeftisSwipeableViewRenderedrowHeight三个状态(State)赋初始值。
getInitialState函数也是ES5的一种写法,在ES6中一般都在构造函数(constructor(props) {})中初始化

函数getDefaultProps的作用就是为属性(Prop)赋默认值(即如果父组件未给某个属性赋值,在这里可以给个默认值)。
getDefaultProps函数也是ES5的一种写法,在ES6中一般使用static defaultProps = {}中为属性添加默认值

UNSAFE_componentWillMount()
  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,
    });
  },

UNSAFE_componentWillMount方法在render方法执行前被调用。官方不建议使用此方法,所以为方法名加了UNSAFE前缀。官方建议把要在这里面写的内容放到constructor()或者componentDidMount()里面。

重点:PanResponder是系统封装的触摸响应系统。
关于手势PanResponder不在这里多说,可以参考以下给出的资料。
React Native 触摸事件处理详解
PanResponder官方文档
可以详细参阅以上两篇文章,研究透彻手势操作及手势机理。

手势这个东东搞懂了之后,然后再稍微看一下布局,这个组件基本上就都弄懂的差不多了。

componentDidMount()
  componentDidMount(): void {
    if (this.props.shouldBounceOnMount) {
      /**
       * Do the on mount bounce after a delay because if we animate when other
       * components are loading, the animation will be laggy
       */
      this.setTimeout(() => {
        this._animateBounceBack(ON_MOUNT_BOUNCE_DURATION);
      }, ON_MOUNT_BOUNCE_DELAY);
    }
  },

这个方法主要是第一次执行的时候会执行一次,其中设置了延迟执行关闭动画(将item显示与底部自定义view重合,即不显示删除按钮),因为我的demo是从iOS原生界面跳转到RN界面,当进来的时候,第一行的item会有一个动画(item自动往左部分距离再往右正常显示),如果将这里的代码删除,就没有了这个动画(后话:在后来看到SwipeableFlatList.js文件的时候才知道,这是系统封装的时候故意留下来的,为的就是要让用户知道这个列表是可以左右滑动的,属性shouldBounceOnMount默认为true,可以自行设置)。

UNSAFE_componentWillReceiveProps()
UNSAFE_componentWillReceiveProps(nextProps: Object): void {
    /**
     * We do not need an "animateOpen(noCallback)" because this animation is
     * handled internally by this component.
     */
    if (this.props.isOpen && !nextProps.isOpen) {
      this._animateToClosedPosition();
    }
  },

这个方法将会在属性改变的时候执行,其中nextProps会传递新的属性,这里的功能主要实现的就是当属性有改变的时候,在这里比较一下当前item中的属性isOpen和传递过来的isOpen,如果当前item处于打开状态并且最新传进来的属性isOpenfalse的时候,执行关闭当前item的操作。这样就可以做到在列表当中,最多只能让一个item显示出底部自定义的删除按钮(或者收藏按钮等等,这里就以删除按钮举例说明了)。

render()
render(): React.Element<any> {
    // The view hidden behind the main view
    let slideOutView;
    if (this.state.isSwipeableViewRendered && this.state.rowHeight) {
      slideOutView = (
        <View
          style={[styles.slideOutContainer, {height: this.state.rowHeight}]}>
          {this.props.slideoutView}
        </View>
      );
    }

    // The swipeable item
    const swipeableView = (
      <Animated.View
        onLayout={this._onSwipeableViewLayout}
        style={{transform: [{translateX: this.state.currentLeft}]}}>
        {this.props.children}
      </Animated.View>
    );

    return (
      <View {...this._panResponder.panHandlers}>
        {slideOutView}
        {swipeableView}
      </View>
    );
  },

render()中的代码就是关于item的布局了,我们可以看到,在render()中一共定义了两个变量,分别代表的是底层显示的自定义veiw和顶层显示的item视图。在return()中的View绑定了this._panResponder.panHandlers

  • 通过代码可以知道,变量slideOutView定义的是底部删除按钮的自定义view,并且这个view采用的是绝对定位的方式让其填充满外部可滑动的view(即充满<View {...this._panResponder.panHandles}></View>)。变量slideOutView中的View接收一个名为slideoutView的属性(即自定义视图);
  • 变量swipeableView定义的是顶部的item视图,其使用<Animated.View></Animated.View>来包裹父组件传递过来的子节点,使用Animated可以来制作滑动打开和关闭的动画。style中的transform[{translateX : this.state.currentLeft}]表示的是在X轴上的偏移量,0表示不偏移,负数表示向左偏移。

关于Animated的参考文章
官方文档

题外话:在之前我的RN项目中,如果是根据不同的变量值显示不同的view,这时候我往往都是在render()外定义一个方法,在render()return()中使用{this.slideOutView()}这种调用方法的形式改变View,通过上面的代码,我们以后就可以在return()之前先定义自定义视图变量,然后在return()中直接通过{变量名}的形式就可以显示不同的视图了,这样还便于查找,省的render()外边一大堆方法,不方便查找和修改(至于真真使用哪种都可以,根据自己喜好来,我看到这里就是惊讶于原来还可以这么搞)。

逻辑处理

从开始到现在为止,关于SwipeableRow.js的整体代码结构和布局已经清楚,接下来就详细的来介绍一下侧滑删除item的逻辑思路。这里的逻辑思路主要是跟手势相关,所以就从手势PanResponder的方法说起。

  • onMoveShouldSetPanResponderCapture
    这个属性接收一个回调函数,函数原型是 function(evt): bool,在触摸移动事件时询问容器组是否要劫持手势事件。
  _handleMoveShouldSetPanResponderCapture(
    event: Object,
   gestureState: Object,
 ): boolean {
   // Decides whether a swipe is responded to by this component or its child
   return gestureState.dy < 10 && this._isValidSwipe(gestureState);
 },

 // Ignore swipes due to user's finger moving slightly when tapping
 _isValidSwipe(gestureState: Object): boolean {
   if (
     this.props.preventSwipeRight &&
     this._previousLeft === CLOSED_LEFT_POSITION &&
     gestureState.dx > 0
   ) {
     return false;
   }

   return Math.abs(gestureState.dx) > HORIZONTAL_SWIPE_DISTANCE_THRESHOLD;
 },

以上代码主要是在确定当用户是在做出侧滑操作的时候才劫持手势操作申请成为响应者,否则则不视为侧滑操作。

  • onPanResponderGrant
    这个属性接收一个回调函数,函数原型是 function(evt): void,表示开始手势操作(即申请成为响应者成功)。
_handlePanResponderGrant(event: Object, gestureState: Object): void {},

在这个回调方法中并未作出任何动作

  • onPanResponderMove
    表示触摸手指移动的事件,这个回调可能非常频繁,所以这个回调函数的内容需要尽量简单。
 _handlePanResponderMove(event: Object, gestureState: Object): void {
   if (this._isSwipingExcessivelyRightFromClosedPosition(gestureState)) >{
     return;
   }

   this.props.onSwipeStart();

   if (this._isSwipingRightFromClosed(gestureState)) {
     this._swipeSlowSpeed(gestureState);
   } else {
     this._swipeFullSpeed(gestureState);
   }
 },

 _isSwipingExcessivelyRightFromClosedPosition(gestureState: Object): boolean {
   /**
    * We want to allow a BIT of right swipe, to allow users to know that
    * swiping is available, but swiping right does not do anything
    * functionally.
    */
   const gestureStateDx = IS_RTL ? -gestureState.dx : gestureState.dx;
   return (
     this._isSwipingRightFromClosed(gestureState) &&
     gestureStateDx > RIGHT_SWIPE_THRESHOLD
   );
 },

 _isSwipingRightFromClosed(gestureState: Object): boolean {
   const gestureStateDx = IS_RTL ? -gestureState.dx : gestureState.dx;
   return this._previousLeft === CLOSED_LEFT_POSITION && gestureStateDx > 0;
 },

 _swipeFullSpeed(gestureState: Object): void {
   this.state.currentLeft.setValue(this._previousLeft + gestureState.dx);
 },

 _swipeSlowSpeed(gestureState: Object): void {
   this.state.currentLeft.setValue(
     this._previousLeft + gestureState.dx / SLOW_SPEED_SWIPE_FACTOR,
   );
 },

首先判断如果是右滑的话,就不作为;然后再回调函数this.props.onSwipeStart()告诉属性onSwipeStart侧滑手势开始了;最后就是根据不同情况来设置不同的滑动速度。

  • onPanResponderRelease
    表示触摸完成(touchUp)的时候的回调,表示用户完成了本次的触摸交互,这里应该完成手势识别的处理,这以后,组件不再是事件响应者,组件取消激活。
 _handlePanResponderEnd(event: Object, gestureState: Object): 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();
 },
});

当手势操作完成的时候,就判断当前的手势应该是什么样纸的,经过判断该手势并让侧滑动画做出响应,该关闭的关闭,该打开的打卡,同时还伴有this.props.onClose()this.props.onOpen()的回调。

  • onPanResponderTerminationRequest
    在组件成为事件响应者期间,其他组件也可能会申请触摸事件处理。此时 RN 会通过回调询问你是否可以释放响应者角色让给其他组件。如果回调函数返回为 true,则表示同意释放响应者角色,同时会回调onPanResponderTerminate函数,通知组件事件响应处理被终止了。
 _onPanResponderTerminationRequest(
   event: Object,
   gestureState: Object,
 ): boolean {
   return false;
 },
  • onPanResponderTerminate
    这个回调也会发生在系统直接终止组件的事件处理,例如用户在触摸操作过程中,突然来电话的情况。这时候的操作应该跟正常结束手势操作一样(即调用了_handlePanResponderEnd ()函数)。
  • onShouldBlockNativeResponder
    返回一个布尔值,决定当前组件是否应该阻止原生组件成为JS响应者。默认返回true。目前暂时只支持android。
onShouldBlockNativeResponder: (event, gestureState) => false,

以上就是关于SwipeableRow.js中的所有内容。

2. SwipeableQuickActions.js解读

关于SwipeableQuickActions.js文件的代码量就少得多了,只有七十行,关于该文件中的功能,就要慢慢理解和发掘了。

关于前面的引入就不说了,出去引入剩下的就只有一个类SwipeableQuickActions的实现并导出为组件。接下来我们就来详细的看一下类SwipeableQuickActions的实现。

属性检查
  static propTypes = {
    style: ViewPropTypes.style,
  };

这里也是类型定义,接收一个属性style,其类型为ViewPropTypes.style

render()

在该类中,出去一个类型检查以外,就只有一个render方法了。

  render(): React.Node {
    // $FlowFixMe found when converting React.createClass to ES6
    const children = this.props.children;
    let buttons = [];

    // Multiple children
    if (children instanceof Array) {
      for (let i = 0; i < children.length; i++) {
        buttons.push(children[i]);

        // $FlowFixMe found when converting React.createClass to ES6
        if (i < this.props.children.length - 1) {
          // Not last button
          buttons.push(<View key={i} style={styles.divider} />);
        }
      }
    } else {
      // 1 child
      buttons = children;
    }

    return <View style={[styles.background, this.props.style]}>{buttons}</View>;
  }

首先,在render()中,定义了一个children来接收属性this.props.children,该属性是一个特殊的属性,并不需要在父组件中写出,这个children代表的是父组件的子节点,这个子节点可以有一个、两个等等。

回归正文,在接收到父组件的子节点后,这里进行了判断,其主要就是实现将父组件的子节点分别存储到buttons数组中。

return()中返回的是将子节点包裹在一个View中再返回,这个view的style可以自定义,默认的这里给了styles.background样式定义。

3. SwipeableFlatList.js解读

SwipeableFlatList.js顾名思义,就知道这是一个可以侧滑的FlatList列表。关于这里的封装代码有二百行左右。相对来说还是比较简单的。

引入
'use strict';

import type {Props as FlatListProps} from 'FlatList';
import type {renderItemType} from 'VirtualizedList';

const PropTypes = require('prop-types');
const React = require('React');
const SwipeableRow = require('SwipeableRow');
const FlatList = require('FlatList');

这里主要是类引入,其中包括了前面介绍过的SwipeableRow

属性类型定义
type SwipableListProps = {
  /**
   * To alert the user that swiping is possible, the first row can bounce
   * on component mount.
   */
  bounceFirstRowOnMount: boolean,
  // Maximum distance to open to after a swipe
  maxSwipeDistance: number | (Object => number),
  // Callback method to render the view that will be unveiled on swipe
  renderQuickActions: renderItemType,
};

type Props<ItemT> = SwipableListProps & FlatListProps<ItemT>;

type State = {
  openRowKey: ?string,
};

这里有很多写法都不是太明白,虽然写法不明白,但是其主要是干什么的还是凑合着能看懂的,这里主要定义了三个属性(属性的功能注释中也有说明),之后又定义了状态(State)openRowKey和属性Props,该属性包含了SwipableListPropsFlatListProps的属性合集。

接下来就进入主题,开始了SwipeableFlatList类的实现

属性类型检查及默认值设置
  props: Props<ItemT>;
  state: State;

  _flatListRef: ?FlatList<ItemT> = null;
  _shouldBounceFirstRowOnMount: boolean = false;

  static propTypes = {
    ...FlatList.propTypes,

    /**
     * To alert the user that swiping is possible, the first row can bounce
     * on component mount.
     */
    bounceFirstRowOnMount: PropTypes.bool.isRequired,

    // Maximum distance to open to after a swipe
    maxSwipeDistance: PropTypes.oneOfType([PropTypes.number, PropTypes.func])
      .isRequired,

    // Callback method to render the view that will be unveiled on swipe
    renderQuickActions: PropTypes.func.isRequired,
  };

  static defaultProps = {
    ...FlatList.defaultProps,
    bounceFirstRowOnMount: true,
    renderQuickActions: () => null,
  };

这里值得一说的是...FlatList.propTypes,这个写法的意思就是说在定义的propsTypes中不仅仅包含了bounceFirstRowOnMountmaxSwipeDistancerenderQuickActions三个属性,还同时继承了FlatList的所有属性类型。下面的默认值也同样是这个道理。

constructor
  constructor(props: Props<ItemT>, context: any): void {
    super(props, context);
    this.state = {
      openRowKey: null,
    };

    this._shouldBounceFirstRowOnMount = this.props.bounceFirstRowOnMount;
  }

constructor是类的构造方法,按照ES6的写法,我们都在这里面定义变量和状态(State)。在这个方法中主要定义了openRowKeythis._shouldBounceFirstRowOnMount并赋值。

render()
  render(): React.Node {
    return (
      <FlatList
        {...this.props}
        ref={ref => {
          this._flatListRef = ref;
        }}
        onScroll={this._onScroll}
        renderItem={this._renderItem}
        extraData={this.state}
      />
    );
  }

在这里会返回显示的View,在return()中仅返回了一个系统的FlatList,这个FlatList不仅继承了FlatList的所有属性,同时还特别定义和实现了onScrollrenderItemextraData这三个属性(这里的三个属性其实有重写的意味)。总之这里的{...this.props}所处位置很关键,可以去搜索了解一下。

React Native 之{...this.props}解惑
ReactNative中 ...this.other与...this.props的区别

onScroll={this._onScroll}
_onScroll = (e): void => {
    // Close any opens rows on ListView scroll
    if (this.state.openRowKey) {
      this.setState({
        openRowKey: null,
      });
    }

    this.props.onScroll && this.props.onScroll(e);
  };

这里的作用主要是在FlatList在滑动的时候,如果存在有打开的侧滑栏,就将其关闭。

renderItem={this._renderItem}
  _renderItem = (info: Object): ?React.Element<any> => {
    const slideoutView = this.props.renderQuickActions(info);
    const key = this.props.keyExtractor(info.item, info.index);

    // If renderQuickActions is unspecified or returns falsey, don't allow swipe
    if (!slideoutView) {
      return this.props.renderItem(info);
    }

    let shouldBounceOnMount = false;
    if (this._shouldBounceFirstRowOnMount) {
      this._shouldBounceFirstRowOnMount = false;
      shouldBounceOnMount = true;
    }

    return (
      <SwipeableRow
        slideoutView={slideoutView}
        isOpen={key === this.state.openRowKey}
        maxSwipeDistance={this._getMaxSwipeDistance(info)}
        onOpen={() => this._onOpen(key)}
        onClose={() => this._onClose(key)}
        shouldBounceOnMount={shouldBounceOnMount}
        onSwipeEnd={this._setListViewScrollable}
        onSwipeStart={this._setListViewNotScrollable}>
        {this.props.renderItem(info)}
      </SwipeableRow>
    );
  };

  // This enables rows having variable width slideoutView.
  _getMaxSwipeDistance(info: Object): number {
    if (typeof this.props.maxSwipeDistance === 'function') {
      return this.props.maxSwipeDistance(info);
    }

    return this.props.maxSwipeDistance;
  }

  _setListViewScrollableTo(value: boolean) {
    if (this._flatListRef) {
      this._flatListRef.setNativeProps({
        scrollEnabled: value,
      });
    }
  }

  _setListViewScrollable = () => {
    this._setListViewScrollableTo(true);
  };

  _setListViewNotScrollable = () => {
    this._setListViewScrollableTo(false);
  };

  _onOpen(key: any): void {
    this.setState({
      openRowKey: key,
    });
  }

  _onClose(key: any): void {
    this.setState({
      openRowKey: null,
    });
  }

这里就是侧滑功能在FlatList中的封装了,在renderItem()中首先获取到属性renderQuickActions并检测其是否为空或者未实现,如果没有实现的话,就不能侧滑。

    let shouldBounceOnMount = false;
    if (this._shouldBounceFirstRowOnMount) {
      this._shouldBounceFirstRowOnMount = false;
      shouldBounceOnMount = true;
    }

这里主要就是在刚进入的时候有一个侧滑的小动画,旨在告诉用户,这里的列表是可以侧滑的,该功能由属性bounceFirstRowOnMount控制(在构造器中已经赋值给this._shouldBounceFirstRowOnMount)。

    return (
      <SwipeableRow
        slideoutView={slideoutView}
        isOpen={key === this.state.openRowKey}
        maxSwipeDistance={this._getMaxSwipeDistance(info)}
        onOpen={() => this._onOpen(key)}
        onClose={() => this._onClose(key)}
        shouldBounceOnMount={shouldBounceOnMount}
        onSwipeEnd={this._setListViewScrollable}
        onSwipeStart={this._setListViewNotScrollable}>
        {this.props.renderItem(info)}
      </SwipeableRow>
    );

这里就调用了封装好的SwipeableRow组件,

slideoutView就是侧滑底部自定义view的实现
isOpen主要根据item数据对比来判断该item是否应该打开
maxSwipeDistance设置最大侧滑距离
onOpenonClose主要用于打开或关闭item侧滑(实质是改变状态[State]openRowKey的值)
shouldBounceOnMount第一次进入是否执行小动画告诉用户此处可以侧滑
onSwipeEnd侧滑结束的时候,允许Scroll滑动
onSwipeStart侧滑开始的时候,禁止Scroll滑动

这样就可以做到在当前列表中最多只能侧滑打开一个item,并且在滑动的时候自动关闭侧滑。

看到这里是不是才感觉侧滑删除也就那样,很好实现嘛,那就自己动手实践实践吧。

后话

关于系统为开发的侧滑删除我就只介绍到这里了,至于还有三个文件,暂时也没有时间去挨个的写出来了,其实跟上面介绍的三个文件方法类似,一遍看不懂,那就两遍,前后连贯着看,不仅要看,还要动脑子想。

多看、多想大神写的代码,从代码中去理解他们的封装思路等等,这是一个快速提高自己的很好的方法。

猜你喜欢

转载自blog.csdn.net/weixin_34102807/article/details/86908949
今日推荐