图像手势示例

演示了在窗口小部件中使用简单手势的方法。

此示例展示了如何为一个窗口小部件启用手势并使用手势输入来执行动作。

我们使用两个类来创建应用程序的用户界面:MainWidget(主窗口小部件)和 ImageWidget。主窗口小部件类仅作为 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(),以帮助管理发送到小部件的手势事件,以及三个基于手势执行动作的函数:panTriggered()pinchTriggered()swipeTriggered()

ImageWidget 类实现

在窗口小部件的构造函数中,我们先设置各种要用于控制图片显示方式的参数。

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

通过调用 QWidget::grabGesture() 并传入所需的手势类型,我们为窗口小部件启用了三种标准手势。这些手势将由应用程序的默认手势识别器识别,并将事件发送到我们的窗口小部件。

由于 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类提供了属性来将两个触摸点之间的变化距离解释为缩放因子,并将角度变化解释为要应用到图像上的旋转。触摸点之间的中心点可以用来拖动图像,但在本例中我们使用平移手势来达到这个目的。

scaleFactor()表示从上一个事件到下一个事件,缩放应该改变多少,而totalScaleFactor()提供了手势开始以来已经表达过的缩放量。当触摸点释放并开始新的手势时,totalScaleFactor()将再次从1.0开始。在这种情况下,我们将totalScaleFactor()存储在currentStepScaleFactor变量中,以便在paintEvent()中使用它来缩放图像。或者,可以在捏合处理程序中将存储的总缩放因子简单地与scaleFactor()相乘。

相比之下,rotationAngle()表示自捏合手势开始以来的旋转量,而lastRotationAngle()提供前一个值。因此,需要减法以获得增量delta。当用户开始新的捏合手势时,rotationAngle()将从头开始,我们想让图像从其当前角度开始旋转。这是通过将增量delta添加到存储的rotationAngle(它将在paintEvent()中应用)来实现的。如果我们简单地将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

© 2024 Qt公司。此处包含的文档贡献的版权属于其各自的所有者。此处提供的文档是根据自由软件基金会发布的GNU自由文档许可证版本1.3的条款许可的。Qt和相应标志是芬兰和/或其他国家的Qt公司商标。所有其他商标均为其各自所有者的财产。