Qt 实现炫酷锁屏源码分享

一、源码分享

1、效果展示

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2、源码分享

2.1、工程结构

在这里插入图片描述

2.2、mainwindow.cpp


#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);


    lockScreen = new LockScreen(this);
    lockScreen->setGeometry(this->rect());
}

MainWindow::~MainWindow()
{
    delete ui;
}



void MainWindow::on_pushButton_clicked()
{
    lockScreen->lockScreen(true);
}

void MainWindow::keyPressEvent(QKeyEvent *e)
{
    if(e->key() == Qt::Key_F1)
    {
        lockScreen->lockScreen(false);
    }
}

void MainWindow::resizeEvent(QResizeEvent *event)
{
    lockScreen->setGeometry(this->rect());
}


2.3、lockScreen.h

#ifndef LOCKSCREEN_H
#define LOCKSCREEN_H

#include <QWidget>
#include <QPaintEvent>
#include <QPainter>
#include <QPropertyAnimation>
#include <QPainterPath>

class LockScreen : public QWidget
{
    Q_OBJECT
    Q_PROPERTY(int doorLeftX READ getDoorLeftX WRITE setDoorLeftX NOTIFY doorLeftXChanged FINAL)
    Q_PROPERTY(int doorRightX READ getDoorRightX WRITE setDoorRightX NOTIFY doorRightXChanged FINAL)
    Q_PROPERTY(float ledOpacity READ getLedOpacity WRITE setLedOpacity NOTIFY ledOpacityChanged FINAL)
signals:
    void doorLeftXChanged();

    void doorRightXChanged();

    void ledOpacityChanged();

public:
    explicit LockScreen(QWidget *parent = nullptr);
    void lockScreen(bool isLock);

    int getDoorLeftX() const
    {
        return doorLeftX;
    }

    void setDoorLeftX(int newDoorLeftX)
    {
        if (doorLeftX == newDoorLeftX)
            return;
        doorLeftX = newDoorLeftX;
        emit doorLeftXChanged();
    }

    int getDoorRightX() const
    {
        return doorRightX;
    }

    void setDoorRightX(int newDoorRightX)
    {
        if (doorRightX == newDoorRightX)
            return;
        doorRightX = newDoorRightX;
        emit doorRightXChanged();
    }

    float getLedOpacity() const
    {
        return ledOpacity;
    }

    void setLedOpacity(float newLedOpacity)
    {
        if (qFuzzyCompare(ledOpacity, newLedOpacity))
            return;
        ledOpacity = newLedOpacity;
        emit ledOpacityChanged();
    }

protected:
    void paintEvent(QPaintEvent *e) override;
    bool eventFilter(QObject *obj, QEvent *e) override;
private:
    float normalizeAngle(float angle) {
        return fmod(angle + 360.0f, 360.0f);
    }
    float clamp(float val,float min,float max){
        if(val < min)
            return min;
        else if(val > max)
            return max;
        else
            return val;
    }

private:
    int doorLeftX = 0,doorRightX = 0;
    const int dialSize = 250;
    const int indictorSize = dialSize + 50;
    float currentAngle = 95;
    QRect rectDial;
    float deltaDegrees;
    float recordAngle;
    const int startAngle = 95;
    const int endAngle = 265;
    float ledOpacity = 1.0f;

    QPropertyAnimation *animDoorLeftX = nullptr,*animDoorRightX = nullptr;

    QPropertyAnimation *animLedOpacity = nullptr;
    QImage *imageDial,*imageLines,*imageNotch,*imgageLed;

};

#endif // LOCKSCREEN_H

2.4、lockScreen.cpp

#include "lockScreen.h"

