qt中的手势

qt中的手势

手势在触屏中用的比较多。

前面写过一篇博客,讲了qt中的触屏事件是怎样的,不懂的,可以先看一些之前的文章,现在就是有些触屏事件怎么合成手势并调用的。

Qt 包含一个用于手势编程的框架,它能够通过一系列事件形成手势,而与所使用的输入方法无关。手势可以是鼠标的特定移动、触摸屏操作或其他来源的一系列事件。输入的性质、对手势的解释和采取的行动都由开发人员决定。

QGesture 类概述

QGesture 这个类是qt中提供的标准手势的基础类,提供所有手势通用信息的属性,这些属性还可扩展以提供额外的特定手势信息。常用的平移、捏合和轻扫手势由专门的子类QPanGesture QPinchGesture QSwipeGesture来实现,还有QTapAndHoldGesture,QTapGesture类。

标准手势的使用

可以为QWidget 和QGraphicsObject 子类的实例启用手势。接受手势输入的对象在整个文档中称为目标对象。

1.手势的启用

要为目标对象启用手势,请调用其QWidget::grabGesture() 或QGraphicsObject::grabGesture() 函数,并在参数中说明所需的手势类型。标准类型由Qt::GestureType 枚举定义,包括许多常用手势。

2.手势的识别之后事件处理

当用户执行手势时,QGestureEvent 事件将传递给目标对象,这些事件可通过重新实现部件的QWidget::event() 处理函数或图形对象的QGraphicsItem::sceneEvent() 来处理。

要在这两个函数中,用户可以对识别到的手势事件进行自己的事件触发和处理。

由于一个目标对象可以订阅不止一种手势类型,因此QGestureEvent 可以包含不止一个QGesture ,表示有几种可能的手势同时处于活动状态。这就需要小工具来决定如何处理这些多重手势,并选择是否取消其中一些手势,转而使用其他手势。

QGestureEvent 对象中包含的每个QGesture 都可以被单独接受或忽略,也可以一起被接受或忽略。此外,还可以使用多个获取器查询单个QGesture 数据对象状态。

3.手势事件标准处理过程

当QGesture 到达您的 widget 时,默认情况下会被接受。不过,好的做法是始终明确接受或拒绝一个手势。一般规则是,如果您接受一个手势,那么您就在使用它。如果忽略它,则表示对它不感兴趣。忽略一个手势可能意味着它被提供给另一个目标对象,或者被取消。

每个QGesture 都会经历几种状态;有一种明确定义的方法可以改变状态,通常用户输入是状态改变的原因(例如通过开始和停止交互),但小工具也可以导致状态改变。

当一个特定的QGesture 首次传送到一个部件或图形项目时,它将处于Qt::GestureStarted 状态。此时处理手势的方式会影响以后能否与之交互。

接受手势意味着 Widget 会对手势采取行动,随后将出现 Qt::GestureUpdatedstate 的手势。

忽略手势意味着手势将不再提供给你。手势也将提供给父部件或项目。

当手势处于开始状态并被接受时,调用 setGestureCancelPolicy() 会导致其他手势被取消。

使用QGesture::CancelAllInContext 取消手势会导致处于任何状态的所有手势被取消,除非它们被明确接受。这意味着子系统上的活动手势也会被取消。这也意味着,如果小部件忽略了在同一个QGestureEvent 中发送的手势,这些手势也会被取消。这也是一种有用的方法,可以过滤掉除你感兴趣的手势之外的所有手势。

简单点说,就是在多个手势都被触发的条件下,你可以选择自己所感兴趣的,其他的手势可以忽略掉或者通过 setGestureCancelPolicy()函数或者QGesture::CancelAllInContext 函数来取消已经触发的手势,只保留自己想要的。

4.对手势事件处理示例

为方便起见,图像手势示例重新实现了一般的event() 处理函数,并将手势事件委托给专门的 gestureEvent() 函数:

