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(代码片段

QT Quick QML 实例之 Popup 弹出界面

QT Quick QML 实例之 Popup 弹出界面

QT Quick QML 实例之 Popup 弹出界面

QT Quick QML 实例之 Popup 弹出界面

Qt Quick QML 实例之疯狂数字游戏(QML C++混合编程翻译QSetting )建议收藏