通过MouseArea Qt QML检测左滑或右滑

7
我有一个问题,如何在QML的MouseArea中检测滑动手势?
这段代码来自官方文档:
    Rectangle {
    id: container
    width: 600; height: 200

    Rectangle {
        id: rect
        width: 500; height: 500

        MouseArea {
            anchors.fill: parent
            drag.target: rect
            drag.axis: Drag.XAxis
            drag.minimumX: 0
            drag.maximumX: container.width - rect.width

            //event slide here ?
        }
    }
}

但我不明白如何进行左右滑动,我知道这在移动应用程序(iOS和Android)中使用。
有人可以帮助我吗?谢谢。 如何检测手指的左右滑动?谢谢。

2
请查看 MultiPointTouchArea QML 类型。 - ManuelH
8个回答

4

就像derM解释的事件系列一样:触摸 -> 移动 -> 释放
这是我对该概念的解决方案。 运行得非常好!

MouseArea {
            width: 100
            height: 50
            preventStealing: true
            property real velocity: 0.0
            property int xStart: 0
            property int xPrev: 0
            property bool tracing: false
            onPressed: {
                xStart = mouse.x
                xPrev = mouse.x
                velocity = 0
                tracing = true
                console.log( " pressed  "+xStart)
                console.log( " pressed  "+xPrev)
            }
            onPositionChanged: {
                if ( !tracing ) return
                var currVel = (mouse.x-xPrev)
                velocity = (velocity + currVel)/2.0
                xPrev = mouse.x
                if ( velocity > 15 && mouse.x > parent.width*0.2 ) {
                    tracing = false
                    console.log( " positioned  ")
                    // SWIPE DETECTED !! EMIT SIGNAL or DO your action
                }
            }
            onReleased: {
                tracing = false
                if ( velocity > 15 && mouse.x > parent.width*0.2 ) {
                    // SWIPE DETECTED !! EMIT SIGNAL or DO your action
                    console.log("abcccccccccc")
                }
            }
        }
    }

3
一次滑动操作是由一系列简单的事件组成:
Touch -> Movement -> Release

这就是检测它的确切方法:
你需要在 onPressed 中初始化一些变量(例如 originX/Y),然后在onPositionChanged中检测移动,计算原点和当前位置之间的向量,分析长度和方向以评估方向和速度。将 originX/Y 设置为新位置并继续直到 onReleased。然后,您可以根据上一个向量或者运动历史记录来确定它是否为滑动(以某种方式存储或累积计算出的向量)。
需要考虑的事项:用户在释放之前减速,或在两个步骤之间释放,所以最后的移动可能很短。因此仅考虑最后的向量可能会导致不良结果。
相反,您可以累积最后n个向量并应用一些权重。
另外,如果您将onPositionChanged替换为 Timer,则可以使用更长的时间间隔进行分析。您可以尝试调整间隔以找到最佳行为。
由于实现精心调整的检测算法并不容易,我建议您重新考虑是否有必要自己实现检测,或者是否有许多已经实现了滑动行为的 Item 可以使用。

在QT/QML跨平台框架中,可能不存在一种自动检测滑动的模式吗?好的,我会使用on pressed x 和 on release x,并计算矢量以检测滑动方向。谢谢! - Mr. Developer
@Mr.Developer 不是的。你应该自己动手。曾经有一个GestureArea,现在已经被PinchAreaMultiPointTouchArea所取代(几乎)。我从未使用过原始的,但后者对我的需求非常有效。 - BaCaRoZzo

3

有一种更简单的方法,你可以使用一个空的Flickable。然后你可以查看滑动标志来看用户是否已经滑动它。


2

以下是使用Flickable实现的代码(受Stuart答案的启发):

Flickable {
    id: swipeArea
    ...
    
    flickableDirection: Flickable.HorizontalFlick
    onFlickStarted: {
        if (horizontalVelocity < 0) {
            console.log("swiped right")
        }
        if (horizontalVelocity > 0) {
            console.log("swiped left")
        }
    }
    boundsMovement: Flickable.StopAtBounds
    pressDelay: 0
    
    // children
}

