【活动】为中秋献上祝福

        又是一年中秋,作为一名程序员,接下来会以程序的方式,为即将到来的中秋节献上美好的祝福。

中秋的发展

        中秋节起源于上古时代,当时的人们出于对天象的崇拜,而演化出祭月、拜月等祭祀活动,最早见于文件《周礼》,先秦已有“中秋夜迎寒”、“中秋献良裘”、“秋分夕月(拜月)”的活动。在汉代时出现一些中秋活动,而在唐代时已普及成为了全国性节日,在北宋时期正式认定阴历八月十五为中秋节,在明清时期节日氛围愈加浓厚。而如今,在中秋吃月饼,走亲访友仍是必不可少的风俗。自2007年12月7日起,国务院通过清明节,端午节,中秋节作为中国法定假日,也表现出了国家对于传统节日的重视。

中秋的别称

        中秋节,又称祭月节、月光诞、月夕、秋节、仲秋节、拜月节、月娘节、月亮节、团圆节等,是中国民间的传统节日。中秋节源自天象崇拜,由上古时代秋夕祭月演变而来。中秋节自古便有祭月、赏月、吃月饼、看花灯、赏桂花、饮桂花酒等民俗,流传至今,经久不息。

为中秋献上祝福

        写个小程序,为中秋节献上祝福。接下来我会用Qt Creator制作一个滚动的背景相册,然后加入一些诗句来修饰点缀。

让中秋背景动起来

        这里是第一阶段的运行结果,首先在准备2张中秋背景图,确定动画效果,然后开始写小部件。

       

1.建立工程,添加图片资源

        新建名为image_carousel的Qt Widgets Application工程。然后在工程名上右键点击添加新文件,选择Qt - Qt Resource File新建qrc文件,然后在资源编辑器中设置前缀和添加准备好的文件。

2.完成基础接口 - 添加图片、设置尺寸

//添加背景图
void ImageCarouselWidget::addImage(QImage image)
{
    list_image.append(image);
}

//设置图片尺寸
void ImageCarouselWidget::setImageSize(QSize size)
{
    m_image_size = size;
    for(int i = 0; i < list_image.size(); i++){
        list_image[i].scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
    }
}

        外部调用方法:

             widget.addImage(QImage(":/image/mid_autumn_1.jpeg"));

             widget.setImageSize(QSize(600,225));

        其中setImageSize接口中的scaled为QImage的方法,目的是为了将图片拉伸为当前设置的尺寸,这个尺寸要根据资源来看,尽量比例接近。Qt::IgnoreAspectRatio参数是默认值,目的是将图片进行完整的进行拉伸,非裁剪丢失部分图片。Qt::SmoothTransformation表示使用双线性滤波对得到的图像进行变换,即平滑变换,  与Qt::FastTransformation快速变换不同,平滑变换能减小失真,但是对于质量过高的图片效率很低。

3.设置动画

        在构造函数中初始化:

    //默认值
    m_animation = new QPropertyAnimation(this, "image_pos", this);
    m_animation->setDuration(1000);
    m_animation->setStartValue(0);
    m_animation->setEndValue(-300);
    m_animation->setEasingCurve(QEasingCurve::InOutCubic);

        修改设置图片尺寸接口:

//设置图片尺寸
void ImageCarouselWidget::setImageSize(QSize size)
{
    m_image_size = size;
    for(int i = 0; i < list_image.size(); i++){
        list_image[i].scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
    }

    //设定动画的起始值、结束值
    m_animation->setStartValue(0);
    m_animation->setEndValue(-1 * m_image_size.width());
}

        在头文件中用Q_PROPERTY宏来定义动画属性:

    Q_PROPERTY(int image_pos READ getImagePos WRITE setImagePos)

        其中image_pos为动画属性名称,getImagePos与setImagePos为读取属性、设置属性接口,会被动画所调用。图片坐标读取、设置接口:

int ImageCarouselWidget::getImagePos() const
{
    return m_image_pos;
}

void ImageCarouselWidget::setImagePos(const int image_pos)
{
    m_image_pos = image_pos;
    update();
}

        在setImagePos接口中,调用update的目的是为了刷新绘画事件接口(paintEvent) 。

4.绘画事件

        动画的属性(图片x坐标)、开始值到结束值(0 ~ 图片宽度的负数值,目的是为了移动到左侧来让图片消失)、动画持续时间(1000ms)、动画曲线(EasingCurve)均已设置完毕。那么接下来开始实现绘画事件接口:

