<!-- 页面拖拽列表 -->
<text class="flex-center">拖拽列表排序</text>
<!-- HACK: 这里使用catch,是为了避免拖拽列表的时候页面滑动 -->
<movable-area
class="movable-container"
style="height: {{ elementHeight * list.length }}px;"
catch:touchmove="onHackTouchMove"
>
<block wx:for="{{ list }}" wx:key="index">
<view
class="flex-center item {{ dragElement && dragElement.id === item.id ? 'item--tran' : '' }} {{ lastTarget === index ? 'item--target' : '' }}"
style='height: {{ elementHeight }}px;'
data-index="{{ index }}"
bind:longpress="onLongPress"
bind:touchmove="onTouchMove"
bind:touchend="onTouchEnd"
>{{ item.content }}</view>
<!-- <view
class="flex-center item"
style='height:80px;'
data-index="{{ index }}"
bind:longpress="onLongPress"
bind:touchmove="onTouchMove"
bind:touchend="onTouchEnd"
>{{ item.content }}</view> -->
</block>
<!-- HACK: animation设置为false,避免开始显示此组件的滑动效果 -->
<movable-view
hidden="{{ !dragElement }}"
class="flex-center item"
style='height: {{ elementHeight }}px;'
direction="vertical"
disabled
animation="{{ false }}"
y="{{ movableViewY }}"
>{{ dragElement.content }}</movable-view>
</movable-area>
/* 居中 */
.flex-center {
display: flex;
align-items: center;
justify-content: center;
}
.movable-container {
width: 100%;
border: 2rpx solid cadetblue;
}
.item {
width: 100%;
border: 1px solid salmon;
font-size: 100rpx;
background-color:
}
.item--tran {
background-color:
}
.item--target {
background-color: lightseagreen;
}
Page({
data: {
/** 列表 */
list: [{
id: 1,
content: 1
}, {
id: 2,
content: 2
}, {
id: 3,
content: 3
}, {
id: 4,
content: 4
}, {
id: 5,
content: 5
}, {
id: 6,
content: 6
}, {
id: 7,
content: 7
}, {
id: 8,
content: 8
}, {
id: 9,
content: 9
}, {
id: 10,
content: 10
}, {
id: 11,
content: 11
}, {
id: 12,
content: 12
}],
/** 单一项高度 */
elementHeight: 80,
/** 滑动项 */
dragElement: null,
/** movable-view组件y轴坐标,滑动时滑动项左上角距离文档顶部纵坐标,单位px */
movableViewY: null,
/** 滑动过程中经过的项 */
lastTarget: null,
//
/** 屏幕高度,单位px */
_windowHeight: null,
/** 开始触摸时的单一项左上角y坐标 */
_startOffsetY: null,
/** 开始触摸位置y坐标 */
_startPageY: null,
/** 开始触摸项索引 */
_startDragElementIndex: null,
/** 滑动偏移 */
_scrollThreshold: 0.5,
/** 距顶部/左边多远时,触发 _scrollToUpper 事件,单位px,即上滑至屏幕顶部 */
_upperThreshold: 100,
/** 距底部/右边多远时,触发 _scrollToLower 事件,单位px,即下滑至屏幕底部 */
_lowerThreshold: 100,
/** 上滑和下滑时间,单位毫秒 */
_scrollDuration: 1000,
//
},
/** 生命周期函数--监听页面展示 */
onShow() {
this._getWindowHeight();
},
/** 长按触发事件 */
/** */
onLongPress(event) {
console.log('[onLongPress]', event);
let dragElementIndex = event.currentTarget.dataset.index;
let dragElement = this.data.list[dragElementIndex];
this.setData({
/** 点击项左上角y坐标 */
_startOffsetY: event.target.offsetTop,
/** 点击位置y坐标 */
_startPageY: event.touches[0].pageY,
/** 点击项索引 */
_startDragElementIndex: dragElementIndex,
/** 点击项 */
dragElement,
/** movable-view组件左上角y坐标 */
movableViewY: event.target.offsetTop
});
},
/**
*
* @param {*} event
*/
/**
* 手指触摸后移动
* - 触底或触顶时下滑或者上滑
* - 移动movable-view
*/
onTouchMove(event) {
console.log('[onTouchMove]', event);
// 长按事件
if (this.data.dragElement) {
/** 触摸点位置在显示屏幕区域左上角Y坐标 */
let clientY = event.touches[0].clientY;
/** 触摸点位置距离文档左上角Y坐标 */
let pageY = event.touches[0].pageY;
/** 和最初点击位置比较移动距离 */
let targetMoveDistance = pageY - this.data._startPageY;
/** 移动后的movable-view组件位置 */
let movableViewY = this.data._startOffsetY + targetMoveDistance;
/** 经过项索引 */
let targetIndex = this._computeFutureIndex(targetMoveDistance, this.data._startDragElementIndex);
this._pageScroll(clientY, pageY);
this.setData({
movableViewY,
lastTarget: targetIndex
});
}
},
/** 滑动结束 */
onTouchEnd(event) {
console.log('[onTouchEnd]', event);
if (this.data.dragElement) {
let list = this._deepCopy(this.data.list);
/** 结束点位置y坐标 */
let pageY = event.changedTouches[0].pageY;
/** 和初始点击位置比较移动距离 */
let targetMoveDistance = pageY - this.data._startPageY;
/** 初始点击项索引 */
let dragElementIndex = this.data._startDragElementIndex;
/** 目标项索引 */
const futureIndex = this._computeFutureIndex(targetMoveDistance, dragElementIndex);
if (futureIndex !== false) {
list.splice(futureIndex, 0, list.splice(dragElementIndex, 1)[0]); // 移动位置
}
this.setData({
list,
dragElement: null,
lastTarget: null
});
}
},
/** 阻止滑动 */
onHackTouchMove() { },
/** 获取可使用窗口高度,单位px */
_getWindowHeight() {
try {
const { windowHeight } = wx.getSystemInfoSync();
this.setData({
_windowHeight: windowHeight
});
} catch (err) {
console.error('[_getWindowHeight]', err);
}
},
/** 页面滑动 */
_pageScroll(clientY, pageY) {
if (clientY + this.data._upperThreshold >= this.data._windowHeight) {
// 下滑接近屏幕底部
wx.pageScrollTo({
scrollTop: pageY + this.data.elementHeight,
duration: this.data._scrollDuration
});
} else if (clientY - this.data._lowerThreshold <= 0) {
// 上滑接近屏幕顶部
wx.pageScrollTo({
scrollTop: pageY - this.data.elementHeight,
duration: this.data._scrollDuration
})
}
},
/**
* 计算目标索引
* @param {number} targetMoveDistance 移动距离
* @param {number} dtagElementIndex 初始移动项索引
* 若轻轻拂动则返回false
*/
_computeFutureIndex(targetMoveDistance, dragElementIndex) {
let willInsertAfter = this._getSwapDirection(targetMoveDistance);
if (willInsertAfter !== false) {
/** 偏移索引 */
let offsetElementIndex = dragElementIndex + willInsertAfter;
/** 移动步数 */
let step = targetMoveDistance / this.data.elementHeight;
/** 步数补偿,当只有移动距离超过单项 _scrollThreshold 时才算有效 */
if (step <= -1) {
step += this.data._scrollThreshold;
} else if (step >= 1) {
step -= this.data._scrollThreshold;
}
/** 目标索引 */
let futureIndex = parseInt(step) + offsetElementIndex;
// 避免越界
if (futureIndex < 0) {
futureIndex = 0;
} else if (futureIndex > this.data.list.length - 1) {
futureIndex = this.data.list.length - 1;
}
return futureIndex;
} else {
return willInsertAfter;
}
},
/**
* 获取滑动方向
* @param {number} targetMoveDistance 移动距离
* @returns {number/boolean}
* - 1 下滑
* - -1 上滑
* - false 拂动,滑动距离小于一半单项高度
*/
_getSwapDirection(targetMoveDistance) {
if (Math.abs(targetMoveDistance) < this.data.elementHeight / 2) {
// 轻轻拂动,滑动距离小于1/2单项高度
return false;
} else if (targetMoveDistance >= this.data.elementHeight / 2) {
console.log('[_getSwapDirection] 👇👇👇');
return 1; // 下滑
} else if (targetMoveDistance <= this.data.elementHeight / -2) {
console.log('[_getSwapDirection] 👆👆👆');
return -1; // 上滑
}
},
/** 深拷贝 */
_deepCopy(obj) {
// 只拷贝对象
if (typeof obj !== 'object') return;
// 根据obj的类型判断是新建一个数组还是一个对象
var newObj = obj instanceof Array ? [] : {};
for (var key in obj) {
// 遍历obj,并且判断是obj的属性才拷贝
if (obj.hasOwnProperty(key)) {
// 判断属性值的类型,如果是对象递归调用深拷贝
newObj[key] = typeof obj[key] === 'object' ? this._deepCopy(obj[key]) : obj[key];
}
}
return newObj;
}
})