RN仿微信朋友圈图片拖拽删除

前言

之前负责的一个需求,让在RN端做仿微信朋友圈的图片删除和排序,由于经验和时间限制,就跟PM协商改为点击删除,由此欠下一个技术栈,今天是来还债的。本文基于transform实现,而非定位。

源码

初始时

将数据源转化为如下格式

const dataArray = props.dataArray.map((item, index)=>{
  const newData = {}
  const translateX = (index % COLUMN_NUM) * this.itemW;
  const translateY = parseInt((index/COLUMN_NUM)) * this.itemW;

  newData.data = item; // 原始内容
  newData.originIndex = index; // 初始下标
  newData.originX = translateX; // 当前项基于父试图的相对x值
  newData.originY = translateY; // 当前项基于父试图的相对y值
  // 当前项的偏移量,初始时都是按照正常的布局来,都不需要偏移,故都为0
  newData.transAnimated = new Animated.ValueXY({ 
    x: 0,
    y: 0,
  });
  return newData
});
this.setState({ dataArray });

渲染时

新数据源属性做如下分配

{this.state.dataArray.map((item, index) => (
  <Draggable 
    ref={ref => this.refDrag.set(index, ref)} // 收集每项的ref引用
    pan={this.state.dataArray[index].transAnimated} // 创建的动画给到每一项
    key={`${item.data}`} 
    destination={{ // 删除区域
      x: 0,
      y: ScreenH-40,
    }} 
    onMoveStart={() => this.handleMoveStart(item.originIndex)} // 此处不要使用map的index,因为排序后数组的数组没有变
    onMoveEnd={this.handleMoveEnd} 
    onMove={this.handleMove}
    onArriveDestination={() => this.handleArriveDestination(item.data)} 
  >
    {this.renderItem(item.data)}
  </Draggable>
))}

开始拖拽

将如下属性赋值给this.touchCurItem

handleMoveStart = (move) => {
  this.touchCurItem = {
  ref: this.refDrag.get(move), // 获取当前拖拽的item引用
  index: move, // 当前拖拽item的下标
  moveToIndex: 0, // 将要拖拽到的位置,初始为0即可
}

拖拽中

不断计算所有item(除拖拽项)将要到达的位置,然后用动画移动

handleMove = (e, gestureState) => {
  if (!this.touchCurItem) return;
  // 偏移量
  let translateX = gestureState.dx;
  let translateY = gestureState.dy;

  // 让拖拽项跟着手指移动
  this.state.dataArray[this.touchCurItem.index].transAnimated.setOffset({
    x: translateX,
    y: translateY
  });
    
  let moveXNum = translateX/this.itemW
  let moveYNum = translateY/this.itemH;

  // 向四个方向移动一半位置就触发排序
  if (moveXNum > 0) {
    moveXNum = parseInt(moveXNum+0.5)
  } else if (moveXNum < 0) {
    moveXNum = parseInt(moveXNum-0.5)
  }
  if (moveYNum > 0) {
    moveYNum = parseInt(moveYNum+0.5)
  } else if (moveYNum < 0) {
    moveYNum = parseInt(moveYNum-0.5)
  }

  let moveToIndex = this.touchCurItem.index + moveXNum + moveYNum * COLUMN_NUM;

  // 边缘线制
  if (moveToIndex > this.state.dataArray.length-1) {
    moveToIndex = this.state.dataArray.length-1
  } else if (moveToIndex < 0) {
    moveToIndex = 0;
  }

  if (this.touchCurItem.moveToIndex == moveToIndex ) return;

  this.touchCurItem.moveToIndex = moveToIndex
  this.state.dataArray.forEach((item,index)=> {
    let nextItem = null
    if (index > this.touchCurItem.index && index <= moveToIndex) { // 向右拖拽
      nextItem = this.state.dataArray[index-1]
    } else if (index >= moveToIndex && index < this.touchCurItem.index) { // 向左拖拽
      nextItem = this.state.dataArray[index+1]
    } else if (index != this.touchCurItem.index &&
          (item.transAnimated.x._value != item.originX || item.transAnimated.y._value != item.originY)) { // 处理不松开时左右滑动
      nextItem = this.state.dataArray[index]
    } 
    if (!nextItem) return;
    Animated.timing(
      item.transAnimated,
      {
        // 还记得在初始时的构造的数据源么,curItem和nextItem偏移量的差值就是当前想要到达的位置
        toValue: {x: parseInt(nextItem.originX+0.5 - item.originX),y: parseInt(nextItem.originY+0.5 - item.originY)},
        duration: 300,
        easing: Easing.out(Easing.quad),
        useNativeDriver: false,
      }
    ).start()
  })
}

拖拽结束

handleMoveEnd = () => {
  const curIndex = this.touchCurItem.index;
  const moveToIndex = this.touchCurItem.moveToIndex;
  const curItem = this.state.dataArray[this.touchCurItem.index];
  const moveToItem = this.state.dataArray[moveToIndex];

  let translateX = 0;
  let translateY = 0;
  
  // 判断移动是否达到移动标准,若是:动画方式返回原位置,否:动画偏移到新位置
  if (curIndex != moveToIndex) {
    translateX = parseInt(moveToItem.originX - curItem.originX+0.5);
    translateY = parseInt(moveToItem.originY - curItem.originY+0.5); 
  }
  Animated.timing(
    curItem.transAnimated,
    {
       toValue: {x: translateX,y: translateY},
       duration: 300,
       easing: Easing.out(Easing.quad),
       useNativeDriver: false,
    }
  ).start()
  this.touchCurItem = null;
  this.changePosition(curIndex, moveToIndex);
}

// 每次排序结束,按照新的排序整理dataArray
changePosition(startIndex,endIndex) {
  let isCommon = true; // 向右边移
  if (startIndex > endIndex) {  // 向左移
    isCommon = false 
    let tempIndex = startIndex
    startIndex = endIndex
    endIndex = tempIndex
  }

  const newDataArray = [...this.state.dataArray].map((item,index)=>{
    let newIndex = null;
    if (isCommon) {
      if (endIndex > index && index >= startIndex) {
        newIndex = index+1
      } else if (endIndex == index) {
        newIndex = startIndex
      }
    } else {
      if (endIndex >= index && index > startIndex) {
        newIndex = index-1
      } else if (startIndex == index) {
        newIndex = endIndex
      }
    }

    if (newIndex != null) {
      const newItem = {...this.state.dataArray[newIndex]}
      newItem.originX = item.originX
      newItem.originY = item.originY
      newItem.originIndex = item.originIndex;
      newItem.transAnimated = new Animated.ValueXY({ // 每次排序过后都将偏移量给清空,准备下次排序
        x: 0,
        y: 0,
      })
      item = newItem
    }
    return item
  })
    
  this.setState({
    dataArray: newDataArray
  });
}

拖拽删除

这个比较简单了,通过通知的方式来传送消息,没啥可说的,有兴趣直接看源码吧

参考链接

  1. react-native-drag-sort
  2. rndd
  3. RN动画详解
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值