请确保您拥有的任何控件都是 Flickable 的子级,因为按下事件不会传播到 Flickable 下面的项目。


1

也可以使用 DragHandler 来实现滑动检测。在我的情况下,这是检测垂直ScrollView / Flickable上的水平滑动最简单的方法(我将 DragHandler 添加为 ScrollView 的下一个兄弟节点,而不是它的子元素)。

DragHandler {
    property real lastHorizontalVelocity

    target: null
    xAxis.enabled: true
    yAxis.enabled: false

    // add this to ignore vertical scrolling of your Flickable, flick is id of the Flickable
    //enabled: !flick.moving

    onCentroidChanged: {
        if (centroid.position.x !== 0 || centroid.velocity.x !== 0) {
            lastHorizontalVelocity = centroid.velocity.x
            return
        }
        // 0.4 threshold was obtained by making lots of swipes to find an appropriate value
        if (Math.abs(lastHorizontalVelocity) < 0.4)
            return
        if (lastHorizontalVelocity < 0) {
            // handle swipe from right to left
        } else {
            // handle swipe from left to right
        }
    }
}

0

我也遇到了抽屉项的问题(似乎是QTBUG-59141),所以我实现了一个简单的可在QML中注册和创建的可滑动区域(qmlRegisterType...)。请注意,它只检测一般的滑动方向,即水平/垂直。应该很容易实现支持从左到右或从上到下(反之亦然)的检测。以下是基本示例。

//头部分

class FrontendFlickArea : public QQuickItem
{
    Q_OBJECT

public:
    explicit FrontendFlickArea(QQuickItem *parent = nullptr);

protected:
    void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
    void mouseMoveEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
    void mouseReleaseEvent(QMouseEvent *event) Q_DECL_OVERRIDE;

signals:
    void horizontalFlick();
    void verticalFlick();

private:
    QElapsedTimer m_timer;

    QPoint m_pos;
    qint64 m_elapsedTime;

    QPair<qreal, qreal> m_velocitySum;
    int m_velocitySz;
};

// 实现部分

#define DEBUG 0
#define VELOCITY_THRESH 2000 // pix / s
#define NSEC_TO_SEC_CONST 1E-9

#if DEBUG
#include <QDebug>
#endif

FrontendFlickArea::FrontendFlickArea(QQuickItem *parent)
    : QQuickItem(parent),
      m_timer(),
      m_pos(),
      m_elapsedTime(),
      m_velocitySum(),
      m_velocitySz(0)
{
    setAcceptedMouseButtons(Qt::LeftButton);
}

void FrontendFlickArea::mousePressEvent(QMouseEvent *event)
{
    m_pos = event->pos();

    if(m_timer.isValid())
        m_timer.invalidate();
    m_timer.start();

    m_velocitySum.first = m_velocitySum.second = 0.0;
    m_velocitySz = 0;
    m_elapsedTime = m_timer.nsecsElapsed();
}

void FrontendFlickArea::mouseMoveEvent(QMouseEvent *event)
{
    const QPoint diffPos = event->pos() - m_pos;
    const qint64 elapsed = m_timer.nsecsElapsed();

    const qreal timeDiff = static_cast<qreal>(elapsed - m_elapsedTime) * NSEC_TO_SEC_CONST;

    m_velocitySum.first += diffPos.x() / timeDiff;
    m_velocitySum.second += diffPos.y() / timeDiff;
    m_velocitySz++;

#if DEBUG
    qInfo() << "VelocityX: " << diffPos.x() / timeDiff << ", VelocityY: " << diffPos.y() / timeDiff;
#endif

    m_elapsedTime = elapsed;
    m_pos = event->pos();
}