LockScreen::LockScreen(QWidget *parent)
    : QWidget{parent},
    imageDial(new QImage(":/image/images/dial-frame.png")),
    imageLines(new QImage(":/image/images/lines.png")),
    imageNotch(new QImage(":/image/images/notch.png")),
    imgageLed(new QImage(":/image/images/led-dark.png"))
{

    this->animDoorLeftX = new QPropertyAnimation(this,"doorLeftX",this);
    this->animDoorLeftX->setDuration(800);
    this->animDoorLeftX->setEasingCurve(QEasingCurve::InExpo);

    connect(this->animDoorLeftX,&QPropertyAnimation::valueChanged,this,
            [=](const QVariant &value)
            {
                Q_UNUSED(value);
                this->update();
                //qDebug()<<value;
            });
    connect(this->animDoorLeftX,&QPropertyAnimation::finished,this,
            [=]()
            {
                if(this->doorLeftX <= -this->width())
                {
                    this->setVisible(false);
                    this->currentAngle = this->startAngle;
                }
            });

    this->animDoorRightX = new QPropertyAnimation(this,"doorRightX",this);
    this->animDoorRightX->setDuration(800);
    this->animDoorRightX->setEasingCurve(QEasingCurve::InExpo);


    this->animLedOpacity = new QPropertyAnimation(this,"ledOpacity",this);
    this->animLedOpacity->setDuration(800);
    this->animLedOpacity->setEasingCurve(QEasingCurve::InExpo);
    this->animLedOpacity->setStartValue(1.0);
    this->animLedOpacity->setEndValue(0.0);
    this->animLedOpacity->setEasingCurve(QEasingCurve::InOutQuad);
    this->animLedOpacity->start();
    connect(this->animLedOpacity,&QPropertyAnimation::valueChanged,this,
                [=](const QVariant &value)
                {
                    Q_UNUSED(value);
                    this->update();
                });
    connect(this->animLedOpacity,&QPropertyAnimation::finished,this,
            [=]()
            {
                if(ledOpacity == 1.0f)
                {
                    this->animLedOpacity->setDirection(QAbstractAnimation::Forward);
                    this->animLedOpacity->start();
                }
                else
                {
                    this->animLedOpacity->setDirection(QAbstractAnimation::Backward);
                    this->animLedOpacity->start();
                }
            });
    this->setVisible(false);

    this->installEventFilter(this);
}

void LockScreen::lockScreen(bool isLock)
{
    if(isLock)
        this->setVisible(true);
    this->animDoorLeftX->setStartValue(isLock?-this->width()-10:-this->width()/2 + 30);
    this->animDoorLeftX->setEndValue(isLock?-this->width()/2 + 30:-this->width()-10);
    this->animDoorLeftX->start();

    this->animDoorRightX->setStartValue(isLock?this->width():this->width()/2 - indictorSize/2);
    this->animDoorRightX->setEndValue(isLock?this->width()/2 - indictorSize/2:this->width());
    this->animDoorRightX->start();
}