void ImageCarouselWidget::paintEvent(QPaintEvent*)
{
    QPainter painter(this);

    int cur_index = m_index;
    int next_index = m_index + 1;

    if (next_index >= list_image.count()) {
        next_index = 0;
    }

    painter.drawImage(QRect(QPoint(m_image_pos,0), m_image_size), list_image[cur_index]);
    painter.drawImage(QRect(QPoint(m_image_size.width() + m_image_pos,0), m_image_size), list_image[next_index]);
}

        在绘画事件中计算当前索引与下一个背景的索引,然后分别绘制2个背景图。在动画set接口中的update的调用之下,paintEvent会随之呼应,就会达到让图片连起来的移动效果。

5.开启动画

void ImageCarouselWidget::startAnimation()
{
    if (m_animation->state() == QPropertyAnimation::Running) {
        return;
    }else if (m_animation->state() == QPropertyAnimation::Stopped){
        m_index++;
        if (m_index >= list_image.count()) {
            m_index = 0;
        }
    }
    if (!isVisible()){
        show();
    }
    m_animation->start();
    QTimer::singleShot(1100, this, [=](){startAnimation();});
}

         当动画结束时,索引指向下一个背景图,然后开始执行动画,动画持续事件为1秒,使用QTimer定时1.1秒之后再次执行动画,用来达到背景轮播的效果。

点缀中秋氛围感

        接下来开始第二阶段的开发 - 加入诗句,先改动一下addImage接口,原来的参数只有QImage,先定义参数结构:

typedef struct {
   QImage image;
   QString poem;
   QPoint pos_poem;
} bg_info;

        将addImage接口改动一下:

void ImageCarouselWidget::addImage(bg_info info)
{
    list_image.append(info);
}

         接着改动绘画事件接口:

void ImageCarouselWidget::paintEvent(QPaintEvent*)
{
    QPainter painter(this);

    int cur_index = m_index;
    int next_index = m_index + 1;

    if (next_index >= list_image.count()) {
        next_index = 0;
    }

    //画背景
    painter.drawImage(QRect(QPoint(m_image_pos,0), m_image_size), list_image[cur_index].image);
    painter.drawImage(QRect(QPoint(m_image_size.width() + m_image_pos,0), m_image_size), list_image[next_index].image);

    //设置绘画字体
    painter.setFont(m_font);

    //计算第一幅背景的坐标
    int pos_x1 = list_image[cur_index].pos_poem.x();
    int pos_y1 = list_image[cur_index].pos_poem.y();
    pos_x1 =  m_image_pos + pos_x1;

    //画文字
    painter.drawText(QRect(pos_x1, pos_y1, 400, 120), Qt::AlignTop,  list_image[cur_index].poem);

    //计算下一幅背景的坐标
    int pos_x2 = list_image[next_index].pos_poem.x();
    int pos_y2 = list_image[next_index].pos_poem.y();
    pos_x2 =  m_image_size.width() + m_image_pos + pos_x2;

    //画文字
    painter.drawText(QRect(pos_x2, pos_y2, 400, 120), Qt::AlignTop,  list_image[next_index].poem);
}

        在startAnimation接口中,调整一下定时器的间隔时间为1500ms,目的是让绘画时间充足,不然无法实现轮播效果:

QTimer::singleShot(1500, this, [=](){startAnimation();});

第二阶段执行结果:  

    

源码分享

imagecarouselwidget.h
#ifndef IMAGECAROUSELWIDGET_H
#define IMAGECAROUSELWIDGET_H

#include <QWidget>
#include <QPainter>
#include <QPaintEvent>
#include <QImage>
#include <QPropertyAnimation>
#include <QTimer>
#include <QFont>

namespace Ui {
class ImageCarouselWidget;
}

typedef struct {
   QImage image;
   QString poem;
   QPoint pos_poem;
} bg_info;

class ImageCarouselWidget : public QWidget
{
    Q_OBJECT
    Q_PROPERTY(int image_pos READ getImagePos WRITE setImagePos)

public:
    explicit ImageCarouselWidget(QWidget *parent = nullptr);
    ~ImageCarouselWidget();

    void addImage(bg_info info);
    void setImageSize(QSize size);
    void startAnimation();

private:
    int getImagePos() const;
    void setImagePos(const int image_pos);

protected:
    void paintEvent(QPaintEvent*);

private:
    Ui::ImageCarouselWidget *ui;

    QList<bg_info> list_image;
    QSize m_image_size;

    //图片向左滚动动画
    QPropertyAnimation* m_animation;
    int m_image_pos;

    //标记索引
    int m_index;

    //字体
    QFont m_font;
};

