小程序—小程序列表项拖拽排序

  • Post author:
  • Post category:小程序


一、拖拽效果图展示

首先,上个gif图看看效果

吐血测试了一天,目前还未发现bug。ps(拖拽效果仅在前端实现,未和后端交互)

文章代码参考

小程序实现列表拖拽排序

,参考文章还是存在一些bug和不足,比如,样式代码没给对,还有实现的拖拽是拖拽起始位置数据的替换,不是我想要的拖拽效果。

二、解决思路

首先,拖拽重要的计算清楚位置关系,从什么地方开始拖,拖到什么地方结束,以及如何判断有效的拖拽排序。

在拖拽开始时、拖拽过程中、拖拽结束时获取拖拽数据项的索引和实时的位置相关数据。给拖拽节点绑定catchtouchstart、catchtouchmove、catchtouchend事件,利用data-xxx属性传递索引)

  <!-- 拖拽节点 -->
 <view data-index='{{index}}' catchtouchstart='dragStart' catchtouchmove='dragMove'
    catchtouchend='dragEnd'>
   <image class="img" src="/image/icon-sort.png"></image>
 </view>

结合下图进行分析,如图,需要拖拽的数据列表都放在class=habitlist的节点中,为方便计算位置,固定每个数据项的height=60px;假设数据列表habitList=[{ name: “跳舞”,icon: “./image/icon/icon-dance.png”},{name:”看书”,…},…],当前拖拽项(name为跳舞的项)的克隆为kelong={name: “跳舞”,icon: “./image/icon/icon-dance.png”}

1、【catchtouchstart】-拖拽开始时,获取当前拖拽项的索引,克隆当前项的内容给对象kelong。通过节点查询器,选择class=habitlist的节点,获取节点位置信息的查询请求,rect.top为该节点距离顶部的距离;rec.height为该节点区域的高度;e.changedTouches[0].clientY代表当前点击位置距离可视区域顶部的垂直距离,由于每个数据项的height=60px;预估:top=.changedTouches[0].clientY – rect.top – 30为当前克隆项显示的top,

注意:克隆项显示的位置要相对class=habitlist的节点定位(子绝父相)

。该值也为拖拽开始时的kelong项的位置startTop,(startTop值主要用于拖拽结束时判断是向下还是向上拖拽了)

  // 拖拽开始
  dragStart(e) {
    // console.log("拖拽开始", e);
    var i = e.currentTarget.dataset.index // 当前拖拽项的索引index
    // 把当前拖拽项的内容复制给kelong
    var kelong = this.data.habitList[i]
    var query = wx.createSelectorQuery()  // 创建节点查询器 quer
    //选择class=habitlist的节点,获取节点位置信息的查询请求
    query.select('.habitlist').boundingClientRect((rect) => {
    var top = e.changedTouches[0].clientY - rect.top - 30
     var startTop = top;
      this.setData({
        kelong: kelong,
        selectedIndex:i,
        showkelong: true,
        top:top,
        startTop:startTop
      })
    }).exec();
  },

2、【catchtouchmove】-拖拽移动时,要对拖拽的范围做控制,文中做了顶部边界控制:控制克隆项不会拖拽出class=habitlist节点的顶部边界。当top<0时,将top置为0,底部可不做限制。

  // 拖拽移动
  dragMove(e) {
    // console.log("拖拽移动", e);
    var query = wx.createSelectorQuery();
    var top =this.data.top
    query.select('.habitlist').boundingClientRect((rect) => {
     top = e.changedTouches[0].clientY - rect.top - 30
      if (top < 0) {
        // 顶部边界控制:控制克隆项不会拖拽出class=habitlist节点的顶部边界
       top = 0
      }
      this.setData({
        top:top
      })
    }).exec();
  },

3、【catchtouchend】-拖拽结束时,

(1)进行边界控制:不仅要进行顶部边界限制,还要做底部边界控制,即如果拖拽超出底部边界,则该拖拽数据项为最后一个位置的数据了。

(2)向上或向下拖拽的数据备份及处理:向上拖拽时,需要备份插入位置target开始的下方数据(除了拖拽数据项);向下拖拽时,需要备份插入位置target开始的上方数据;然后按拖拽后的正确索引位置遍历赋值。

