QML 拖拽篇:自由移动与单轴约束

发布时间:2026/9/2 21:39:16
QML 拖拽篇:自由移动与单轴约束 目录基础自由拖拽演示代码关键逻辑解析单轴约束拖拽演示代码关键逻辑解析运行验证小结系列文章我们常常会有这样的需求把窗口拖到屏幕边缘、拖动滑块的圆钮、把任务挪到另一个列表。QML 里实现拖拽的基础就是MouseArea上那几个drag.*属性——指定拖谁、往哪个方向拖、最多拖多远。下面从最简单的自由拖拽开始再讲怎么把拖拽限制在单一方向上。基础自由拖拽一个蓝色方块放在网格画布中间按住拖动可以满画布跑但永远出不了画布边界。松开时底部显示方块当前坐标旁边有个重置位置按钮把它拉回中心。演示代码import QtQuick import QtQuick.Controls import QtQuick.Layouts FadeInAnimation { ColumnLayout { anchors.fill: parent anchors.margins: 20 spacing: 15 // ... 省略标题组件 TitleSeparator ... // 拖拽画布 Rectangle { id: canvas Layout.alignment: Qt.AlignHCenter width: 540 height: 300 radius: 10 color: #FAFAFA border.color: #ddd border.width: 1 clip: true // 网格背景便于观察位置 Canvas { anchors.fill: parent onPaint: { var ctx getContext(2d) ctx.strokeStyle #EEEEEE ctx.lineWidth 1 for (var x 0; x width; x 30) { ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, height); ctx.stroke() } for (var y 0; y height; y 30) { ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(width, y); ctx.stroke() } } } // 可拖拽方块 Rectangle { id: dragBox width: 90 height: 90 radius: 12 color: dragArea.pressed ? #0E7BD9 : #1296FF x: canvas.width / 2 - 45 y: canvas.height / 2 - 45 Behavior on color { ColorAnimation { duration: 120 } } Text { anchors.centerIn: parent text: 拖我 color: white font.bold: true font.pointSize: 14 } Drag.active: dragArea.drag.active Drag.dragType: Drag.None MouseArea { id: dragArea anchors.fill: parent drag.target: dragBox drag.axis: Drag.XAndYAxis drag.minimumX: 0 drag.maximumX: canvas.width - dragBox.width drag.minimumY: 0 drag.maximumY: canvas.height - dragBox.height cursorShape: Qt.SizeAllCursor onPositionChanged: statusText.text 拖拽中… onReleased: { statusText.text 已释放 位置: ( Math.round(dragBox.x) , Math.round(dragBox.y) ) } } } // 状态提示 Text { id: statusText anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter anchors.bottomMargin: 10 text: 拖拽方块自由移动 font.pointSize: 12 color: #999 } } RowLayout { Layout.alignment: Qt.AlignHCenter spacing: 20 Text { text: 当前坐标: ( Math.round(dragBox.x) , Math.round(dragBox.y) ) font.pointSize: 13 color: #1296FF font.bold: true } Button { text: 重置位置 onClicked: { dragBox.x canvas.width / 2 - 45 dragBox.y canvas.height / 2 - 45 statusText.text 拖拽方块自由移动 } } } Text { Layout.alignment: Qt.AlignHCenter Layout.preferredWidth: 540 text: drag.axis: Drag.XAndYAxis → 仅可在画布范围内移动minimumX/Y ~ maximumX/Y font.pointSize: 10 color: #999 horizontalAlignment: Text.AlignHCenter wrapMode: Text.Wrap } Item { Layout.fillHeight: true } } }关键逻辑解析四件套target、axis、minimum、maximum实现拖拽靠 MouseArea 上的一组drag属性drag.target指定被拖动的对象这里是方块 dragBox。它填满了 MouseArea所以鼠标按住方块就能拖drag.axis决定拖拽方向Drag.XAndYAxis表示水平和垂直都能动drag.minimumX/drag.maximumX限定水平范围minimumY/maximumY限定垂直范围。上界写canvas.width - dragBox.width是因为方块有 90 宽坐标超过这个值右侧就出界了这套约束配齐后方块天然出不了画布不需要自己写撞墙判断。按下变深色给个反馈color: dragArea.pressed ? #0E7BD9 : #1296FF让方块在按住时变成深蓝色配一个Behavior on color做 120 毫秒的颜色过渡。加上cursorShape: Qt.SizeAllCursor鼠标悬停变成四向箭头用户一眼就知道这个能拖。监听状态汇报坐标onPositionChanged在拖拽过程中触发把状态文字改成拖拽中…onReleased在松开时把方块当前坐标写到底部。坐标用Math.round(dragBox.x)取整读起来干净。这里用的是纯物理移动不牵扯 Drag 会话所以Drag.dragType: Drag.None关掉系统拖拽避免产生多余的拖拽预览。单轴约束拖拽一个横的、一个竖的两条轨道各放一个圆形滑块。横的只能左右滑竖的只能上下滑滑块永远沿着轨道中线走旁边实时显示滑到哪了。这正是滑杆类控件最常见的实现思路。演示代码import QtQuick import QtQuick.Layouts FadeInAnimation { ColumnLayout { anchors.fill: parent anchors.margins: 20 spacing: 18 // ... 省略标题组件 TitleSeparator ... // 水平拖拽轨道 Rectangle { id: hTrack Layout.alignment: Qt.AlignHCenter width: 480 height: 70 radius: 8 color: #FAFAFA border.color: #ddd border.width: 1 // 轨道中线 Rectangle { anchors.verticalCenter: parent.verticalCenter x: 12 width: parent.width - 24 height: 2 color: #E0E0E0 } Text { x: 12 y: 8 text: XAxis仅水平拖拽 font.pointSize: 10 color: #999 } Rectangle { id: hKnob width: 50 height: 50 radius: 25 color: hMouse.pressed ? #0E7BD9 : #1296FF y: (hTrack.height - height) / 2 x: 12 Behavior on color { ColorAnimation { duration: 120 } } Text { anchors.centerIn: parent text: ↔ color: white font.pointSize: 20 font.bold: true } MouseArea { id: hMouse anchors.fill: parent drag.target: hKnob drag.axis: Drag.XAxis drag.minimumX: 12 drag.maximumX: hTrack.width - hKnob.width - 12 cursorShape: Qt.SizeHorCursor } } Text { anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter anchors.bottomMargin: 4 text: x Math.round(hKnob.x) font.pointSize: 11 color: #1296FF } } // ... 省略垂直轨道 vTrack与水平轨道对称仅 axis 改为 Drag.YAxis、cursorShape 改为 Qt.SizeVerCursor... Item { Layout.fillHeight: true } } }关键逻辑解析drag.axis 决定自由度水平滑块写drag.axis: Drag.XAxis于是 x 可以变、y 被锁死——滑块被约束在中线上不会乱跑。垂直滑块反过来写Drag.YAxis。改一个属性值拖拽的自由度就从二维变成一维。轴方向与光标匹配水平拖拽配cursorShape: Qt.SizeHorCursor左右箭头垂直配Qt.SizeVerCursor上下箭头滑块上的 “↔” 和 “↕” 文字进一步提示方向。最小/最大值的边界计算水平滑块minimumX: 12、maximumX: hTrack.width - hKnob.width - 12两边各留 12 像素边距。滑块中心对齐轨道中线的关键是y: (hTrack.height - height) / 2——把滑块垂直居中水平拖拽就永远贴着中线走。drag.axis 有四种取值Drag.XAxis仅水平音量条、进度条Drag.YAxis仅垂直滚动条、高度调节Drag.XAndYAxis双轴自由自由摆放的元素Drag.None禁用拖拽临时锁住某元素。运行验证Qt Creator 打开示例的 CMakeLists.txt按 CtrlR 运行自由拖拽按住蓝色方块满画布拖动观察坐标变化点重置位置看方块回中心单轴约束拖横滑块只能左右动拖竖滑块只能上下动观察 x / y 数值变化。小结拖动窗口标题栏移动整个窗口把drag.target指向窗口根元素即可。单轴滑块可以扩展成音量条用Math.round(hKnob.x)换算成 0-100 的值。双轴拖拽加一条Behavior on x动画可以让元素松手后弹回某个区域做卡片归位效果。用drag.filterChildren可以让父级 MouseArea 同时处理多个子元素。系列文章QML 拖拽篇自由移动与单轴约束本篇QML 拖拽篇接收拖拽与数据交换QML 拖拽篇拖拽吸附与列表排序QML 拖拽篇跨列拖拽与数字拼图已验证环境Qt 版本Qt 6.11.1操作系统Windows 11项目地址QML 示例合集V2.0 - qml_drag_drop