列表拖动排序在很多应用中都存在, 在 Qt Quick 中如何实现呢。网上查了下没有太多相关资料,于是决定自己写一个。

原理

MouseArea 中可以可以设置 DragEvent 来对某个 Item 进行拖动。Drag 这个类一般附着在可能被拖动的 Item 上,用来设置相关的信息。它提供了很多附加属性。如

  • Drag.active: bool 这个属性用来指示控件是否正在被拖动。可以用来绑定 MouseArea 的 MouseArea::drag 属性,当用户拖动鼠标的时候就会产生 drag 事件。
  • Drag.target : Object 当 active 为 true (拖动处于活跃状态)时,这个属性保存最后一个被拖动的 dragged item。

DropArea 定义了一个可以接收拖放的区域。它的 entered 信号在有物体被拖入区域时发射,exited 信号在物体被拖出区域时发射,当物体在区域内被拖着来回移动时会不断发射 positionChanged 信号,当用户释放了物体,dropped 信号被发射。

在这个例子中,我们需要在整个 ListView 中填充一个 DropArea 用来接收释放的 Item。Item 中绑定 MouseArea 的 drag 事件。释放的时候计算相应的位置并把拖动位置的 Item 移到释放位置。需要注意的是列表滚动之后要加上滚动的距离。

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
Item {
id:root
anchors.fill: parent
ListView {
id: listView
width: parent.width / 2
height: parent.height
property int dragItemIndex: -1
ScrollBar.vertical: ScrollBar{id:scrollBar}

DropArea {
id: dropArea
anchors.fill: parent
onDropped: {
// 获取释放时listView的targetIndex, 需要加上滚动的高度
var targetIndex = listView.indexAt(drag.x, drag.y + listView.contentY)
listView.model.move(listView.dragItemIndex, targetIndex, 1)
listView.dragItemIndex = -1;
}
}

model: ListModel {
Component.onCompleted: {
for (var i = 0; i < 30; ++i) {
append({value: i});
}
}
}

delegate: Item {
id: delegateItem
width: listView.width
height: 30

Rectangle {
anchors.fill: parent
Label {
text: index+1
anchors.verticalCenter: parent.verticalCenter
}

Rectangle {
id: dragRect
width: listView.width-50
height: 30
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
color: "salmon"
border.color: Qt.darker(color)

Text {
anchors.centerIn: parent
text: modelData
}

MouseArea {
id: mouseArea
anchors.fill: parent
drag.target: dragRect
drag.axis: Drag.YAxis // 只允许沿Y轴拖动

drag.onActiveChanged: {
if (mouseArea.drag.active) {
listView.dragItemIndex = index;
}
dragRect.Drag.drop();
}
}

states: [
State {
when: dragRect.Drag.active
ParentChange {
target: dragRect
parent: root
}

AnchorChanges {
target: dragRect
anchors.horizontalCenter: undefined
anchors.verticalCenter: undefined
}
}
]

Drag.active: mouseArea.drag.active
Drag.hotSpot.x: dragRect.width / 2
Drag.hotSpot.y: dragRect.height / 2
}

}
}
}

}