// 拖拽结束
  dragEnd(e) {
    // console.log("拖拽结束", e);
    var i = e.currentTarget.dataset.index
    var query = wx.createSelectorQuery();
    var kelong = this.data.kelong
    var habitList = this.data.habitList
    query.select('.habitlist').boundingClientRect((rect) => {
      var top = e.changedTouches[0].clientY - rect.top - 30
      if (top > rect.height) {
        // 底部边界控制:控制克隆项拖拽结束时不会出class=habitlist节点的底部边界
        top = rect.height - 60
      } else if (top < 0) {
        // 顶部边界控制:控制克隆项拖拽结束时不会出class=habitlist节点的顶部边界
        top = 0
      }
      this.setData({
        top: top,
      })
      var target = parseInt(top / 60)
      var list = []  //用于备份数据
      if (this.data.startTop > top) {
        //  往上方位置拖拽
        for (var k = 0; k <= i - target; k++) {
          //  备份插入位置target开始的下方数据,除了拖拽数据项
          if (habitList[target + k].name != kelong.name) {
            list.push(habitList[target + k])
          }
        }
        if (list.lenghth != 0) {
          habitList[target] = kelong
          for (var m = target + 1, n = 0; n < list.length; m++, n++) {
            habitList[m] = list[n]
          }
        }
      } else {
        // 往下边位置拖拽
        for (var k = 1; k <= target - i; k++) {
          //  备份插入位置target开始的上方数据,除了拖拽数据项
          if (habitList[i + k].name != kelong.name) {
            list.push(habitList[i + k])
          }
        }
        if (list.length != 0) {
          habitList[target] = kelong
          for (var m = i, n = 0; n < list.length; m++, n++) {
            habitList[m] = list[n]
          }
        }
      }
      this.setData({
        habitList: habitList,
        selectedIndex:-1,
        showkelong: false
      })
    }).exec();
  },

三、完整代码

【sortHabit.wxml】

<view  class="page-wrapper">
  <top-title toptitle="排序习惯" backImgFlag="true"></top-title>
  <view class="habitlist">
    <!-- 克隆当前拖拽的项 -->
    <view class='habit kelong' hidden='{{!showkelong}}' style='top:{{top}}px'>
      <view class='index'>?</view>
      <view class="icon">
        <image class="iconImg" src="{{kelong.icon}}"></image>
      </view>
      <view class="info">
        <view class="title">{{kelong.name}}</view>
      </view>
      <image class="img"
        src="cloud://xbd-cloud-xxx/icon/icon-sort.png"></image>
    </view>
    <block wx:for="{{habitList}}" wx:key="name">
      <view class="habit {{selectedIndex==index?'gray':''}}" >
        <view class='index'>{{index+1}}</view>
        <view class="icon">
          <image class="iconImg" src="{{item.icon}}"></image>
        </view>
        <view class="info">
          <view class="title">{{item.name}}</view>
        </view>
        <!-- 拖拽节点 -->
        <view data-index='{{index}}' catchtouchstart='dragStart' catchtouchmove='dragMove'
          catchtouchend='dragEnd'>
          <image class="img"
            src="cloud://xbd-cloud-xxx/icon/icon-sort.png"></image>
        </view>
      </view>
    </block>
  </view>
</view>

【sortHabit.js】

