QT Quick QML 实例之虚拟操作杆
Posted 火山上的企鹅
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了QT Quick QML 实例之虚拟操作杆相关的知识,希望对你有一定的参考价值。
GitHub 地址: QmlLearningPro ,选择子工程 VirtualJoystick.pro
QML 其它文章请点击这里: QT QUICK QML 学习笔记
一、演示
本文参考 QGroundControl 地面站的虚拟操作杆部分,它使用了左右两个遥控共四通道,实现无人机的横滚、俯仰、航向和油门的控制。
而在本文中,左右遥控分别控制一个小狗狗和小猫咪的移动,而且确保拇指放下的地方为操作杆的中心, 如下演示
Android 平台下:
Windows 平台下:
二、实现思路
核心思路如下:
其中核心控件为 MultiPointTouchArea:
MultiPointTouchArea 为 qml 中的多点触摸提供了最基本、最重要的支持,它与TouchPoint及相关域结合,可以说是qml中多点触摸的基石。
MultiPointTouchArea是不可见元素,它用来跟踪多点触摸。从 Item 继承过来的 enabled 属性用来标识触点操作是否有效。如果该属性为false,则触摸区域将忽略鼠标以及触摸事件。
默认情况下,鼠标的处理方式与单个触摸点的处理方式相同,触摸区域下的项目不会接收鼠标事件,因为触摸区域正在处理它们。 但是,如果 mouseEnabled 属性设置为false,则它对鼠标事件变得透明,以便可以使用另一个鼠标敏感项(例如MouseArea)分别处理鼠标交互。
具体参考 QT 官方帮助文档,以下为本项目中使用:
///--Multiple Point Touch Area, core: touchPoints
MultiPointTouchArea {
anchors.fill: parent
minimumTouchPoints: 1 //only one
maximumTouchPoints: 1 //only one
touchPoints: [ TouchPoint { id: touchPoint } ]
onPressed: _joyRoot.thumbDown(touchPoints)
onReleased: _joyRoot.reCenter()
//border visible
Rectangle {
border.color: "#A6FFA6"
border.width: 2
color: "transparent"
anchors.fill: parent
}
}
鼠标或者拇指的触控的点的输入坐标为:touchPoint.x、touchPoint.y。
三、核心代码
遥控中(输入)核心代码: JoystickThumbPad.qml
import QtQuick.Window 2.12
import QtQuick 2.12
import QtQuick.Controls 1.2
Item {
id: _joyRoot
///--Input:
property real imageHeight: 10
///--Output:xAxis、yAxis、xPositionDelta、yPositionDelta
property real xAxis: 0 ///< Value range [-1,1], negative values left stick, positive values right stick
property real yAxis: 0 ///< Value range [-1,1], negative values down stick, positive values up stick
property real xPositionDelta: 0 ///< Amount to move the control on x axis ( [-50,50] )
property real yPositionDelta: 0 ///< Amount to move the control on y axis ( [-50,50] )
property real _centerXY: width / 2
property bool _processTouchPoints: false
property color _fgColor: "black"
property real _hatWidth: 15
property real _hatWidthHalf: _hatWidth / 2
property real stickPositionX: _centerXY //Value range [0,width]
property real stickPositionY: _centerXY //Value range [0,height]
onWidthChanged: calculateXAxis()
onStickPositionXChanged: calculateXAxis()
onHeightChanged: calculateYAxis()
onStickPositionYChanged: calculateYAxis()
function calculateXAxis() {
//xAxis = ((stickPositionX / width) * 2 - 1)
xAxis = stickPositionX / width
}
function calculateYAxis() {
//yAxis = (1 - (stickPositionY / height) * 2)
yAxis = stickPositionY / height
}
///--Release the thumb and return to the center position
function reCenter() {
_processTouchPoints = false
// Move control back to original position
xPositionDelta = 0
yPositionDelta = 0
// Re-Center sticks as needed
stickPositionX = _centerXY
stickPositionY = _centerXY
}
///--Where the thumb is pressed, it is the center of the joystick
function thumbDown(touchPoints) {
// Position the control around the initial thumb position
console.log("touchPoints[0].x",touchPoints[0].x)
console.log("touchPoints[0].y",touchPoints[0].y)
xPositionDelta = touchPoints[0].x - _centerXY //[-50,50]
yPositionDelta = touchPoints[0].y - _centerXY //[-50,50]
// We need to wait until we move the control to the right position before we process touch points
_processTouchPoints = true
}
///--stickPositionX = touchPoint.x ; stickPositionY = touchPoint.y
Connections {
target: touchPoint
onXChanged: {
if (_processTouchPoints) {
_joyRoot.stickPositionX = Math.max(Math.min(touchPoint.x, _joyRoot.width), 0)
}
}
onYChanged: {
if (_processTouchPoints) {
_joyRoot.stickPositionY = Math.max(Math.min(touchPoint.y, _joyRoot.height), 0)
}
}
}
///--Multiple Point Touch Area, core: touchPoints
MultiPointTouchArea {
anchors.fill: parent
minimumTouchPoints: 1 //only one
maximumTouchPoints: 1 //only one
touchPoints: [ TouchPoint { id: touchPoint } ]
onPressed: _joyRoot.thumbDown(touchPoints)
onReleased: _joyRoot.reCenter()
//border visible
Rectangle {
border.color: "#A6FFA6"//"#E8FFF5"
border.width: 2
color: "transparent"
anchors.fill: parent
}
}
///--UI: inside circle + outer circle
...
///--UI: Up Down Left Right
...
///--UI: touch points
Rectangle {
width: _hatWidth
height: _hatWidth
radius: _hatWidthHalf
border.color: _fgColor
border.width: 1
color: Qt.rgba(_fgColor.r, _fgColor.g, _fgColor.b, 0.5)
x: stickPositionX - _hatWidthHalf //By default the middle
y: stickPositionY - _hatWidthHalf //By default the middle
}
}
狗狗猫咪(输出):Output.qml
import QtQuick 2.12
import QtQuick.Controls 1.2
Item {
property real leftX
property real leftY
property real rightX
property real rightY
Rectangle {
id: dogRect
width: (parent.width - 50)/2
height: parent.height
color: "#FFE6D9"
border.width: 2
border.color: "black"
property real imageCenter: dogImage.width / 2
property real moveX: Math.max(Math.min(leftX*width - imageCenter, width - dogImage.width) , 0)
property real moveY: Math.max(Math.min(leftY*height - imageCenter, height - dogImage.height), 0)
Image {
id: dogImage
mipmap: true
fillMode: Image.PreserveAspectFit
source: "/images/Dog.png"
x: dogRect.moveX
y: dogRect.moveY
}
}
Rectangle {
id: catRect
width: (parent.width - 50)/2
height: parent.height
color: "#FFE6D9"
anchors.left: dogRect.right
anchors.leftMargin: 50
border.width: 2
border.color: "black"
property real imageCenter: catImage.width / 2
property real moveX: Math.max(Math.min(rightX*width - imageCenter , width - catImage.width) , 0)
property real moveY: Math.max(Math.min(rightY*height - imageCenter, height - catImage.height), 0)
Image {
id: catImage
mipmap: true
fillMode: Image.PreserveAspectFit
source: "/images/Cat.png"
x: catRect.moveX
y: catRect.moveY
}
}
}
根目录和驱动器(触发): main.qml
import QtQuick.Window 2.12
import QtQuick 2.12
import QtQuick.Controls 1.2
Window {
visible: true
width: 192 * 4
height: 108 * 4
color: "grey"
property real _offset: leftStick.width/2
JoystickThumbPad {
id: leftStick
anchors.leftMargin: xPositionDelta + _offset
anchors.bottomMargin: -yPositionDelta + _offset
anchors.left: parent.left
anchors.bottom: parent.bottom
width: 100
height: 100
imageHeight: 20
}
JoystickThumbPad {
id: rightStick
anchors.rightMargin: -xPositionDelta + _offset
anchors.bottomMargin: -yPositionDelta + _offset
anchors.right: parent.right
anchors.bottom: parent.bottom
width: 100
height: 100
imageHeight: 20
}
Output {
id: output
x: 50
y: 10
height: parent.height - leftStick.height*2 - y*2
width: parent.width - x*2
}
///--You can also use signals
Timer {
interval: 50 // 20Hz
running: true
repeat: true
onTriggered: {
output.leftX = leftStick.xAxis
output.leftY = leftStick.yAxis
output.rightX = rightStick.xAxis
output.rightY = rightStick.yAxis
}
}
}
四、android 配置
1. 创建 AndroidManifest.xml
打开后点击默认
2. 添加 icon
3. 修改为全屏
然后用 “普通文本编辑器” 打开,修改 android:screenOrientation=“sensorLandscape” 默认为全屏。
小插曲: Android 上猫狗的图片为 svg 的会报错,修改为 png 格式就没问题了,原因未知
GitHub 地址: QmlLearningPro ,选择子工程 VirtualJoystick.pro
QML 其它文章请点击这里: QT QUICK QML 学习笔记
以上是关于QT Quick QML 实例之虚拟操作杆的主要内容,如果未能解决你的问题,请参考以下文章
26.Qt Quick QML-RotationAnimationPathAnimationSmoothedAnimationBehaviorPauseAnimationSequential(代码片段