收藏
回答

小程序实现拖拽排序?

拖拽排序,但是列表的每个元素的高度不一样,拖拽就有问题,大佬,救救孩子

回答关注问题邀请回答
收藏

2 个回答

  • 陈宇明
    陈宇明
    2021-01-21

    自己写的?还是框架?

    2021-01-21
    有用 1
    回复 3
    • 张先绅
      张先绅
      2021-01-22
      网上复制别人的
      2021-01-22
      回复
    • 张先绅
      张先绅
      2021-01-22回复张先绅
      // components/dragorder/dragorder.js
      /**
       * 判断是否超出范围
       */
      const IsOutRange = (x1, y1, x2, y2, x3, y3) => {
       return x1 < 0 || x1 >= y1 || x2 < 0 || x2 >= y2 || x3 < 0 || x3 >= y3
      };
      /**
       * 版本号比较
       */
      const compareVersion = (v1, v2) => {
       v1 = v1.split('.')
       v2 = v2.split('.')
       const len = Math.max(v1.length, v2.length)
       while (v1.length < len) {
        v1.push('0')
       }
       while (v2.length < len) {
        v2.push('0')
       }
       for (let i = 0; i < len; i++) {
        const num1 = parseInt(v1[i])
        const num2 = parseInt(v2[i])
        if (num1 > num2) {
         return 1
        } else if (num1 < num2) {
         return -1
        }
       }
       return 0
      }
      Component({
       options: {
        multipleSlots: true
       },
       properties: {
        extraNodes: {type: Array, value: []}, // 额外节点
        listData: {type: Array, value: []}, // 数据源
        columns: {type: Number, value: 1}, // 列数
        topSize: {type: Number, value: 0}, // 顶部高度
        bottomSize: {type: Number, value: 0}, // 底部高度
        scrollTop: {type: Number, value: 0} // 页面滚动高度
       },
       data: {
        /* 未渲染数据 */
        pageMetaSupport: false, // 当前版本是否支持 page-meta 标签
        windowHeight: 0, // 视窗高度
        platform: '', // 平台信息
        realTopSize: 0, // 计算后顶部固定高度实际值
        realBottomSize: 0, // 计算后底部固定高度实际值
        rows: 0, // 行数
        itemDom: {width: 0, height: 0, left: 0, top: 0}, // 每一项 item 的 dom 信息, 由于大小一样所以只存储一个
        itemWrapDom: {width: 0, height: 0, left: 0, top: 0}, // 整个拖拽区域的 dom 信息
        startId: 0, // 初始触摸点 identifier
        preStartKey: -1, // 前一次排序时候的起始 sortKey 值
        /* 渲染数据 */
        list: [], // 渲染数据列
        cur: -1, // 当前激活的元素
        curZ: -1, // 当前激活的元素, 用于控制激活元素z轴显示
        tranX: 0, // 当前激活元素的 X轴 偏移量
        tranY: 0, // 当前激活元素的 Y轴 偏移量
        itemWrapHeight: 0, // 动态计算父级元素高度
        dragging: false, // 是否在拖拽中
          itemTransition: false, // item 变换是否需要过渡动画, 首次渲染不需要
          putShow:false,
          show:false
       },
       methods: {
          openClick(){
            this.setData({putShow: true});
          },
          putClick(){
            this.setData({putShow: false});
          },
          delTeammate(){
            this.setData({
              show: true
            });
          },
          updateTeammate(){
          },
          onClose() {
            this.setData({
              show: false
            });
          },
        /**
         * 长按触发移动排序
         */
        longPress(e) {
         // 获取触摸点信息
         let startTouch = e.changedTouches[0];
         if (!startTouch) return;
         // 固定项则返回
         let index = e.currentTarget.dataset.index;
         if (this.isFixed(index)) return;
         // 防止多指触发 drag 动作, 如果已经在 drag 中则返回, touchstart 事件中有效果
         if (this.data.dragging) return;
         this.setData({dragging: true});
         let {platform, itemDom, itemWrapDom} = this.data,
          {pageX: startPageX, pageY: startPageY, identifier: startId} = startTouch;
         // 计算X,Y轴初始位移, 使 item 中心移动到点击处
         let tranX = startPageX - itemDom.width / 2 - itemWrapDom.left,
          tranY = startPageY - itemDom.height / 2 - itemWrapDom.top;
         // 单列时候X轴初始不做位移
         if (this.data.columns === 1) tranX = 0;
         this.data.startId = startId;
         this.setData({cur: index, curZ: index, tranX, tranY});
         if (platform !== "devtools") wx.vibrateShort();
        },
        touchMove(e) {
         // 获取触摸点信息
         let currentTouch = e.changedTouches[0];
         if (!currentTouch) return;
         if (!this.data.dragging) return;
         let {pageMetaSupport, windowHeight, realTopSize, realBottomSize, itemDom, itemWrapDom, preStartKey, columns, rows} = this.data,
          {pageX: currentPageX, pageY: currentPageY, identifier: currentId, clientY: currentClientY} = currentTouch;
         // 如果不是同一个触发点则返回
         if (this.data.startId !== currentId) return;
         // 通过 当前坐标点, 初始坐标点, 初始偏移量 来计算当前偏移量
         let tranX = currentPageX - itemDom.width / 2 - itemWrapDom.left,
          tranY = currentPageY - itemDom.height / 2 - itemWrapDom.top;
         // 单列时候X轴初始不做位移
         if (columns === 1) tranX = 0;
         // 到顶到底自动滑动
         if (currentClientY > windowHeight - itemDom.height - realBottomSize) {
          // 当前触摸点pageY + item高度 - (屏幕高度 - 底部固定区域高度)
          if (pageMetaSupport) {
           this.triggerEvent("scroll", {
            scrollTop: currentPageY + itemDom.height - (windowHeight - realBottomSize)
           });
          } else {
           wx.pageScrollTo({
            scrollTop: currentPageY + itemDom.height - (windowHeight - realBottomSize),
            duration: 300
           });
          }
         } else if (currentClientY < itemDom.height + realTopSize) {
          // 当前触摸点pageY - item高度 - 顶部固定区域高度
          if (pageMetaSupport) {
           this.triggerEvent("scroll", {
            scrollTop: currentPageY - itemDom.height - realTopSize
           });
          } else {
           wx.pageScrollTo({
            scrollTop: currentPageY - itemDom.height - realTopSize,
            duration: 300
           });
          }
         }
         // 设置当前激活元素偏移量
         this.setData({tranX: tranX, tranY: tranY});
         // 获取 startKey 和 endKey
         let startKey = parseInt(e.currentTarget.dataset.key);
         let curX = Math.round(tranX / itemDom.width), curY = Math.round(tranY / itemDom.height);
         let endKey = curX + columns * curY;
         // 遇到固定项和超出范围则返回
         if (this.isFixed(endKey) || IsOutRange(curX, columns, curY, rows, endKey, this.data.list.length)) return;
         // 防止拖拽过程中发生乱序问题
         if (startKey === endKey || startKey === preStartKey) return;
         this.data.preStartKey = startKey;
         // 触发排序
         this.sort(startKey, endKey);
        },
        touchEnd() {
         if (!this.data.dragging) return;
         this.triggerCustomEvent(this.data.list, "sortend");
         this.clearData();
        },
        /**
         * 根据 startKey 和 endKey 去重新计算每一项 sortKey
         */
        sort(startKey, endKey) {
         this.setData({itemTransition: true});
         let list = this.data.list.map((item) => {
          if (item.fixed) return item;
          if (startKey < endKey) { // 正序拖动
           if (item.sortKey > startKey && item.sortKey <= endKey) {
            item.sortKey = this.excludeFix(item.sortKey - 1, startKey, 'reduce');
           } else if (item.sortKey === startKey) {
            item.sortKey = endKey;
           }
           return item;
          } else if (startKey > endKey) { // 倒序拖动
           if (item.sortKey >= endKey && item.sortKey < startKey) {
            item.sortKey = this.excludeFix(item.sortKey + 1, startKey, 'add');
           } else if (item.sortKey === startKey) {
            item.sortKey = endKey;
           }
           return item;
          }
         });
         this.updateList(list);
        },
        /**
         * 排除固定项得到最终 sortKey
         */
        excludeFix(sortKey, startKey, type) {
         if (sortKey === startKey) return startKey;
         if (this.data.list[sortKey].fixed) {
          let _sortKey = type === 'reduce' ? sortKey - 1 : sortKey + 1;
          return this.excludeFix(_sortKey, startKey, type);
         } else {
          return sortKey;
         }
        },
        /**
         * 根据排序后 list 数据进行位移计算
         */
        updateList(data, vibrate = true) {
         let {platform} = this.data;
         let list = data.map((item, index) => {
          item.tranX = `${(item.sortKey % this.data.columns) * 100}%`;
          item.tranY = `${Math.floor(item.sortKey / this.data.columns) * 100}%`;
          return item;
         });
         this.setData({list: list});
         if (!vibrate) return;
         if (platform !== "devtools") wx.vibrateShort();
         this.triggerCustomEvent(list, "change");
        },
        /**
         * 判断是否是固定的 item
         */
        isFixed(index) {
         let list = this.data.list;
         if (list && list[index] && list[index].fixed) return 1;
         return 0;
        },
        /**
         * 清除参数
         */
        clearData() {
         this.setData({
          preStartKey: -1,
          dragging: false,
          cur: -1,
          tranX: 0,
          tranY: 0
         });
         // 延迟清空
         setTimeout(() => {
          this.setData({
           curZ: -1,
          })
         }, 300)
        },
        /**
         * 点击每一项后触发事件
         */
        itemClick(e) {
         let {index, key} = e.currentTarget.dataset;
         let list = this.data.list;
         let currentItem = list[index];
         if (!currentItem.extraNode) {
          let _list = [];
          list.forEach((item) => {
           _list[item.sortKey] = item;
          });
          let currentKey = -1;
          for (let i = 0, len = _list.length; i < len; i++) {
           let item = _list[i];
           if (!item.extraNode) {
            currentKey++;
           }
           if (item.sortKey === currentItem.sortKey) {
            break;
           }
          }
          this.triggerEvent('click', {
           key: currentKey,
           data: currentItem.data
          });
         }
        },
        /**
         * 封装自定义事件
         * @param list 当前渲染的数据
         * @param type 事件类型
         */
        triggerCustomEvent(list, type) {
         let _list = [], listData = [];
         list.forEach((item) => {
          _list[item.sortKey] = item;
         });
         _list.forEach((item) => {
          if (!item.extraNode) {
           listData.push(item.data);
          }
         });
         this.triggerEvent(type, {listData: listData});
        },
        /**
         * 初始化获取 dom 信息
         */
        initDom() {
         let {windowWidth, windowHeight, platform, SDKVersion} = wx.getSystemInfoSync();
         this.data.pageMetaSupport = compareVersion(SDKVersion, '2.9.0') >= 0;
         let remScale = (windowWidth || 375) / 375,
          realTopSize = this.data.topSize * remScale / 2,
          realBottomSize = this.data.bottomSize * remScale / 2;
         this.data.windowHeight = windowHeight;
         this.data.platform = platform;
         this.data.realTopSize = realTopSize;
         this.data.realBottomSize = realBottomSize;
         this.createSelectorQuery().select(".item").boundingClientRect((res) => {
          let rows = Math.ceil(this.data.list.length / this.data.columns);
          this.data.rows = rows;
              this.data.itemDom = res;
              console.log(res)
              console.log(rows)
          this.setData({
           itemWrapHeight: rows * res.height,
          });
          this.createSelectorQuery().select(".item-wrap").boundingClientRect((res) => {
                console.log(res)
           this.data.itemWrapDom = res;
           this.data.itemWrapDom.top += this.data.scrollTop
          }).exec();
         }).exec();
        },
        /**
         * 初始化函数
         * {listData, columns, topSize, bottomSize} 参数改变需要重新调用初始化方法
         */
        init() {
         this.clearData();
         this.setData({itemTransition: false});
         let delItem = (item, extraNode) => ({
          id: item.dragId,
          slot: item.slot,
          fixed: item.fixed,
          extraNode: extraNode,
          tranX: "0%",
          tranY: "0%",
          data: item
         });
         let {listData, extraNodes} = this.data;
         let _list = [], _before=[], _after=[], destBefore = [], destAfter = [];
         extraNodes.forEach((item, index) => {
          if(item.type === "before") {
           _before.push(delItem(item, true));
          } else if(item.type === "after") {
           _after.push(delItem(item, true));
          } else if(item.type === "destBefore") {
           destBefore.push(delItem(item, true));
          } else if(item.type === "destAfter") {
           destAfter.push(delItem(item, true));
          }
         });
         // 遍历数据源增加扩展项, 以用作排序使用
         listData.forEach((item, index) => {
          destBefore.forEach((i) => {
           if (i.data.destKey === index) _list.push(i);
          });
          _list.push(delItem(item, false));
          destAfter.forEach((i) => {
           if (i.data.destKey === index) _list.push(i);
          });
         });
         let list = _before.concat(_list, _after).map((item, index) => {
              console.log(item)
          item.sortKey = index; // 初始化 sortKey 为当前项索引值
          item.tranX = `${(item.sortKey % this.data.columns) * 100}%`;
          item.tranY = `${Math.floor(item.sortKey / this.data.columns) * 100}%`;
          return item;
         });
         if (list.length === 0) {
          this.setData({itemWrapHeight: 0});
          return;
         }
         this.updateList(list, false);
         // 异步加载数据时候, 延迟执行 initDom 方法, 防止基础库 2.7.1 版本及以下无法正确获取 dom 信息
         setTimeout(() => this.initDom(), 0);
        }
       },
       ready() {
        this.init();
       }
      });
      2021-01-22
      回复
    • 张先绅
      张先绅
      2021-01-22回复张先绅
      <!--components/dragorder/dragorder.wxml-->
      <view class="item-wrap" style="height: {{ itemWrapHeight }}px;">
       <view
        class="item {{cur == index ? 'cur':''}} {{curZ == index ? 'zIndex':''}} {{itemTransition && index !== cur ? 'itemTransition':''}} {{item.fixed ? 'fixed' : ''}}"
        wx:for="{{list}}"
        wx:key="id"
        data-key="{{item.sortKey}}"
        data-index="{{index}}"
        style="transform: translate3d({{index === cur ? tranX + 'px' : item.tranX}}, {{index === cur ? tranY + 'px' : item.tranY}}, 0);width: {{100 / columns}}%"
        bindtap="itemClick"
        bind:longpress="longPress"
        catch:touchmove="{{dragging?'touchMove':''}}"
        catch:touchend="{{dragging?'touchEnd':''}}">
        <!-- start:请在该区域编写自己的渲染逻辑 -->
            <view class="cell" wx:if="{{list.length<=2}}">
              <view class="team-item">
                <view class="team-info">
                  <image class="team-image" mode="aspectFill" src="{{item.data.images? item.data.images : 'https://elitego-lable-dev-1300257084.cos.ap-chengdu.myqcloud.com/zmn/2020/12/18/691e60f2-5e3b-4bd3-9b48-fdae903d2b5d.png'}}" alt=""/>
                  <view class="team-desc">{{item.data.title}}</view>
                </view>
                <van-swipe-cell>
                  <template slot="left">
                    <van-button square type="primary" text="选择" />
                  </template>
                  <view class="team-option">
                    <van-icon name="wap-nav"/>
                  </view>
                  <template slot="right">
                    <van-button square type="danger" text="删除" />
                    <van-button square type="primary" text="收藏" />
                  </template>
                </van-swipe-cell>
              </view>
              <view wx:for="{{item.data.rankList}}" wx:for-index="index" wx:key="id" wx:for-item="item1">
                <rank-item wx:if="{{item.data.id==item1.id}}" bang-obj="{{item1}}" />
              </view>
            </view>
            <view class="cell" wx:if="{{(index==0||index==1)&&list.length>2}}">
              <view class="team-item">
                <view class="team-info">
                  <image class="team-image" mode="aspectFill" src="{{item.data.images? item.data.images : 'https://elitego-lable-dev-1300257084.cos.ap-chengdu.myqcloud.com/zmn/2020/12/18/691e60f2-5e3b-4bd3-9b48-fdae903d2b5d.png'}}" alt=""/>
                  <view class="team-desc" catchtap="delTeammate">{{item.data.title}}</view>
                </view>
                <van-swipe-cell>
                  <template slot="left">
                    <van-button square type="primary" text="选择" />
                  </template>
                  <view class="team-option">
                    <van-icon name="wap-nav" />
                  </view>
                  <template slot="right">
                    <van-button square type="danger" text="删除" />
                    <van-button square type="primary" text="收藏" />
                  </template>
                </van-swipe-cell>
              </view>
              <view style="width:100%" wx:for="{{item.data.rankList}}" wx:for-index="index1" wx:key="id" wx:for-item="item1">
                <!-- <rank-item wx:if="{{item.data.id==item1.id}}" bang-obj="{{item1}}" /> -->
                <image class="team-image" mode="aspectFill" src="https://elitego-lable-dev-1300257084.cos.ap-chengdu.myqcloud.com/zmn/2020/12/18/691e60f2-5e3b-4bd3-9b48-fdae903d2b5d.png" alt=""/>
                <!-- <image wx:if="{{index1==1}}" class="team-image" mode="aspectFill" src="https://elitego-lable-dev-1300257084.cos.ap-chengdu.myqcloud.com/zmn/2020/12/18/691e60f2-5e3b-4bd3-9b48-fdae903d2b5d.png" alt=""/> -->
              </view>
              <view class="put" wx:if="{{index==1&&!putShow}}" bindtap="openClick">
                展开全部
              </view>
            </view>
            <view class="cell" wx:if="{{(index!=0&&index!=1)&&list.length>2&&putShow}}">
              <view class="team-item">
                <view class="team-info">
                  <image class="team-image" mode="aspectFill" src="{{item.data.images? item.data.images : 'https://elitego-lable-dev-1300257084.cos.ap-chengdu.myqcloud.com/zmn/2020/12/18/691e60f2-5e3b-4bd3-9b48-fdae903d2b5d.png'}}" alt=""/>
                  <view class="team-desc">{{item.data.title}}</view>
                </view>
                <van-swipe-cell>
                  <template slot="left">
                    <van-button square type="primary" text="选择" />
                  </template>
                  <view class="team-option">
                    <van-icon name="wap-nav" />
                  </view>
                  <template slot="right">
                    <van-button square type="danger" text="删除" />
                    <van-button square type="primary" text="收藏" />
                  </template>
                </van-swipe-cell>
              </view>
              <view style="width:100%" wx:for="{{item.data.rankList}}" wx:for-index="index" wx:key="id" wx:for-item="item1">
                <rank-item wx:if="{{item.data.id==item1.id}}" bang-obj="{{item1}}" />
              </view>
              <view class="put" wx:if="{{index==list.length-1}}" bindtap="putClick">
                收起全部
              </view>
            </view>
        <!-- end:请在该区域编写自己的渲染逻辑 -->
       </view>
      </view>
      <van-popup show="{{ show }}" bind:close="onClose" custom-style="width:600rpx;height:380rpx;border-radius: 10px;" >
        <view class="pop">
          <view class="reason">提示</view>
          <view class="title">确认删除队友吗?</view>
          <view class="btn">
            <van-button custom-class="confirm" bind:click='onClose'>犹豫一下</van-button>
            <van-button custom-class="true" bind:click="updateTeammate">暂时删除</van-button>
          </view>
        </view>
      </van-popup>
      2021-01-22
      回复
  • 面条君
    面条君
    2021-01-26

    找轮子,不过一般都要根据自己的情况改的,基本思路就是使用movable-view来做,布局那块你要自己写函数生成布局的x和y,当拖动一个块到另一个块上触发touchend的时候对比一下x和y判断是在哪个块上,然后触发位置交换,直接交换两个块的x和y就行。大概思路就这样,细节的慢慢抠一般能写出来。

    2021-01-26
    有用
    回复
登录 后发表内容
问题标签