Reputation: 3417
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.
Upvotes: 7
Views: 6277
Reputation: 2511
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
}
}
}
Upvotes: 1
Reputation: 11
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, i.e. 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.
Upvotes: 0
Reputation: 260
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");
}
Upvotes: 0
Reputation: 2711
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
.
Upvotes: 2
Reputation: 21
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");
}
}
}
Upvotes: 0
Reputation: 31
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).
Upvotes: 3
Reputation: 510
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")
}
}
}
}
Upvotes: 4
Reputation: 13701
A swipe is a simple chain of events:
Touch -> Movement -> Release
So this is exactly how you detect it:
You initialize some variables (e.g. 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.
Upvotes: 3