前言
做的项目中有需要用到排序,里面有一个内容是通过拖动来设定排序方案,所以找了一个项目来分析了一下,然后自己做了一个demo。项目参考:kevenfeng/DragSort: 小程序拖动排序 (github.com)
PS:参考的项目不写注释真的好难看懂,他的这个代码也有其他的功能,不是纯粹的拖动排序,看起来真的很费力
分析
下面是实例,因为用ps处理的图像,所以会有奇怪的抖动?: (
需求分析
我们来分析一下这个demo的需求,首先按住滑块的响应区域(注意,只能在那个区域中),滑块有一个“被拉上来的”效果,且滑块原来的位置没有内容,接下来我们可以在除了标题外的整个区域中拖动,来选择放置的位置,在我们“拉上来的滑块”覆盖滑块一半高度的时刻,发生交换事件,在我们松手之后,“拉上来的滑块”又“回到”空白区域
我们从逻辑层和视图层两个方面来分析这个demo
视图分析
首先,我们要先确定放置底部滑块的区域,因为这个区域需要能够滚动,我们选择使用scroll-view(可滚动视图区域)来放置所有没有被选中的滑块,接下来我们需要一个能够触发事件的区域(touchArea),即demo中的响应列,这个区域需要能够响应我们的点击,并且监听我们移动的位置。
下图蓝色区域为响应区域
其次,因为选中的滑块需要在视图中能够移动,我们需要使用movable-view(可移动的视图容器)搭配movable-area(限制movable-view的可移动区域)来使用滑块。
这里我们先注意一点,movable和scroll不能直接嵌套使用(同时出现),因为大家都具有滑动的功能,那么小程序又怎么判断你到底是在移动movable还是srcoll?(可能以后有办法,至少目前我不行)在理解了这一点之后,我们就要明白,我们不能直接把一个滑块从“底部”移动到“顶部”,即从scroll-view组件移动到movable的组件中,那么我们的解决办法就是在movable(可移动的视图容器)中去“复制”一个和选中的滑块一模一样的滑块,并通过移动这个滑块来完成排序。
既然movable和scroll不能同时出现,那么我们的解决办法就是控制movable的出现时间,即在我们选中滑块的时候,出现movable区域,然后控制滑块来进行排序,接着在取消选中的时候,再让movable区域“消失”
样式分析
首先,能够触发事件的区域touch-Area一直在那个地方,所以需要设定为绝对定位,因为区域高度不定,由js来控制高度,其次,可移动的视图区域movable-area也需要使用绝对定位,因为可移动的区域在视图中永远是标题往下,高度由js来控制。在选中的时候,让下面scroll中被选中的滑块设为透明。在我们为选中的滑块的时候,需要把movable-area的display改为none,在触发事件的时候改回来
逻辑分析
首先为触发事件的区域绑定事件,catchtouchstart,catchtouchmove,catchtouchend。
在点击开始catchtouchstart的时候,获取第一次接触的地点的坐标,这里注意一点,我们自己在设置时往往用微信的rpx像素单位,但是此时获取的是px像素单位。然后通过我们得到的像素单位,来获取我们选择的对象(即通过像素获得目标…)得到数据之后将我们“拉上来的滑块”的中点移动到我们接触的点(因为是绝对定位,即获取的点的坐标减去区域外标题的高度再减去一半的滑块高度),这是我们开始点击时需要做的。
接下来是按住移动catchtouchmove的时候,我们可以从事件传来的参数中得到我们目前的视图中坐标,我们需要重新计算坐标,让“拉上来的滑块”的中心一直在我们接触的点,然后又要通过像素计算位置,来进行数组的交换(这里其实是个难点,可以看代码)
最后是松开事件的时候catchtouchend的时候,我们只需要把之前设置的点击触发的属性全部改回去即可
代码
<view class="title">
<view class="title_row">
<text class="left">响应</text>
<text class="right">属性</text>
</view>
</view>
<view class="content">
<!-- 滑块的点击区域,用于绑定事件 -->
<view class="touchArea" style="height:{{scrollPosition.scrollViewHeight}}rpx"
catchtouchstart="draggleTouch"catchtouchmove="draggleTouch" catchtouchend="draggleTouch">
</view>
<!-- 滑块的可移动区域 -->
<movable-area class="movable_area {{movableViewPosition.className}}" style="height:{{scrollPosition.scrollViewHeight}}rpx">
<movable-view class="movable_view" style="height:{{movableViewPosition.everyOptionCell}}rpx"
direction="vertical" x="{{movableViewPosition.x}}" y="{{movableViewPosition.y}}" damping="1000">
<view class="content_row shadow">
<text class="content_row_left">按住</text>
<text class="content_row_right">{{movableViewPosition.data.name}}</text>
</view>
</movable-view>
</movable-area>
<scroll-view class="scroll_content" scroll-y="{{scrollPosition.scrollY}}"
style="height:{{scrollPosition.scrollViewHeight}}rpx" bindscroll="bindscroll">
<block wx:for="{{optionsListData}}" wx:key="id">
<view class="content_row {{item.isSelect?'black':''}}">
<text class="content_row_left">按住</text>
<text class="content_row_right">{{item.name}}</text>
</view>
</block>
</scroll-view>
</view>
.title {
height: 100rpx;
border: 1rpx solid #000;
display: flex;
width: 100%;
position: fixed;
z-index: 200;
background-color: #fff;
.title_row {
width: 100%;
height: 100rpx;
position: fixed;
display: flex;
align-items: center;
justify-content: space-between;
}
.left {
padding-left: 100rpx;
}
.right {
padding-right: 100rpx;
}
}
.content{
padding-top: 100rpx;
width: 100%;
.touchArea{
z-index: 200;
position: absolute;
top:100rpx;
left: 95rpx;
width: 80rpx;
}
.movable_area{
width: 100%;
z-index: 100;
position: absolute;
background-color: rgba(0,0,0,0);
.movable_view{
width: 100%;
z-index: 150;
}
.content_row{
background-color: #fff;
}
}
.content_row{
display: flex;
height: 150rpx;
width: 100%;
align-items: center;
justify-content:space-between;
border: 1rpx solid #ccc;
.content_row_left{
display: flex;
width: 80rpx;
margin-left: 95rpx;
font-size: 25rpx;
}
.content_row_right{
margin-right: 100rpx;
}
}
}
.none{
display: none;
}
.black{
background-color: rgba(0,0,0,0);
color: rgba(0,0,0,0);
}
.shadow{
box-shadow: 0 0 12px rgba(0, 0, 0, .2)
}
Page({
data: {
optionsListData: [
],
scrollPosition: {
everyOptionCell: 75,
top: 50,
scrollTop: 0,
scrollY: true,
scrollViewHeight: 1100,
scrollViewWidth: 375,
windowViewHeight: 1100,
},
movableViewPosition: {
y: 0,
className: "none",
data: {}
},
selectItemInfo: {
selectPosition: 0
}
},
onLoad: function () {
let optionsListData = [
{ name: '计算机211', id: 1, isSelect: false },
{ name: '计算机212', id: 2, isSelect: false },
{ name: '计算机213', id: 3, isSelect: false },
{ name: '计算机214', id: 4, isSelect: false },
{ name: '计算机215', id: 5, isSelect: false },
{ name: '计算机216', id: 6, isSelect: false },
{ name: '计算机217', id: 7, isSelect: false },
{ name: '计算机218', id: 8, isSelect: false },
{ name: '计算机219', id: 9, isSelect: false },
{ name: '计算机220', id: 10, isSelect: false }
]
this.setData({
optionsListData: optionsListData
})
},
bindscroll: function (event) {
let scrollTop = event.detail.scrollTop;
this.setData({
'scrollPosition.scrollTop': scrollTop
})
},
//监听事件,进行分别处理
draggleTouch: function (event) {
let touchType = event.type;
switch (touchType) {
case "touchstart":
this.scrollTouchStart(event);
break;
case "touchmove":
this.scrollTouchMove(event);
break;
case "touchend":
this.scrollTouchEnd(event);
break;
}
},
//获取下标
getOptionInfo: function (code) {
for (let i = 0, j = this.data.optionsListData.length; i < j; i++) {
let optionData = this.data.optionsListData[i];
if (optionData.id == code) {
optionData.selectIndex = i;
return i;
}
}
return {};
},
//通过y获取对象
getPositionDomByXY: function (potions) {
let y = potions.y - this.data.scrollPosition.top + this.data.scrollPosition.scrollTop;//减去滑块之外的距离,获得从第一个滑块到当前滑块的距离
let optionsListData = this.data.optionsListData;
let everyOptionCell = this.data.scrollPosition.everyOptionCell;
//遍历数组,得到我们的数据
for (let i = 0, j = optionsListData.length; i < j; i++) {
if (y >= i * everyOptionCell && y < (i + 1) * everyOptionCell) {
return optionsListData[i];
}
}
return optionsListData[0];
},
scrollTouchStart: function (event) {
let optionsListData = this.data.optionsListData
let firstTouchPosition = {
//获取开始地点
y: event.changedTouches[0].pageY,
}
let data = this.getPositionDomByXY(firstTouchPosition);//返回对象
//movable-area滑块位置处理
let movableY = firstTouchPosition.y - this.data.scrollPosition.top - this.data.scrollPosition.everyOptionCell / 2;//获取的位置的y减去滑块外的区域再减去一半的行滑块高度,使得滑块中间使我们选中的区域
this.setData({
movableViewPosition: {
y: movableY,
className: "",
data: data
}
})
data.selectPosition = event.changedTouches[0].clientY;
let index = this.getOptionInfo(data.id)
optionsListData[index].isSelect = true
this.setData({
'scrollPosition.scrollY': false,
selectItemInfo: data,
optionsListData
})
},
scrollTouchMove: function (event) {
let moveDistance = event.changedTouches[0].clientY;
let selectItemInfo = this.data.selectItemInfo
let selectPosition = selectItemInfo.selectPosition;
let movableY = event.changedTouches[0].pageY - this.data.scrollPosition.top - this.data.scrollPosition.everyOptionCell / 2;
let selectIndex = selectItemInfo.selectIndex;
let optionsListData = this.data.optionsListData
const everyOptionCell = this.data.scrollPosition.everyOptionCell
const data = this.data.movableViewPosition.data
this.setData({
movableViewPosition: {
y: movableY,
className: "",
data: data
}
})
if (moveDistance - selectPosition + everyOptionCell / 2 > 0 && selectIndex < optionsListData.length - 1) {
if (optionsListData[selectIndex] == selectItemInfo) {
optionsListData.splice(selectIndex, 1);
optionsListData.splice(++selectIndex, 0, selectItemInfo);
selectPosition += everyOptionCell;
}
}
if (moveDistance - selectPosition + everyOptionCell / 2 < 0 && selectIndex > 0) {
if (optionsListData[selectIndex] == selectItemInfo) {
optionsListData.splice(selectIndex, 1);
optionsListData.splice(--selectIndex, 0, selectItemInfo);
selectPosition -= everyOptionCell;
}
}
this.setData({
'selectItemInfo.selectPosition': selectPosition,
'selectItemInfo.selectIndex': selectIndex,
optionsListData: optionsListData,
});
},
scrollTouchEnd: function (event) {
console.log(event)
let data = this.data.movableViewPosition.data
let index = this.getOptionInfo(data.id)
let optionsListData = this.data.optionsListData
optionsListData[index].isSelect = false
this.setData({
movableViewPosition: {
y: 0,
className: "none",
data: {}
},
'scrollPosition.scrollY': true,
'movableViewPosition.className': "none",
optionsListData: this.data.optionsListData
})
console.log(optionsListData)
}
})
Q.E.D.