又是一年中秋,作为一名程序员,接下来会以程序的方式,为即将到来的中秋节献上美好的祝福。
中秋的发展
中秋节起源于上古时代,当时的人们出于对天象的崇拜,而演化出祭月、拜月等祭祀活动,最早见于文件《周礼》,先秦已有“中秋夜迎寒”、“中秋献良裘”、“秋分夕月(拜月)”的活动。在汉代时出现一些中秋活动,而在唐代时已普及成为了全国性节日,在北宋时期正式认定阴历八月十五为中秋节,在明清时期节日氛围愈加浓厚。而如今,在中秋吃月饼,走亲访友仍是必不可少的风俗。自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();
}