Page({

  /**
   * 页面的初始数据
   */
  data: {
    habitList: [],
    // 当前拖拽项的克隆
    kelong: {
      name: '',
      icon: ''
    },
    startTop: 0, //拖拽开始时克隆项距离class=habitlist节点顶部边界的值
    top: 0,
    selectedIndex: -1, //被选择拖拽的项的index
    backupList: [], //用于备份数据
    showkelong: false, //是否显示克隆项
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
    var list = [{
      name: "跳舞",
      icon: "cloud://xbd-cloud-xxx/icon/icon-dance.png"
    }, {
      name: "看书",
      icon: "cloud://xbd-cloud-xxx/icon/icon-reading.png"
    }, {
      name: "运动",
      icon: "cloud://xbd-cloud-xxx/icon/icon-sports.png"
    }, {
      name: "护肤",
      icon: "cloud://xbd-cloud-xxx/icon/icon-facialmask.png"
    }]
    this.setData({
      habitList: list
    })
  },
  
  // 拖拽开始
  dragStart(e) {
    // console.log("拖拽开始", e);
    var i = e.currentTarget.dataset.index // 当前拖拽项的索引index
    // 把当前拖拽项的内容复制给kelong
    var kelong = this.data.habitList[i]
     console.log("拖拽开始i=",i,"kelong=",kelong);
    var query = wx.createSelectorQuery(); // 创建节点查询器 quer
    //选择class=habitlist的节点,获取节点位置信息的查询请求
    query.select('.habitlist').boundingClientRect((rect) => {
    var top = e.changedTouches[0].clientY - rect.top - 30
     var startTop = top;
      this.setData({
        kelong: kelong,
        selectedIndex:i,
        showkelong: true,
        top:top,
        startTop:startTop
      })
    }).exec();
  },
  // 拖拽移动
  dragMove(e) {
    // console.log("拖拽移动", e);
    var query = wx.createSelectorQuery();
    var top =this.data.top
    query.select('.habitlist').boundingClientRect((rect) => {
     top = e.changedTouches[0].clientY - rect.top - 30
      if (top < 0) {
        // 顶部边界控制:控制克隆项不会拖拽出class=habitlist节点的顶部边界
       top = 0
      }
      this.setData({
        top:top
      })
    }).exec();
  },
  // 拖拽结束
  dragEnd(e) {
    // console.log("拖拽结束", e);
    var i = e.currentTarget.dataset.index
    var query = wx.createSelectorQuery();
    var kelong = this.data.kelong
    var habitList = this.data.habitList
    query.select('.habitlist').boundingClientRect((rect) => {
      var top = e.changedTouches[0].clientY - rect.top - 30
      if (top > rect.height) {
        // 底部边界控制:控制克隆项拖拽结束时不会出class=habitlist节点的底部边界
        top = rect.height - 60
      } else if (top < 0) {
        // 顶部边界控制:控制克隆项拖拽结束时不会出class=habitlist节点的顶部边界
        top = 0
      }
      this.setData({
        top: top,
      })
      var target = parseInt(top / 60)
      var list = []  //用于备份数据
      if (this.data.startTop > top) {
        //  往上方位置拖拽
        for (var k = 0; k <= i - target; k++) {
          //  备份插入位置target开始的下方数据,除了拖拽数据项
          if (habitList[target + k].name != kelong.name) {
            list.push(habitList[target + k])
          }
        }
        console.log("往上拖拽 list=======", list);
        if (list.lenghth != 0) {
          habitList[target] = kelong
          for (var m = target + 1, n = 0; n < list.length; m++, n++) {
            habitList[m] = list[n]
          }
        }
      } else {
        // 往下边位置拖拽
        for (var k = 1; k <= target - i; k++) {
          //  备份插入位置target开始的上方数据,除了拖拽数据项
          if (habitList[i + k].name != kelong.name) {
            list.push(habitList[i + k])
          }
        }
        console.log("往下拖拽 list=======", list);
        if (list.length != 0) {
          habitList[target] = kelong
          for (var m = i, n = 0; n < list.length; m++, n++) {
            habitList[m] = list[n]
          }
        }
      }
      console.log(habitList);
  
      this.setData({
        habitList: habitList,
        selectedIndex:-1,
        showkelong: false
      })
    }).exec();
  },
 
})

【sortHabit.wxss】

.habit {
  margin: 0rpx auto;
  width: 100%;
  height: 60px;
  border-radius: 10rpx;
  background-color: #fff;
  display: flex;
  padding: 15rpx;
  box-sizing: border-box;
  position: relative;
  border-bottom: 1px solid #f1f1f1;
}
.habit .index {
  margin: 25rpx 20rpx 0px 30rpx;
  font-weight: bold;
  color: #c93a3a;
}
.habit .icon {
  margin-right: 25rpx;
}
.habit .icon .iconImg {
  margin-top: 10rpx;
  width: 70rpx;
  height: 70rpx;
}
.habit .info {
  display: flex;
  flex-direction: column;
  width: 60vw;
}
.habit .info .title {
  margin: 25rpx 0rpx;
  font-size: 30rpx;
}
.habit .img {
  position: absolute;
  right: 30rpx;
  margin-top: 25rpx;
  width: 40rpx;
  height: 40rpx;
}
.habitlist {
  position: relative;
}
.kelong {
  z-index: 999;
  position: absolute;
  box-shadow: 1px 1px 5px #ccc;
}
.gray {
  background-color: #efecec;
}

ps:也不知道自己讲清楚没,记录点滴心路历程~下篇讲讲日历打卡



版权声明:本文为maidu_xbd原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。