void FrontendFlickArea::mouseReleaseEvent(QMouseEvent *event)
{
    if(m_velocitySz == 0)
        return;

    bool eventAccept = false;

    const qreal velocityX = m_velocitySum.first / m_velocitySz;
    const qreal velocityY = m_velocitySum.second / m_velocitySz;

#if DEBUG
    qInfo() << "Avg VelocityX: " << velocityX << ", Avg VelocityY: " << velocityY;
#endif

    if(qAbs(velocityX) > VELOCITY_THRESH) {
        eventAccept = true;
        emit horizontalFlick();
    }

    if(qAbs(velocityY) > VELOCITY_THRESH) {
        eventAccept = true;
        emit verticalFlick();
    }

    if(event->button() == Qt::LeftButton && eventAccept)
        event->accept();
}

编辑: 为了在具有不同像素密度的设备上获得最佳性能,最好使用密度无关像素而不是实际像素来设置速度阈值。


0
    MouseArea {
        property variant previousPosition: 0
        property variant direction: 0

        anchors.fill: parent;

        onPressed: {
            previousPosition = mouseX;
            direction = 0;
            console.debug("onPressed mouseX:" + mouseX);
        }

        onPositionChanged: {
            if(previousPosition > mouseX){
                direction = 1;
            }
            else if(previousPosition < mouseX){
                direction = -1;
            }
            else{
                direction = 0;
            }
            previousPosition = mouseX;
        }

        onReleased: {
            if(direction > 0){
                console.debug("swipe to right");
            }
            else if(direction < 0){
                console.debug("swipe to left");
            }
            else{
                console.debug("swipe no detected");
            }
        }
    }

请在您的答案中添加一些解释 :) - Robson

0

这是我的五分建议:

MouseArea {

signal sgSwipeLeft();
signal sgSwipeRight();
signal sgSwipeDown();
signal sgSwipeUp();

QtObject {

    property bool pTracing: false;
    property real pXVelocity: 0.0;
    property real pYVelocity: 0.0;
    property int pXPrev: 0;
    property int pYPrev: 0;

    id: oPrivate;
}

id: oRoot;
preventStealing: true;

onPressed: {

    oPrivate.pTracing = true;
    oPrivate.pXVelocity = 0;
    oPrivate.pYVelocity = 0;
    oPrivate.pXPrev = mouse.x;
    oPrivate.pYPrev = mouse.y;
}

onPositionChanged: {

    if (!oPrivate.pTracing) return;

    var oCurrentXVelocity = (mouse.x - oPrivate.pXPrev);
    oPrivate.pXVelocity = (oPrivate.pXVelocity + oCurrentXVelocity) / 2.0;
    oPrivate.pXPrev = mouse.x;

    var oCurrentYVelocity = (mouse.y - oPrivate.pYPrev);
    oPrivate.pYVelocity = (oPrivate.pXVelocity + oCurrentYVelocity) / 2.0;
    oPrivate.pYPrev = mouse.y;

    if (oPrivate.pXVelocity > 15 && mouse.x > parent.width * 0.2) {
        oPrivate.pTracing = false;
        oRoot.sgSwipeRight();
    } else if (oPrivate.pXVelocity < -15 && mouse.x > parent.width * 0.2) {
        oPrivate.pTracing = false;
        oRoot.sgSwipeLeft();
    } else if (oPrivate.pYVelocity > 15 && mouse.y > parent.height * 0.2) {
        oPrivate.pTracing = false;
        oRoot.sgSwipeDown();
    } else if (oPrivate.pYVelocity < -15 && mouse.y < parent.height * 0.2) {
        oPrivate.pTracing = false;
        oRoot.sgSwipeUp();
    }
}

onClicked: console.log("onClicked");
onPressAndHold: console.log("onPressAndHold");
onSgSwipeLeft: console.log("onSgSwipeLeft");
onSgSwipeDown: console.log("onSgSwipeDown");
onSgSwipeRight: console.log("onSgSwipeRight");
onSgSwipeUp: console.log("onSgSwipeUp");
onReleased: console.log("onReleased");
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接