void LockScreen::paintEvent(QPaintEvent *e)
{
    Q_UNUSED(e);
    QPainter painter(this);
    painter.setRenderHints(QPainter::Antialiasing|QPainter::SmoothPixmapTransform, true); // 开启抗锯齿

    const int borderWidth = 6;

    if(doorLeftX==0)
        doorLeftX = -this->width()-10;
    if(this->doorRightX == 0)
        this->doorRightX = this->width();


    //绘制左边门
    painter.setPen(QPen(QColor("#000000"), 6, Qt::SolidLine, Qt::RoundCap, Qt::MiterJoin));
    QLinearGradient gradient(this->width()/2, 0, this->width()/2,this->height());
    gradient.setColorAt(0, QColor("#34373F"));
    gradient.setColorAt(1, QColor("#1D2026")); // 尾部
    painter.setBrush(gradient);

    QRect rectDoorLeft(doorLeftX,borderWidth/2,this->width(),this->height() - borderWidth);
    rectDoorLeft.setX(doorLeftX);
    painter.drawRoundedRect(rectDoorLeft,40,40);


    //绘制右边门
    QRect rectDoorRight(doorRightX,borderWidth/2,this->width(),this->height() - borderWidth);
    rectDoorRight.setX(doorRightX + indictorSize/2);

    QLinearGradient gradient2(this->width()/2, 0, this->width()/2,this->height());
    gradient2.setColorAt(0, QColor("#2e3037"));
    gradient2.setColorAt(1, QColor("#1D2026")); // 尾部
    painter.setBrush(gradient2);
    QRect rectRoundBorder(doorRightX +15,(this->height() - (dialSize +20))/2,dialSize + 20,dialSize + 20);
    painter.drawEllipse(rectRoundBorder);

    painter.setBrush(gradient);
    painter.drawRoundedRect(rectDoorRight,40,40);

    QRectF rectIndictor(this->doorRightX,(this->height() - indictorSize)/2,indictorSize,indictorSize);//绘制蓝线

    painter.setPen(QPen(QColor("#76AAF5"), 4, Qt::SolidLine, Qt::RoundCap, Qt::MiterJoin));
    painter.drawArc(rectIndictor,-this->startAngle*16,-(this->currentAngle - this->startAngle)*16);

    //绘制遮罩
    painter.save();
    painter.setPen(Qt::NoPen);
    painter.setBrush(gradient2);
    painter.drawRoundedRect(QRect(rectRoundBorder.center().x() - 10,rectRoundBorder.y()+3,20,rectRoundBorder.height()-6),5,5);
    painter.restore();

    //绘制dial
    QRectF rectDial(this->doorRightX + 25,(this->height() - dialSize)/2,dialSize,dialSize);//绘制转盘
    this->rectDial.setX(rectDial.x());
    this->rectDial.setY(rectDial.y());
    this->rectDial.setWidth(rectDial.width());
    this->rectDial.setHeight(rectDial.height());
    painter.drawImage(rectDial,*this->imageDial);

    //绘制指示线
    painter.save();
    int size = dialSize*1.1;
    painter.setOpacity(0.3);
    QRect rectLine(doorRightX+(indictorSize - size)/2,(this->height() - size)/2,size,size);

    int lineWidth = this->imageLines->width();
    int lineHeight = this->imageLines->height();
    float width = sqrt(lineWidth*lineWidth+lineHeight*lineHeight);
    QImage imageRoute((int)width,(int)width,QImage::Format_ARGB8565_Premultiplied);
    imageRoute.fill(Qt::transparent);
    QPainter p(&imageRoute);
    p.translate(imageRoute.width()/2,imageRoute.height()/2);
    p.rotate(this->currentAngle - this->startAngle);
    p.setBrush(Qt::red);
    // p.drawRect(QRect(-this->imageLines->width()/2,-this->imageLines->height()/2,
    //     this->imageLines->width(),this->imageLines->height()));
    p.drawImage(QRect(- this->imageLines->width()/2,- this->imageLines->height()/2,
        this->imageLines->width(),this->imageLines->height()),*this->imageLines);
    p.end();

    painter.drawImage(rectLine,imageRoute);
    painter.setOpacity(1);
    painter.restore();
    //绘制缺口
    int centerX = rectDial.x() + rectDial.width()/2;
    int centerY = rectDial.y() + rectDial.height()/2;
    int circleRadius = rectDial.width()/2 - 50;
    QRectF rectNotch(this->doorRightX + 15,(this->height() - dialSize)/2,64,64);//绘制转盘
    rectNotch.setX(centerX + circleRadius * cos(currentAngle * M_PI/180) - rectNotch.width()/2);
    rectNotch.setY(centerY + circleRadius * sin(currentAngle * M_PI/180) - rectNotch.height()/2);
    rectNotch.setWidth(64);
    rectNotch.setHeight(64);
    painter.drawImage(rectNotch,*this->imageNotch);


     if(this->currentAngle == this->startAngle)
     {
        QRect rectLed(rectIndictor.x() + rectIndictor.width()/2 - 30,rectIndictor.y() + rectIndictor.height()-20,this->imgageLed->width(),this->imgageLed->height());
        painter.setOpacity(ledOpacity);
        painter.drawImage(rectLed,*this->imgageLed);
     }

}

