Qt实现流动的管道效果代码示例

在现代图形用户界面(GUI)应用程序中,动态效果可以显著增强用户体验。本文将介绍如何使用Qt框架实现一个流动的管道效果。我们将通过自定义QWidget来绘制管道,并使用定时器来实现流动效果。

1. 准备工作

首先,确保你已经安装了Qt开发环境。如果没有,可以从Qt官方网站下载并安装。

2. 创建项目

打开Qt Creator,创建一个新的Qt Widgets应用程序项目。我们将在这个项目中实现流动的管道效果。

3. 自定义QWidget

我们将创建一个自定义的QWidget类来绘制管道并实现流动效果。以下是类的定义和实现:

#include <QWidget>
#include <QPainter>
#include <QTimer>
#include <QPainterPath>

class PipeWidget : public QWidget {
    Q_OBJECT

public:
    PipeWidget(QWidget *parent = nullptr);

protected:
    void paintEvent(QPaintEvent *event) override;

private slots:
    void updateOffset();

private:
    int m_offset;
    void drawPipe(QPainter &painter);
};

PipeWidget::PipeWidget(QWidget *parent)
    : QWidget(parent), m_offset(0)
{
    QTimer *timer = new QTimer(this);
    connect(timer, &QTimer::timeout, this, &PipeWidget::updateOffset);
    timer->start(50); // 每50毫秒更新一次
}

void PipeWidget::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event);

    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing, true);

    drawPipe(painter);
}

void PipeWidget::updateOffset()
{
    m_offset += 5;
    if (m_offset > width()) {
        m_offset = 0;
    }
    update(); // 重绘窗口
}

void PipeWidget::drawPipe(QPainter &painter)
{
    QPainterPath path;
    path.moveTo(0, height() / 2);
    path.cubicTo(width() / 3, height() / 2 - 50, 2 * width() / 3, height() / 2 + 50, width(), height() / 2);

    QLinearGradient gradient(0, 0, width(), 0);
    gradient.setColorAt(0, Qt::blue);
    gradient.setColorAt(1, Qt::green);

    painter.setBrush(gradient);
    painter.setPen(Qt::NoPen);

    for (int i = 0; i < 10; ++i) {
        painter.save();
        painter.translate(m_offset - i * width() / 10, 0);
        painter.drawPath(path);
        painter.restore();
    }
}

4. 将自定义QWidget添加到主窗口

在主窗口的UI文件中,添加一个QWidget,并将其提升为我们的自定义QWidget类。具体步骤如下:

  1. 打开主窗口的UI文件。
  2. 添加一个QWidget到主窗口。
  3. 右键点击QWidget,选择“提升为...”。
  4. 在弹出的对话框中,填写提升的类名为PipeWidget,并添加头文件路径。
  5. 点击“添加”,然后点击“提升”。

5. 运行项目

现在,你可以运行项目,看到流动的管道效果。管道会从左到右流动,并且循环往复。

#include <QApplication>

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);

    HorizontalFlowPipeWidget widget;
    widget.resize(800, 400);
    widget.show();

    return app.exec();
}

上述实现的原理介绍:

translate 方法是 QPainter 类中的一个方法,用于平移(移动)绘图坐标系。通过调用 translate 方法,可以改变绘图的原点位置,从而实现图形的平移效果。

translate 方法的签名如下:

void QPainter::translate(const QPointF &offset);
void QPainter::translate(const QPoint &offset);
void QPainter::translate(qreal dx, qreal dy);

其中,offset 是一个 QPointF 或 QPoint 类型的点,表示平移的偏移量;dx 和 dy 分别表示在 x 轴和 y 轴方向上的平移量。

QPainterPath 是 Qt 框架中的一个类,用于创建和操作复杂的 2D 图形路径。它提供了一种方便的方式来定义和操作各种形状,如线条、曲线、矩形、椭圆等。QPainterPath 可以包含多个子路径,每个子路径可以是一个简单的形状或一个复杂的图形。

QPainterPath 的主要用途包括:

  1. 绘制复杂图形:通过组合多个基本形状和路径,可以创建复杂的图形。
  2. 路径操作:可以对路径进行平移、旋转、缩放等变换操作。
  3. 填充和描边:可以对路径进行填充和描边,使用不同的画笔和画刷样式。
  4. 剪裁:可以将路径用作剪裁区域,限制绘图操作的范围。

以下是一些 QPainterPath 的常见用法示例:

QPainterPath path;

// 添加一个矩形
path.addRect(10, 10, 80, 50);

// 添加一个椭圆
path.addEllipse(100, 10, 80, 50);

// 添加一个贝塞尔曲线
path.moveTo(200, 30);
path.cubicTo(250, 10, 300, 50, 350, 30);