#endif // IMAGECAROUSELWIDGET_H

imagecarouselwidget.cpp

#include "imagecarouselwidget.h"
#include "ui_imagecarouselwidget.h"

#include <QFontDatabase>
#include <QDebug>

ImageCarouselWidget::ImageCarouselWidget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::ImageCarouselWidget),
    m_animation(NULL),
    m_image_pos(0),
    m_index(0)
{
    ui->setupUi(this);

    //默认值
    m_animation = new QPropertyAnimation(this, "image_pos", this);
    m_animation->setDuration(1000);
    m_animation->setStartValue(0);
    m_animation->setEndValue(-300);
    m_animation->setEasingCurve(QEasingCurve::InOutCubic);

    //设置字体
    int fontId = QFontDatabase::addApplicationFont(":/image/WangZhiShuFaZiTi-2.ttf");
    if(fontId >= 0){
        QString family = QFontDatabase::applicationFontFamilies(fontId).at(0);
        m_font.setFamily(family);
        m_font.setPointSize(24);
        m_font.setBold(true);
    } else {
        //使用默认字体
        m_font = qApp->font();
    }
}

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

//添加背景图
void ImageCarouselWidget::addImage(bg_info info)
{
    list_image.append(info);
}

//设置图片尺寸
void ImageCarouselWidget::setImageSize(QSize size)
{
    m_image_size = size;
    //将所有的图片缩放到合适的尺寸
    for(int i = 0; i < list_image.size(); i++){
        list_image[i].image.scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
    }

    //设定动画的起始值、结束值
    m_animation->setStartValue(0);
    m_animation->setEndValue(-1 * m_image_size.width());
}

//动画属性get接口
int ImageCarouselWidget::getImagePos() const
{
    return m_image_pos;
}

//动画属性set接口
void ImageCarouselWidget::setImagePos(const int image_pos)
{
    m_image_pos = image_pos;
    update();
}

//开始执行动画
void ImageCarouselWidget::startAnimation()
{
    if (m_animation->state() == QPropertyAnimation::Running) {
        return;
    }else if (m_animation->state() == QPropertyAnimation::Stopped){
        m_index++;
        if (m_index >= list_image.count()) {
            m_index = 0;
        }
    }
    if (!isVisible()){
        show();
    }

    m_animation->start();
    QTimer::singleShot(1500, this, [=](){startAnimation();});
}

//绘画事件
void ImageCarouselWidget::paintEvent(QPaintEvent*)
{
    QPainter painter(this);

    int cur_index = m_index;
    int next_index = m_index + 1;

    if (next_index >= list_image.count()) {
        next_index = 0;
    }

    //画背景
    painter.drawImage(QRect(QPoint(m_image_pos,0), m_image_size), list_image[cur_index].image);
    painter.drawImage(QRect(QPoint(m_image_size.width() + m_image_pos,0), m_image_size), list_image[next_index].image);

    //设置绘画字体
    painter.setFont(m_font);

    //计算第一幅背景的坐标
    int pos_x1 = list_image[cur_index].pos_poem.x();
    int pos_y1 = list_image[cur_index].pos_poem.y();
    pos_x1 =  m_image_pos + pos_x1;

    //画文字
    painter.drawText(QRect(pos_x1, pos_y1, 400, 120), Qt::AlignTop,  list_image[cur_index].poem);

    //计算下一幅背景的坐标
    int pos_x2 = list_image[next_index].pos_poem.x();
    int pos_y2 = list_image[next_index].pos_poem.y();
    pos_x2 =  m_image_size.width() + m_image_pos + pos_x2;

    //画文字
    painter.drawText(QRect(pos_x2, pos_y2, 400, 120), Qt::AlignTop,  list_image[next_index].poem);
}

main.cpp

#include <QApplication>
#include "imagecarouselwidget.h"

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

    ImageCarouselWidget widget;

    bg_info info1;
    info1.image = QImage(":/image/mid_autumn_1.jpeg");
    info1.poem = "海上生明月 \n天涯共此时";
    info1.pos_poem = QPoint(90,30);
    widget.addImage(info1);

    bg_info info2;
    info2.image = QImage(":/image/mid_autumn_2.jpeg");
    info2.poem = "但愿人长久 \n千里共婵娟";
    info2.pos_poem = QPoint(190,30);
    widget.addImage(info2);


    widget.setImageSize(QSize(600,225));
    widget.setFixedSize(600, 225);
    widget.startAnimation();

    return a.exec();
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Quz

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

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

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

打赏作者

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

抵扣说明:

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

余额充值