cpp 复制代码
bool ImageWidget::event(QEvent *event)
{//判断是否是手势事件,如果是,就进入自己手势处理的函数gestureEvent(),
//是把前面说的手势事件QGestureEvent事件传递了过去
    if (event->type() == QEvent::Gesture)
        return gestureEvent(static_cast<QGestureEvent*>(event));
    return QWidget::event(event);
}

交付给目标对象的手势事件可以单独进行检查并适当处理:

cpp 复制代码
bool ImageWidget::gestureEvent(QGestureEvent *event)
{
    qCDebug(lcExample) << "gestureEvent():" << event;
    //这里就直接就获取对应的手势就处理了,其实这里还可以对触发有冲突的事件进行屏蔽
	//QList<QGesture *> activeGestures() const 	获取活跃的手势
 	//QList<QGesture *> canceledGestures() const 获取关闭的手势	
 	//QGesture *gesture(Qt::GestureType type) const	获取所有触发的手势
 	//QList<QGesture *> gestures() const		获取对应的手势
 	//比如这里不给同时触发SwipeGesture和PanGesture手势,就可以通过gestures()判断数量,如果超过了一个,不处理等等逻辑
    if (QGesture *swipe = event->gesture(Qt::SwipeGesture))
        swipeTriggered(static_cast<QSwipeGesture *>(swipe));
    else if (QGesture *pan = event->gesture(Qt::PanGesture))
        panTriggered(static_cast<QPanGesture *>(pan));
    if (QGesture *pinch = event->gesture(Qt::PinchGesture))
        pinchTriggered(static_cast<QPinchGesture *>(pinch));
    return true;
}

响应手势只需获取QGestureEvent 发送给目标对象的QGesture 对象,并检查其中包含的信息即可。

cpp 复制代码
void ImageWidget::swipeTriggered(QSwipeGesture *gesture)
{
	//这里是具体的手势的状态进行处理了,能够获取到手势里面的具体属性等信息在处理对应的逻辑
    if (gesture->state() == Qt::GestureFinished) {
        if (gesture->horizontalDirection() == QSwipeGesture::Left
            || gesture->verticalDirection() == QSwipeGesture::Up) {
            qCDebug(lcExample) << "swipeTriggered(): swipe to previous";
            goPrevImage();
        } else {
            qCDebug(lcExample) << "swipeTriggered(): swipe to next";
            goNextImage();
        }
        update();
    }
}

在这里,我们会检查用户滑动部件的方向,并相应地修改其内容。

可以通过这些步骤,对一个触屏事件触发的手势进行比较好的控制。

创建自己的手势识别器

添加对新手势的支持需要创建和注册一个新的手势识别器。根据手势的识别过程,可能还需要创建一个新的手势对象。

要创建一个新的识别器,需要子类化QGestureRecognizer 以创建一个自定义识别器类。其中有一个虚拟函数必须重新实现,另外两个可以根据需要重新实现。

过滤输入事件

必须重新实现recognize() 函数。该函数用于处理和过滤目标对象的输入事件,并确定它们是否与识别器正在寻找的手势相对应。

虽然手势识别的逻辑是在该函数中实现的,可能会使用基于Qt::GestureState 枚举的状态机,但您可以在所提供的QGesture 对象中存储有关识别过程状态的持久信息。

您的recognize() 函数必须返回一个QGestureRecognizer::Result 值,该值表示给定手势和目标对象的识别状态。这将决定是否向目标对象发送手势事件。

自定义手势

如果选择用自定义QGesture 子类来表示手势,则需要重新实现create() 函数,以构建手势类实例,而不是标准的QGesture 实例。或者,您也可以使用标准的QGesture 实例,但在其中添加额外的动态属性,以表达您想要处理的手势的具体细节。

重置手势

如果您使用的自定义手势对象在取消手势时需要重置或进行其他特殊处理,则需要重新实现reset() 函数来执行这些特殊任务。

请注意,QGesture 对象只针对目标对象和手势类型的每种组合创建一次,而且每次用户尝试在目标对象上执行相同的手势类型时,这些对象都可能被重复使用。因此,在每次尝试识别手势后,重新实现reset() 函数进行清理是非常有用的。