// 使用 QPainter 绘制路径
QPainter painter(this);
painter.setPen(Qt::blue);
painter.setBrush(Qt::green);
painter.drawPath(path);

通过这些方法,你可以创建复杂的图形路径,并在 Qt 应用程序中进行绘制和操作。

使用 QLinearGradient 绘制了一个从左上角到右下角的线性渐变效果。渐变的颜色从红色过渡到绿色,再过渡到蓝色。通过调整 setColorAt 方法的参数,可以控制渐变过程中不同位置的颜色。

QLinearGradient 是 Qt 框架中的一个类,用于创建线性渐变效果。线性渐变是指颜色从一个点线性地过渡到另一个点。QLinearGradient 可以用于填充图形、控件背景等,以实现平滑的颜色过渡效果。

QLinearGradient 的主要构造函数如下:

QLinearGradient(const QPointF &start, const QPointF &finalStop);

其中,start 是渐变的起始点,finalStop 是渐变的结束点。你可以通过调用 setColorAt 方法来设置渐变过程中不同位置的颜色。

使用 QPainterPath 绘制了一条水平曲线。通过使用 QTimer 定时更新偏移量 m_offset,我们可以实现水平流动的效果。每次定时器触发时,偏移量会增加,然后调用 update() 函数重绘窗口,从而实现水平流动的视觉效果。

cubicTo 方法用于绘制三次贝塞尔曲线。三次贝塞尔曲线由四个点定义:起始点、两个控制点和结束点。cubicTo 方法的签名如下:

void QPainterPath::cubicTo(const QPointF &c1, const QPointF &c2, const QPointF &endPoint);

其中,c1 和 c2 是两个控制点,endPoint 是结束点。通过调整这些点的位置,可以控制曲线的形状。

6. 进一步完善

上述代码示例中,显示的动态曲线为贝塞尔曲线,在流动管道中,我们可能想要的是如三角形状的箭头符号。且管道的方向可能有上下左右四个方向,实现的方法是不一样的。可以自定义实现个Widget,继承自QLabel。

在界面上使用时,可以将 QLabel 提升为自定义的 QWidget。在 Qt 中,提升(Promotion)是一种机制,允许你将一个标准的 Qt 控件(如 QLabel)提升为一个自定义的控件(如自定义的 QWidget)。这样,你可以在设计器中使用标准的控件,但在运行时使用自定义的控件。

以上效果的代码实现:

#ifndef PIPEWIDGET_H
#define PIPEWIDGET_H

#include <QLabel>
#include <QPainter>
#include <QTimer>
#include <QPainterPath>

class PipeWidget : public QLabel
{
public:
    PipeWidget(QWidget *parent=0);

    enum Direction {
            Up,
            Down,
            Left,
            Right
        };

    void setDirection(Direction newDirect);

    void setStart(bool newStart);

protected:
    void paintEvent(QPaintEvent *event) override;

private slots:
    void updateOffset();

private:
    int  m_offset;
    bool m_start = false;
    Direction m_direct = Up; // 默认向上
    void upDirect(QPainter &painter);    //向上
    void downDirect(QPainter &painter);  //向下
    void leftDirect(QPainter &painter);  //左向
    void rightDirect(QPainter &painter); //右向
};

#endif // PIPEWIDGET_H

 

#include "pipewidget.h"


PipeWidget::PipeWidget(QWidget *parent):QLabel(parent), m_offset(0) {
    QTimer *timer = new QTimer(this);
    connect(timer, &QTimer::timeout, this, &PipeWidget::updateOffset);
    timer->start(50); // 每50毫秒更新一次
}


void PipeWidget::paintEvent(QPaintEvent *event) {
    Q_UNUSED(event);
    if(m_start){
        QPainter painter(this);
        painter.setRenderHint(QPainter::Antialiasing, true);
        //m_direct = 1;
        switch (m_direct) {
            case Up: upDirect(painter);break;
            case Down: downDirect(painter);break;
            case Left: leftDirect(painter);break;
            case Right: rightDirect(painter);break;
            default:break;
        }
    }
}

void PipeWidget::updateOffset() {
    switch (m_direct) {
    case Up:{
        //上
        m_offset -= 5;
        if (m_offset < 0) {
            m_offset = height() / 2;
        }
    }break;
    case Down:{
        //下
        m_offset += 5;
        if (m_offset > (height() / 2)) {
            m_offset = 0;
        }
    }break;
    case Left:{
        //左
        m_offset -= 5;
        if (m_offset < 0) {
            m_offset = width() / 2;
        }
    }break;
    case Right:{
        //右
        m_offset += 5;
        if (m_offset > (width() / 2)) {
            m_offset = 0;
        }
    }break;
    default:break;
    }

    update();
}

void PipeWidget::setStart(bool newStart)
{
    m_start = newStart;
}