bool LockScreen::eventFilter(QObject *obj, QEvent *e)
{
    static bool isPressed = false,isfirstTouch = false;
    if(e->type() == QEvent::MouseButtonPress)
    {
        QMouseEvent *evt = static_cast<QMouseEvent *>(e);
        if(evt->button() == Qt::LeftButton && this->rectDial.contains(evt->pos()))
        {
            isPressed = true;
        }

    }
    else if(e->type() == QEvent::MouseButtonRelease)
    {
        QMouseEvent *evt = static_cast<QMouseEvent *>(e);
        if(evt->button() == Qt::LeftButton)
        {
            isPressed = false;
            isfirstTouch = false;
        }

    }
    else if(e->type() == QEvent::MouseMove)
    {
        QMouseEvent *evt = static_cast<QMouseEvent *>(e);
        if(isPressed)
        {
            int dx = evt->pos().x() - this->rectDial.center().x();
            int dy = evt->pos().y() - this->rectDial.center().y();

            // 计算角度(弧度)
            float angleRad = atan2(dy, dx);
            float newAngle = angleRad * (180 / M_PI);
            qDebug()<<angleRad<<" "<<newAngle;

            newAngle = normalizeAngle(newAngle);
            if(!isfirstTouch){
                isfirstTouch = true;
                deltaDegrees = newAngle;
                recordAngle = currentAngle;
            }
            else
            {
                currentAngle = clamp(recordAngle + (newAngle - deltaDegrees),this->startAngle,this->endAngle);
            }
            this->update();
            if(currentAngle >= this->endAngle)
                this->lockScreen(false);
            //     root.isClose = false
        }

    }
    return QWidget::eventFilter(obj,e);
}








2.5、使用方法

直接在需要锁屏的界面new一个就可以。
在这里插入图片描述

2.6、完整工程下载

文章顶部下载。

二、实现原理

主要是通过QPainterQPropertyAnimation来实现

1、QPainter详解

1、 QPainter 概述

QPainter 是 Qt 框架中用于执行低级别绘制的核心类。它提供了丰富的功能,允许你在各种绘图设备(如 QPixmap, QImage, QWidget, QPrinter 等)上绘制图形、文本和图像。其设计遵循“画家模式”(Painter’s Model),即在一个“画布”上使用不同的“工具”(画笔、画刷等)进行绘制。

2、核心概念

  • 绘图设备 (QPaintDevice): 这是绘制的目标表面,可以是窗口部件、像素图、图像或打印机页面。
  • 画笔 (QPen): 控制绘制线条(如形状的轮廓线)的属性,包括颜色、宽度、线型(实线、虚线、点线等)、端点样式和连接样式。
    • 示例: pen.setColor(Qt::blue); pen.setWidth(2); pen.setStyle(Qt::DotLine);
  • 画刷 (QBrush): 控制填充形状内部的属性,包括颜色、样式(实心、渐变、纹理等)。
    • 示例: brush.setColor(Qt::green); brush.setStyle(Qt::SolidPattern);
  • 字体 (QFont): 控制绘制文本时使用的字体属性,如字体族、大小、粗细、斜体等。
    • 示例: font.setFamily("Arial"); font.setPointSize(12); font.setBold(true);
  • 变换 (QTransform): 提供坐标系变换功能,包括平移、缩放、旋转和剪切。这允许你在不同的坐标系下绘制相同的图形。
    • 示例: painter.translate(100, 50); painter.rotate(45); painter.scale(2.0, 1.5);
  • 视口 (Viewport) 和 窗口 (Window): 用于逻辑坐标到物理设备坐标的映射。视口定义物理设备上的矩形区域,窗口定义逻辑坐标空间中的矩形区域。QPainter 负责将窗口坐标映射到视口坐标。