使用新的手势识别器

要使用手势识别器,请构建QGestureRecognizer 子类的实例,并通过QGestureRecognizer::registerRecognizer() 向应用程序注册。可以通过QGestureRecognizer::unregisterRecognizer() 删除特定类型手势的识别器。

自定义手势例子(画圆手势)

1. 创建自定义手势类cpp

复制代码
// circlegesture.h
#include <QGesture>
#include <QPointF>

class CircleGesture : public QGesture {
    Q_OBJECT
public:
    explicit CircleGesture(QObject *parent = nullptr);

    QPointF center() const { return m_center; }
    qreal radius() const { return m_radius; }

private:
    friend class CircleGestureRecognizer;
    QPointF m_center;
    qreal m_radius = 0;
};

2. 实现手势识别器

h

复制代码
// circlegesturerecognizer.h
#include <QGestureRecognizer>
#include <QVector>

class CircleGestureRecognizer : public QGestureRecognizer {
public:
    explicit CircleGestureRecognizer();

private:
    QGesture* create(QObject* target) override;
    Result recognize(QGesture* state, QObject* watched, QEvent* event) override;
    void reset(QGesture* state) override;

    // 圆检测算法
    bool isCircle(const QVector<QPointF>& points, QPointF& center, qreal& radius);
};

cpp

复制代码
// circlegesturerecognizer.cpp
#include "circlegesturerecognizer.h"
#include "circlegesture.h"
#include <QMouseEvent>
#include <QDebug>
#include <cmath>

CircleGestureRecognizer::CircleGestureRecognizer() 
    : QGestureRecognizer() {}

QGesture* CircleGestureRecognizer::create(QObject* target) {
    return new CircleGesture(target);
}

QGestureRecognizer::Result CircleGestureRecognizer::recognize(
    QGesture* state, QObject*, QEvent* event)
{
    CircleGesture* gesture = static_cast<CircleGesture*>(state);

    static QVector<QPointF> points;
    static QPointF startPos;

    QTouchEvent* touchEvent = dynamic_cast<QTouchEvent*>(event);
    if (touchEvent) {
        // 处理触摸点
        const QTouchEvent::TouchPoint& tp = touchEvent->touchPoints().first();
        switch (event->type()) {
        case QEvent::TouchBegin:
            // 类似MouseButtonPress
        case QEvent::TouchUpdate:
            // 类似MouseMove
        case QEvent::TouchEnd:
            // 类似MouseButtonRelease
        }
    }
    else if{ // 鼠标处理...
    const QMouseEvent* mouseEvent = static_cast<const QMouseEvent*>(event);

    switch (event->type()) {
    case QEvent::MouseButtonPress:
        points.clear();
        startPos = mouseEvent->pos();
        points.append(startPos);
        return MayBeGesture;
    
    case QEvent::MouseMove:
        if (points.isEmpty()) return Ignore;
        points.append(mouseEvent->pos());
        return MayBeGesture;
    
    case QEvent::MouseButtonRelease: {
        if (points.size() < 10) { // 最少需要10个点
            return CancelGesture;
        }

        QPointF center;
        qreal radius;
        if (isCircle(points, center, radius)) {
            gesture->m_center = center;
            gesture->m_radius = radius;
            points.clear();
            return FinishGesture;
        }
        return CancelGesture;
    }
    
    default:
        break;
    }
    }
    return Ignore;
}

void CircleGestureRecognizer::reset(QGesture* state) {
    CircleGesture* gesture = static_cast<CircleGesture*>(state);
    gesture->m_radius = 0;
    QGestureRecognizer::reset(state);
}