void PipeWidget::setDirection(Direction newDirect)
{
    m_direct = newDirect;
}

void PipeWidget::upDirect(QPainter &painter)
{
    QPainterPath path;
    //朝上的三角形
    path.moveTo(width() / 2 , height()/2-15); // 三角形顶点
    path.lineTo(0 , height()/2); // 三角形左下角
    path.lineTo(width(),height()/2); // 三角形右下角
    path.closeSubpath(); // 闭合路径

    QLinearGradient gradient(0, 0, 0, height());
    gradient.setColorAt(0, Qt::blue);
    gradient.setColorAt(0, Qt::green);

    painter.setBrush(gradient);
    painter.setPen(Qt::NoPen);

    for (int i = 0; i < 10; ++i) {
        painter.save();
        painter.translate(0, m_offset - i *  (height() / 4) );
        painter.drawPath(path);
        painter.restore();
    }
}

void PipeWidget::downDirect(QPainter &painter)
{
    QPainterPath path;
    //朝下的三角形
    path.moveTo(width() / 2 , height()/2+15); // 三角形顶点
    path.lineTo(0 , height()/2); // 三角形左下角
    path.lineTo(width(),height()/2); // 三角形右下角
    path.closeSubpath(); // 闭合路径

    QLinearGradient gradient(0, 0, 0, height());
    gradient.setColorAt(0, Qt::blue);
    gradient.setColorAt(0, Qt::green);

    painter.setBrush(gradient);
    painter.setPen(Qt::NoPen);

    for (int i = 0; i < 10; ++i) {
        painter.save();
        painter.translate(0, m_offset - i *  (height() / 4) );
        painter.drawPath(path);
        painter.restore();
    }
}

void PipeWidget::leftDirect(QPainter &painter)
{
    QPainterPath path;
    //贝塞尔曲线
    //path.moveTo(width() / 2, 0);
    //path.cubicTo(width() / 2 - 50, height() / 3, width() / 2 + 50, 2 * height() / 3, width() / 2, height());
    //朝左的三角形
    path.moveTo(width() / 2 , 0); // 三角形顶点
    path.lineTo(width() / 2 , height()); // 三角形左下角
    path.lineTo(width()/2-15,height()/2); // 三角形右下角
    path.closeSubpath(); // 闭合路径

    QLinearGradient gradient(0, 0, 0, height());
    gradient.setColorAt(0, Qt::blue);
    gradient.setColorAt(0, Qt::green);

    painter.setBrush(gradient);
    painter.setPen(Qt::NoPen);

    for (int i = 0; i < 10; ++i) {
        painter.save();
        painter.translate(m_offset - i *  (width() / 4) , 0);
        painter.drawPath(path);
        painter.restore();
    }
}

void PipeWidget::rightDirect(QPainter &painter)
{
    QPainterPath path;
    //贝塞尔曲线
    //path.moveTo(width() / 2, 0);
    //path.cubicTo(width() / 2 - 50, height() / 3, width() / 2 + 50, 2 * height() / 3, width() / 2, height());
    //朝右的三角形
    path.moveTo(width() / 2 , 0); // 三角形顶点
    path.lineTo(width() / 2 , height()); // 三角形左下角
    path.lineTo(width()/2+15,height()/2); // 三角形右下角
    path.closeSubpath(); // 闭合路径

    QLinearGradient gradient(0, 0, 0, height());
    gradient.setColorAt(0, Qt::blue);
    gradient.setColorAt(0, Qt::green);

    painter.setBrush(gradient);
    painter.setPen(Qt::NoPen);

    for (int i = 0; i < 10; ++i) {
        painter.save();
        //painter.translate(0, 0);
        //painter.translate(m_offset - i * width() / 2 , 0);
        painter.translate(m_offset - i *  (width() / 4) , 0);
        painter.drawPath(path);
        painter.restore();
    }
}

使用:

void MainWindow::on_pushButton_clicked()
{
    //ui->pipe->show();
    //向左
    ui->lb_h->setDirection(PipeWidget::Right);
    ui->lb_h->setStart(true);
    //向上
    ui->lb_v->setDirection(PipeWidget::Down);
    ui->lb_v->setStart(true);
}

 

7. 总结

通过本文的介绍,我们学习了如何使用Qt框架实现一个流动的管道效果。我们创建了一个自定义的QWidget类,并使用定时器和绘图API来实现流动效果。这个示例展示了Qt强大的图形绘制和动画功能,可以用于创建各种动态效果的GUI应用程序。

希望这篇文章对你有所帮助,欢迎进一步探索Qt框架的其他功能和特性。

其他资源

qt 虚线流动效果实现_qt制作 流动 虚线-CSDN博客

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

特立独行的猫a

您的鼓励是我的创作动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值