3、核心功能

  • 绘制基本图形:
    • drawPoint, drawPoints: 绘制点。
    • drawLine, drawLines: 绘制直线。
    • drawRect, drawRects: 绘制矩形。
    • drawEllipse: 绘制椭圆(包括圆)。
    • drawArc, drawChord, drawPie: 绘制弧线、弦形、扇形。
    • drawPolyline, drawPolygon: 绘制折线、多边形。
    • drawConvexPolygon: 绘制凸多边形(优化性能)。
  • 绘制文本:
    • drawText: 绘制文本字符串。有多种重载形式,可以在指定点、矩形或路径上绘制文本。
    • 示例: painter.drawText(rect, Qt::AlignCenter, "Hello Qt!");
  • 绘制图像:
    • drawImage: 在指定位置绘制 QImage
    • drawPixmap: 在指定位置绘制 QPixmap
    • 示例: painter.drawImage(QPoint(10, 10), myImage);
  • 路径 (QPainterPath): 提供一种创建复杂形状(由直线、曲线和其他路径元素组成)并进行绘制或填充的方式。路径可以复用,并支持布尔运算(并集、交集等)。
    QPainterPath path;
    path.moveTo(20, 80);
    path.lineTo(20, 30);
    path.cubicTo(80, 0, 50, 50, 80, 80); // 绘制贝塞尔曲线
    painter.drawPath(path);
    
  • 渐变填充 (QGradient): 使用线性渐变 (QLinearGradient)、径向渐变 (QRadialGradient) 或锥形渐变 (QConicalGradient) 来填充形状,创建平滑的颜色过渡效果。
    QLinearGradient gradient(0, 0, 100, 100);
    gradient.setColorAt(0.0, Qt::white);
    gradient.setColorAt(1.0, Qt::green);
    QBrush brush(gradient);
    painter.setBrush(brush);
    painter.drawRect(0, 0, 100, 100);
    
  • 合成模式 (CompositionMode): 控制新绘制的像素如何与目标设备上已有的像素进行混合。例如,SourceOver(默认,源覆盖目标)、Source(直接替换)、DestinationIn(目标在源内)等。通过 setCompositionMode() 设置。
  • 反走样 (Antialiasing): 通过 setRenderHint(QPainter::Antialiasing, true) 启用,可以平滑图形的边缘,减少锯齿感,提升视觉质量(但可能略微增加性能开销)。
  • 裁剪区域 (Clipping): 使用 setClipRect(), setClipRegion(), setClipPath() 限制绘制只发生在特定的区域内。通过 setClipping(true/false) 启用或禁用裁剪。

4、 使用模式

  • QWidget::paintEvent 中使用: 这是最常见的用法。在 QWidget 子类的 paintEvent(QPaintEvent *event) 方法中创建 QPainter 对象,并传入 this(指向该部件的指针)作为绘图设备。
    void MyWidget::paintEvent(QPaintEvent *event) {
        QPainter painter(this); // 创建QPainter,目标设备是这个部件
        painter.setPen(...);
        painter.setBrush(...);
        // ... 绘制操作 ...
    } // 析构函数自动调用end(),确保资源释放
    
  • 在图像或像素图上绘制: 创建 QImageQPixmap,然后创建 QPainter 指向它们。绘制完成后,可以保存图像或显示在界面上。
    QImage image(800, 600, QImage::Format_ARGB32);
    image.fill(Qt::white); // 初始填充白色背景
    QPainter painter(&image);
    painter.drawText(...);
    // ... 绘制操作 ...
    painter.end(); // 或者让QPainter析构自动结束
    image.save("output.png");
    
  • 打印: 创建 QPrinter 对象,在打印作业开始后创建 QPainter 指向 QPrinter,绘制内容将被发送到打印机。

5、 重要注意事项

  • 生命周期管理: QPainter 的构造函数 (QPainter(QPaintDevice *)) 会隐式调用 begin()。必须确保在绘图设备有效且准备好绘制时创建 QPainterQPainter 的析构函数会自动调用 end()。如果在同一个设备上需要多次绘制,最好在每次绘制之间显式调用 begin()end()(或让局部 QPainter 对象析构)。
  • 状态保存: save()restore() 方法用于保存和恢复当前的状态(画笔、画刷、字体、变换、视口窗口等)。这在嵌套绘制或需要临时改变状态时非常有用。
    painter.save(); // 保存当前状态
    painter.setPen(Qt::red);
    painter.drawLine(...);
    painter.restore(); // 恢复到保存的状态(原来的画笔)
    
  • 性能: 避免在 paintEvent 外创建 QPainter。尽量减少在 paintEvent 中的计算量。重用预先生成的图像、路径或渐变对象。谨慎使用复杂的变换和反走样。
  • 坐标系统: 默认原点 (0, 0) 在设备的左上角,x轴向右增加,y轴向下增加。使用 translate, scale, rotate, shear 可以改变坐标系。setViewportsetWindow 提供了另一种坐标映射方式。
  • 设备独立性: QPainter 旨在提供设备无关的绘制。相同的绘制代码可以在屏幕、图像或打印机上工作,尽管不同设备的物理特性(如分辨率)可能不同。

6、 示例代码片段 (绘制一个简单的自定义部件)

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

class MyCustomWidget : public QWidget {
    Q_OBJECT
public:
    using QWidget::QWidget;

protected:
    void paintEvent(QPaintEvent *event) override {
        QPainter painter(this); // 针对此部件创建QPainter
        painter.setRenderHint(QPainter::Antialiasing); // 开启反走样

        // 设置画笔和画刷
        QPen pen(Qt::darkBlue, 2);
        painter.setPen(pen);
        QBrush brush(Qt::lightGray);
        painter.setBrush(brush);

        // 绘制一个矩形
        painter.drawRect(10, 10, 100, 50);

        // 改变画笔颜色,绘制一条线
        pen.setColor(Qt::red);
        painter.setPen(pen);
        painter.drawLine(10, 70, 110, 70);

        // 创建一个路径并绘制(一个简单的箭头)
        QPainterPath path;
        path.moveTo(150, 30);
        path.lineTo(180, 30);
        path.lineTo(170, 20);
        path.lineTo(180, 30);
        path.lineTo(170, 40);
        painter.drawPath(path);

        // 绘制文本
        QFont font("Arial", 12);
        painter.setFont(font);
        painter.drawText(50, 100, "Qt 6 QPainter Demo");
    }
};

7、调试技巧

  • 确保 paintEvent 被正确调用(例如,在需要重绘时调用 update())。
  • 检查画笔颜色是否设置正确(忘记设置画笔可能导致图形不可见)。
  • 注意坐标范围是否超出设备边界。
  • 使用 save()restore() 避免状态设置错误的影响范围过大。

QPainter 是 Qt 图形编程的基石。掌握其核心概念、功能和使用模式,能够帮助你创建各种自定义的、高性能的图形界面元素和应用程序。

2、QPropertyAnimation详解

QPropertyAnimation 是 Qt 框架中 QVariantAnimation 类的一个子类。它是 Qt 动画框架的核心组成部分,专门用于对 QObject 或其子类对象的特定属性进行动画插值。简单来说,它能让一个对象的属性(比如位置、大小、颜色、透明度等)在一段时间内平滑地从起始值变化到结束值。

1、 核心概念

  • 目标对象 (targetObject): 你想要施加动画效果的那个 QObject (或其子类) 实例。例如,一个 QWidget, 一个 QGraphicsItem 等。
  • 目标属性 (propertyName): 目标对象中你想要动态改变的属性的名称。这个属性必须是:
    • 通过 Q_PROPERTY 宏声明过的。
    • 拥有读 (READ) 和写 (WRITE) 函数。
    • 其类型能够被 QVariant 表示(大多数 Qt 的基本类型和自定义类型只要注册过都可以)。
  • 起始值 (startValue): 动画开始时属性的值。类型必须与属性类型兼容。
  • 结束值 (endValue): 动画结束时属性的值。类型必须与属性类型兼容。
  • 持续时间 (duration): 动画从开始到结束所持续的时间长度,单位是毫秒 (ms)。
  • 插值器 (easingCurve): 定义动画过程中值变化的速率模式(匀速、加速、减速、弹跳等)。QEasingCurve 类提供了多种预定义的曲线。

2、 工作原理

  • 时间驱动: QPropertyAnimation 内部使用一个计时器。
  • 插值计算: 在动画运行的每一帧(由 Qt 的主事件循环驱动),根据当前动画已进行的时间占总持续时间的比例(一个介于 0.0 到 1.0 的值),结合设置的 easingCurve,计算出当前应该达到的插值值。
  • 属性更新: 将计算出的插值值通过目标对象的属性写函数 (WRITE) 设置回去,从而改变属性的当前值。
  • 信号通知: 在动画开始、结束、状态改变以及每一帧属性值更新时,QPropertyAnimation 会发出相应的信号(如 valueChanged, finished),便于应用程序进行响应。

3、 基本用法

以下是一个使用 QPropertyAnimation 移动一个 QPushButton 的简单示例:

#include <QApplication>
#include <QPushButton>
#include <QPropertyAnimation>

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

    QPushButton button("Animate Me!");
    button.resize(100, 50);
    button.show();

    // 1. 创建动画对象,指定目标对象和属性名
    QPropertyAnimation *animation = new QPropertyAnimation(&button, "geometry");
    // 设置父对象为按钮,方便内存管理 (Qt 的对象树机制)
    animation->setParent(&button);

    // 2. 设置持续时间 (2 秒)
    animation->setDuration(2000);

    // 3. 设置起始值 (按钮的初始位置和大小)
    animation->setStartValue(QRect(0, 0, 100, 50));

    // 4. 设置结束值 (移动到 (200, 150) 位置,大小不变)
    animation->setEndValue(QRect(200, 150, 100, 50));

    // 5. (可选) 设置缓动曲线 (例如,OutBounce 弹跳效果)
    animation->setEasingCurve(QEasingCurve::OutBounce);

    // 6. 启动动画
    animation->start();

    return app.exec();
}

4、 关键成员函数

  • setTargetObject(QObject *object): 设置要动画的目标对象。
  • setPropertyName(const QByteArray &propertyName): 设置要动画的属性名称。
  • setDuration(int msecs): 设置动画持续时间。
  • setStartValue(const QVariant &value): 显式设置动画的起始值。
  • setEndValue(const QVariant &value): 显式设置动画的结束值。
  • setEasingCurve(const QEasingCurve &curve): 设置动画的缓动曲线。
  • start(QAbstractAnimation::DeletionPolicy policy = KeepWhenStopped): 启动动画。
  • stop(): 停止动画。
  • pause(): 暂停动画。
  • resume(): 从暂停状态恢复动画。
  • currentValue() const: 获取当前动画的插值值。
  • state() const: 获取动画的当前状态 (Stopped, Paused, Running)。

5、 高级用法与技巧

  • 自动起始/结束值: 如果不显式调用 setStartValue(),动画启动时会自动使用目标属性的当前值作为起始值。类似地,也可以不调用 setEndValue() 而在运行时通过槽函数动态改变结束值。
  • 关键帧动画: 虽然 QPropertyAnimation 本身只支持两点间的线性插值(加上缓动曲线),但你可以通过 QSequentialAnimationGroupQParallelAnimationGroup 组合多个 QPropertyAnimation 来实现更复杂的序列或并行动画效果。对于更精细的控制,可以使用 QVariantAnimation 并重写 virtual void updateCurrentValue(const QVariant &value) 函数,或者使用 QAnimationDriver (更底层)。
  • 动画组: 使用 QSequentialAnimationGroup (顺序执行) 或 QParallelAnimationGroup (并行执行) 来管理多个动画。
  • 状态改变信号: 连接 stateChanged() 信号可以知道动画何时开始、暂停或停止。连接 finished() 信号知道动画何时正常结束。连接 valueChanged() 信号可以实时获取属性变化的值(注意频率可能很高)。
  • 循环动画: 使用 setLoopCount(int count) 可以设置动画的循环次数。设置为 -1 表示无限循环。
  • 方向: 使用 setDirection(QAbstractAnimation::Direction direction) 可以设置动画是正向 (Forward) 播放还是反向 (Backward) 播放。

6、 注意事项

  • 属性支持: 确保目标属性是用 Q_PROPERTY 声明过的,并且有可用的读写函数。尝试动画一个没有 WRITE 函数或类型不兼容的属性会导致运行时警告或错误。
  • 线程安全: QPropertyAnimation 以及整个 Qt 动画框架都不是线程安全的。动画操作必须在主线程(GUI线程)中进行。
  • 对象生命周期: 确保目标对象在动画执行期间一直有效。通常将动画对象设置为目标对象的子对象(利用 Qt 对象树机制)是一种安全的做法,这样当目标对象被删除时,动画对象也会被自动删除。
  • 性能: 对于非常高频的属性更新(比如每帧更新位置),确保操作本身是高效的,避免在 valueChanged() 的槽函数中进行复杂计算。对于 UI 动画,Qt 通常能很好地处理。
  • 与 QML 的对比: Qt Quick (QML) 有自己更声明式的动画系统 (PropertyAnimation, NumberAnimation 等)。QPropertyAnimation 主要用于 Qt Widgets 模块的 C++ 开发。

7、 总结

QPropertyAnimation 是 Qt Widgets 应用程序中为对象属性添加平滑动画效果的有力工具。通过设置目标对象、属性名、持续时间、起始值、结束值和缓动曲线,你可以轻松创建出各种视觉效果。结合动画组和信号槽机制,可以实现非常复杂的动画交互逻辑。理解其核心概念和正确使用方式是关键。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小灰灰搞电子

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值