// 核心检测算法
bool CircleGestureRecognizer::isCircle(
    const QVector<QPointF>& points, 
    QPointF& center, 
    qreal& radius) 
{
    // 1. 计算中心点(所有点的平均值)
    qreal sumX = 0, sumY = 0;
    for (const QPointF& p : points) {
        sumX += p.x();
        sumY += p.y();
    }
    center = QPointF(sumX / points.size(), sumY / points.size());

    // 2. 计算平均半径
    qreal totalDist = 0;
    for (const QPointF& p : points) {
        totalDist += std::hypot(p.x() - center.x(), p.y() - center.y());
    }
    radius = totalDist / points.size();

    // 3. 计算标准差(判断离散程度)
    qreal variance = 0;
    for (const QPointF& p : points) {
        qreal dist = std::hypot(p.x() - center.x(), p.y() - center.y());
        variance += std::pow(dist - radius, 2);
    }
    qreal stdDev = std::sqrt(variance / points.size());

    // 4. 判断圆形度(标准差 < 半径的15%)
    if (stdDev > radius * 0.15) {
        return false;
    }

    // 5. 检查角度覆盖(至少270度)
    QVector<qreal> angles;
    for (const QPointF& p : points) {
        qreal angle = std::atan2(p.y() - center.y(), p.x() - center.x());
        angles.append(angle);
    }
    
    std::sort(angles.begin(), angles.end());
    qreal maxGap = 0;
    for (int i = 1; i < angles.size(); ++i) {
        maxGap = qMax(maxGap, angles[i] - angles[i-1]);
    }
    // 检查首尾间隙
    maxGap = qMax(maxGap, angles.front() + 2*M_PI - angles.back());

    // 最大间隙应小于90度(π/2)
    return maxGap < M_PI_2;
}
  1. 在窗口中使用手势cpp

    // mainwindow.h
    #include
    #include "circlegesture.h"

    class MainWindow : public QMainWindow {
    Q_OBJECT
    public:
    MainWindow(QWidget* parent = nullptr);

    protected:
    bool event(QEvent* event) override;

    private:
    Qt::GestureType circleType;
    };

cpp

复制代码
// mainwindow.cpp
#include "mainwindow.h"
#include "circlegesturerecognizer.h"

MainWindow::MainWindow(QWidget* parent)
    : QMainWindow(parent)
{
    // 注册手势
    CircleGestureRecognizer* recognizer = new CircleGestureRecognizer;
    circleType = QGestureRecognizer::registerRecognizer(recognizer);
    
    // 启用手势识别
    grabGesture(circleType);
}

bool MainWindow::event(QEvent* event) {
    if (event->type() == QEvent::Gesture) {
        QGestureEvent* gestureEvent = static_cast<QGestureEvent*>(event);
        if (CircleGesture* circle = static_cast<CircleGesture*>(
            gestureEvent->gesture(circleType))) 
        {
            if (circle->state() == Qt::GestureFinished) {
                qDebug() << "Circle detected! Center:" << circle->center()
                         << "Radius:" << circle->radius();
                return true;
            }
        }
    }
    return QMainWindow::event(event);
}

注意点:

:
是调用了circle->setHotSpot(mouseEvent->pos());设置了热点,但是要是全局坐标才行,否则就会出现这个错误。热点需要屏幕全局坐标,而pos()是窗口局部坐标。

Qt Quick 手势

没有通用的全局手势识别器;各个组件可以以各自的方式响应触摸事件。例如,PinchArea 处理双指手势,Flickable 用于单指滑动内容,而 MultiPointTouchArea 可以处理任意数量的触摸点,并允许应用程序开发人员编写自定义的手势识别代码。

相关推荐
用户805533698033 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner3 天前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz8 天前
QML Hello World 入门示例
qt
xcyxiner11 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner11 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner12 天前
DicomViewer (添加模型类)3
qt
xcyxiner12 天前
DicomViewer (目录调整) 2
qt
xcyxiner13 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
LDR00614 天前
Type-C 快充全面升级!LDR6601 赋能个人护理便携电机,重塑剃须刀 / 理发器新体验
c语言·开发语言
雪碧聊技术14 天前
Tree.js是什么?一文讲透
开发语言·javascript·ecmascript