一、描述

之前写了几篇文章虽然是使用 bindingX 去实现,但其实都是直接使用了 timing 或者是 scroll 单独的去实现的,并没有介入手势相关的应用。

几篇文章如下:

没有使用手势的主要原因其实是手势的动画效果很不好,而且也没什么特别的地方能够用到手势,一般来说手势都是的起始,没有特别多的需求是完全按照手势来做的,目前我看官网的示例对于手势的使用也就下面这个相对而言实用一些:

虽然这篇文章实现的类似 IOS 左滑删除的效果也有手势,但并不是 bindingx 的 pan eventType,同样的手势也是起始,后面的 binding 则是使用 timing 去实现的。

timing 对于两种不同位置的状态变动其实更加的友好,动画效果也多,不像手势那么死板。

具体要实现的动画效果如下:

下面 gif 是在 web 录制的,文章最后有在 IOS 上的效果

GIF.gif

二、布局实现

完整代码文章后面会给一个 jsplayground 用来

首先实现一个布局,从上面的示意图来看,整个单项菜单有三部分组成:

  • 最外层的统一容器:这个容器宽度 750,overfllow=hidden
  • 列表单项:这个容器宽度 750,是正常显示的菜单或者列表的单个项目,不过 position:absolute
  • 删除按钮:红色部分,position:absolute;right: -100 使其默认在容器外面

JSX 代码:

    <View style={styles.app}>
      {
        this.state.deleted ? null :
        <GestureView  
          style={styles.wrapper} 
          ref='row'
          onHorizontalPan={this.onHorizontalPan}
        >
          <View ref="box1" style={styles.box} />
          <View style={styles.delete} ref="delete" onClick={this.deleteHandle}>
            <Text style={styles.deleteText}>X</Text>
          </View>
        </GestureView >
      }
      </View>

css 代码:

.app {
  flex: 1;
  justify-content: center;
  align-items: center;
}

.wrapper {
  width: 750;
  height: 100;
  overflow: hidden;
}

.box {
  width: 750;
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: #cccccc;
}

.delete {
  width: 100;
  height: 100;
  background-color: red;
  justify-content: center;
  align-items: center;
  position: absolute;
  right: -100;
}

.deleteText {
  font-size: 50;
  color: #ffffff;
  font-weight: bold;
}

三、事件处理及 binding

1、state

为了模拟删除效果,我设置了一个 state.deleted,默认为 false,当删除触发之后,把单个子菜单销毁掉。

2、手势

前面提到了,手势的处理依旧没有使用 binding,而是使用了 rax-gesture-view,关于 rax-gesture-view 的使用我之前也写过一篇文章:

手势的作用是判断左滑还是右滑,然后进行 binding 的绑定。

基本的判断如下,每次都是在 e.state === 'end' 的时候进行判断,因为 rax-gesture-view 的 onHorizontalPan 的参数 event.state 有三个状态,分别是:

  • start
  • move
  • end

其中, animation 的作用是标识正在进行动画,不能再次进行 binding

   onHorizontalPan = (e) => {
    if (e.state === 'start') {
      startX = e.changedTouches[0].pageX;
    } else if (e.state === 'end') {
      endX = e.changedTouches[0].pageX;
      if (startX - endX < 0 && animation === false) {
        animation = true;
        this.hide();
      } else if (startX - endX >= 0 && animation === false) {
        animation = true;
        this.show();
      }
    }
  }

3、binding

show() 方法判断手势左滑的时候使用的,主要内容如下:

如果熟悉 binding 的使用(不熟悉可以看看我上面的几篇文章),就能够轻松看懂下面的内容

show = () => {
  BindingX.bind({
    eventType: 'timing',
    props: [
      {
        element: getEl(this.refs.box1),
        property: 'transform.translateX',
        expression: {
          origin: `linear(t, 0, -100, 100)`
        }
      },
      {
        element: getEl(this.refs.delete),
        property: 'transform.translateX',
        expression: `linear(t, 0, -100, 100)`
      }
    ]
  }, () => {
    animation = false;
  });
}

hide() 方法判断手势右滑的时候进行binding,主要内容如下:

此时操作是隐藏删除按钮

hide = () => {
  BindingX.bind({
    eventType: 'timing',
    props: [
      {
        element: getEl(this.refs.box1),
        property: 'transform.translateX',
        expression: {
          origin: 'linear(t, -100, 100, 100)'
        }
      },
      {
        element: getEl(this.refs.delete),
        property: 'transform.translateX',
        expression: 'linear(t, -100, 100, 100)'
      }
    ]
  }, () => {
    animation = false;
    now = 0;
  });
}

四、代码

在 jsplayground 上放了一份完整代码。

由于我也不知道他的这个 jsplayground 怎么安装依赖,这个代码在 playground 上跑不起来,无法加载 guesture 的依赖。

可以 copy 到本地查看效果

五、手机预览

录了一个 iphone 上的效果

www.alltoall.net_6702391f768acbd1cfcedbf7fca588bd_5nMzUANP4a.gif