简体   繁体   中英

Detect left or right swipe through MouseArea Qt QML

I've the problem, how to detect swipe through MouseArea in qml ?

This code, from documentation:

    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 ?
        }
    }
}

but i didn't understood how to get left or right swipe, i get this use in the mobile application (ios & android).

Somebody can help me ? Thanks. How to detect left or right swipe with finger? Thanks.

Just like derM explained the series of an event : Touch -> Movement -> Release
Here is my solution to that concept. Working Pretty Fine!!

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")
                }
            }
        }
    }

A swipe is a simple chain of events:

Touch -> Movement -> Release

So this is exactly how you detect it:

You initialize some variables (eg originX/Y) onPressed , then you detect movement onPositionChanged , calculate the vector between origin and current position, analyze length and direction to evaluate the direction and velocity. Set originX/Y to the new position and continue until onReleased . Then you can determine whether it was a swipe (depending on the last vector or on the history of the movement - store or accumulate the calculated vectors in some way for this)

Things you need to consider: The last movement might be short for the user slows down just before release or because in between two steps he releases. So considering only the last vector might yield bad results.

Instead you might accumulate the last n vectors, applying some weight.

Also you might improve if you replace the onPositionChanged by a Timer to have longer intervals to analyze. You can play with the interval to find a optimal behavior.

For it is not trivial to implement a finely tuned algorithm for the detection, I recommend to reconsider, whether it is necessary to implement the detection yourself, or whether on of the many Item s that have a swipe behavior implemented might suffice.

There's an easier way, you can just use an empty Flickable. You can then look at the flicking flags to see if the user has flicked it (swiped it).

Here is my implementation using Flickable (inspired by Stuart's answer):

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
}

Just make sure any controls you have are children of the Flickable , since press events won't be propagated to items below a Flickable .

    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");
            }
        }
    }

Here my five cents:

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");
}

I also had a problem with Drawer item (seems like QTBUG-59141 ) , so I implemented a simple flickable area which can be registered and created in QML (qmlRegisterType...). Note that it only detects general flick direction, ie horizontal / vertical. It should be simple to implement support for left-to-right or up-to-down (and vice versa) detection. Basic example below.

// header part

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;
};

// implementation part

#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();
}

EDIT: For best performance across devices with different pixel densities, it would be better to use density independent pixels instead of actual pixels for velocity threshold.

Swipe detection can also be achieved using DragHandler . In my case it was the simplest way to detect horizontal swipes on a vertical ScrollView / Flickable (I've added DragHandler as next sibling of ScrollView , not as its child).

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
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM