图像手势示例

演示如何在 widget 中使用简单的手势。

该示例展示了如何为部件启用手势并使用手势输入执行操作。

我们使用两个类来创建应用程序的用户界面:MainWidgetImageWidgetMainWidget 类只是作为ImageWidget 类的容器,我们将对其进行配置以接受手势输入。由于我们对手势的使用方式很感兴趣,因此我们将专注于ImageWidget 类的实现。

ImageWidget 类定义

ImageWidget 类是一个简单的QWidget 子类,除了几个更具体的事件处理程序外,它还重新实现了一般的QWidget::event() 处理程序函数:

class ImageWidget : public QWidget
{
    Q_OBJECT

public:
    ImageWidget(QWidget *parent = nullptr);
    void openDirectory(const QString &url);
    void grabGestures(const QList<Qt::GestureType> &gestures);

protected:
    bool event(QEvent *event) override;
    void paintEvent(QPaintEvent *event) override;
    void resizeEvent(QResizeEvent *event) override;
    void mouseDoubleClickEvent(QMouseEvent *event) override;

private:
    bool gestureEvent(QGestureEvent *event);
    void panTriggered(QPanGesture*);
    void pinchTriggered(QPinchGesture*);
    void swipeTriggered(QSwipeGesture*);
    ...
};

我们还实现了一个私有辅助函数gestureEvent() ,以帮助管理传递给 widget 的手势事件,以及三个根据手势执行操作的函数:panTriggered() pinchTriggered()swipeTriggered()

ImageWidget 类的实现

在 widget 的构造函数中,我们首先要设置用于控制图像显示方式的各种参数。

ImageWidget::ImageWidget(QWidget *parent)
    : QWidget(parent), position(0), horizontalOffset(0), verticalOffset(0)
    , rotationAngle(0), scaleFactor(1), currentStepScaleFactor(1)
{
    setMinimumSize(QSize(100, 100));
}

通过调用QWidget::grabGesture() 并输入我们需要的手势类型,我们为 widget 启用了三种标准手势。应用程序的默认手势识别器将识别这些手势,并向我们的 widget 发送事件。

由于QWidget 没有为手势定义特定的事件处理程序,因此部件需要重新实现通用的QWidget::event() 来接收手势事件。

bool ImageWidget::event(QEvent *event)
{
    if (event->type() == QEvent::Gesture)
        return gestureEvent(static_cast<QGestureEvent*>(event));
    return QWidget::event(event);
}

我们实现的事件处理程序将手势事件委托给专门为该任务编写的私有函数,并将所有其他事件传递给QWidget 的实现。

gestureHandler() 函数会检查新交付的QGestureEvent 所提供的手势。由于一个给定类型的手势在任何时候都只能在一个部件上使用,因此我们可以使用QGestureEvent::gesture() 函数检查每种手势类型:

bool ImageWidget::gestureEvent(QGestureEvent *event)
{
    qCDebug(lcExample) << "gestureEvent():" << event;
    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;
}

如果为某一类型的手势提供了一个QGesture 对象,我们就会调用一个特殊用途函数来处理它,将手势对象转换为相应的QGesture 子类。

为了说明应用程序如何解释标准手势,我们展示了pinchTriggered() 函数的实现,该函数用于处理用户在显示屏或输入设备上移动两个手指时的捏合手势:

void ImageWidget::pinchTriggered(QPinchGesture *gesture)
{
    QPinchGesture::ChangeFlags changeFlags = gesture->changeFlags();
    if (changeFlags & QPinchGesture::RotationAngleChanged) {
        qreal rotationDelta = gesture->rotationAngle() - gesture->lastRotationAngle();
        rotationAngle += rotationDelta;
        qCDebug(lcExample) << "pinchTriggered(): rotate by" <<
            rotationDelta << "->" << rotationAngle;
    }
    if (changeFlags & QPinchGesture::ScaleFactorChanged) {
        currentStepScaleFactor = gesture->totalScaleFactor();
        qCDebug(lcExample) << "pinchTriggered(): zoom by" <<
            gesture->scaleFactor() << "->" << currentStepScaleFactor;
    }
    if (gesture->state() == Qt::GestureFinished) {
        scaleFactor *= currentStepScaleFactor;
        currentStepScaleFactor = 1;
    }
    update();
}

QPinchGesture 类提供的属性可将两个触摸点之间不断变化的距离解释为缩放系数,并将角度 delta 解释为应用于图像的旋转。触摸点之间的中心点可用于拖动图像,但在本示例中,我们使用平移手势来实现这一目的。

scaleFactor() 是一个相对值,代表从一个事件到下一个事件之间缩放的变化程度,而totalScaleFactor() 则提供自手势开始以来的缩放量。当释放触摸点并开始另一个手势时,totalScaleFactor() 将从 1.0 重新开始。在这种情况下,我们将totalScaleFactor() 存储到currentStepScaleFactor 变量中,以便在paintEvent() 中使用它来缩放图像。或者,也可以在捏合处理程序中将存储的总缩放系数乘以scaleFactor()

相比之下,rotationAngle() 表示的是自捏合手势开始以来的旋转量,而lastRotationAngle() 提供的是上一个值。因此有必要做减法,以获得递增的 delta 值。当用户开始新的捏合手势时,rotationAngle() 将从零开始,我们希望图像从当前角度开始旋转。这可以通过在存储的rotationAngle (将应用于paintEvent() )中加入 delta 来实现。如果我们只是简单地将totalRotationAngle() 赋值给存储的rotationAngle ,一个新的手势将导致图像重置为右侧朝上的方向,然后再开始旋转。但我们可以存储自手势开始以来的旋转角度,并将其添加到paintEvent() 中的rotationAngle ,就像我们存储自手势开始以来的缩放量一样。

本示例中的平移和轻扫手势也是在单独的函数中处理的,并使用了传递给它们的QGesture 对象的属性值。

void ImageWidget::paintEvent(QPaintEvent*)
{
    QPainter p(this);

    if (files.isEmpty() && !path.isEmpty()) {
        p.drawText(rect(), Qt::AlignCenter|Qt::TextWordWrap,
                         tr("No supported image formats found"));
        return;
    }

    const qreal iw = currentImage.width();
    const qreal ih = currentImage.height();
    const qreal wh = height();
    const qreal ww = width();

    p.translate(ww / 2, wh / 2);
    p.translate(horizontalOffset, verticalOffset);
    p.rotate(rotationAngle);
    p.scale(currentStepScaleFactor * scaleFactor, currentStepScaleFactor * scaleFactor);
    p.translate(-iw / 2, -ih / 2);
    p.drawImage(0, 0, currentImage);
}

paintEvent() 中,scaleFactor 表示缩放手势开始前的缩放级别,而 currentStepScaleFactor 表示缩放手势进行时的额外缩放系数。但对于旋转,只存储当前的旋转角度(rotationAngle)。水平和垂直偏移表示平移手势拖动图像的距离。

示例项目 @ code.qt.io

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.