前言
今年情人节刚好是过年期间,趁着过年有时间撸点代码,来个程序员的浪漫。去年七夕整了个抽奖的app,这次就整个有浪漫效果的程序就好了
经过在github上一番搜索,找到了一个相对满意的效果,这个效果包括雪花飘落、图片立体旋转、荧光字体闪烁、音乐及字幕播放
没有看人家js是怎么实现,就参照这个效果自己参悟了,利用Qt进行实现,做成客户端的方式进行展示(毕竟对h5和js不熟)
图片立体旋转效果实现
立体效果
先来看一个立方体
再对比立体图片的效果
只需要使围成的图片的上边和下边能够大致组成一个立方体,就能产生立体的效果(这里直接用别人的效果做展示,手动狗头)当然也可以类比成圆柱体,类比成我们熟知的有立体感的基本图形就好理解了
可以看到除了正中间的图片是正的,其他图片的上下两条边都是斜的,这是图片按垂直方向旋转出来的效果
图片在前面的比在后面的大,这也是一个变化
在图片按y轴旋转的过程中图片的位置也在变化,x和y都进行变化,可以近似看成是走一个椭圆的轨迹
立体原理
综上所描述的,归结为三个要点来实现
- 图片元素按y轴旋转的动画
- 图片元素大小变化的动画
- 图片元素进行椭圆轨迹的动画
三个动画效果同时运行可大致实现这个立体旋转效果
至于使用OpenGL等框架来实现3D效果,咱也没研究过,只能利用自己现有的知识来实现了,毕竟这玩意不是做项目,娱乐娱乐
代码展示
该效果使用QGraphics体系进行实现,使用paint图片也可以
图片元素使用QGraphicsPixmapItem
#ifndef PIXITEM_H
#define PIXITEM_H
#include <QGraphicsPixmapItem>
class QTimer;
class QParallelAnimationGroup;
class MyAnimation;
class PixItem : public QObject, public QGraphicsPixmapItem
{
Q_OBJECT
Q_PROPERTY(QPointF pos READ pos WRITE setPos)
Q_PROPERTY(qreal scale READ scale WRITE setScale)
Q_PROPERTY(qreal rotate READ getRotate WRITE setRotate)
// Q_PROPERTY(qreal ZValue READ setZValue WRITE zValue)
public:
explicit PixItem(QGraphicsItem *parent = nullptr);
void start();
void setRotate(qreal val);
qreal getRotate() const;
private:
QTimer *m_pTimer = nullptr;
qreal m_rotate = 0;
QParallelAnimationGroup *m_pAnimationGroup = nullptr;
};
#endif // PIXITEM_H
#include "pixitem.h"
#include <QTimer>
#include <QPropertyAnimation>
#include <QParallelAnimationGroup>
#include <QSequentialAnimationGroup>
#include "myanimation.h"
#include <QDebug>
PixItem::PixItem(QGraphicsItem *parent):
QGraphicsPixmapItem (parent)
{
QPixmap pix(":/resource/11.jpg");
pix = pix.scaled(pix.size()*2/5, Qt::KeepAspectRatio, Qt::SmoothTransformation);
this->setPixmap(pix);
//初始z值
this->setZValue(10);
//路径动画走椭圆
MyAnimation *pAnimation = new MyAnimation(this, "pos", this);
pAnimation->setStartValue(QPointF(300, 200));
pAnimation->setEndValue(QPointF(1100, 280));
pAnimation->setDuration(27500);
pAnimation->setPathType(MyAnimation::CirclePath);
//旋转动画,旋转360度
QPropertyAnimation *pRotateAnimation = new QPropertyAnimation(this, "rotate", this);
pRotateAnimation->setDuration(29000);
pRotateAnimation->setStartValue(270);
pRotateAnimation->setEndValue(630);
//缩放动画,不同进度不同的缩放大小
QPropertyAnimation *pScaleAnimation = new QPropertyAnimation(this, "scale", this);
pScaleAnimation->setDuration(29000);
pScaleAnimation->setKeyValueAt(0, 1);
pScaleAnimation->setKeyValueAt(0.25, 1);
pScaleAnimation->setKeyValueAt(0.5, 0.8);
pScaleAnimation->setKeyValueAt(0.75, 0.75);
pScaleAnimation->setKeyValueAt(1, 1);
//并行动画组
m_pAnimationGroup = new QParallelAnimationGroup(this);
m_pAnimationGroup->addAnimation(pAnimation);
m_pAnimationGroup->addAnimation(pRotateAnimation);
m_pAnimationGroup->addAnimation(pScaleAnimation);
m_pAnimationGroup->setLoopCount(-1);
}
void PixItem::start()
{
m_pAnimationGroup->start();
}
void PixItem::setRotate(qreal val)
{
QRectF rect = this->boundingRect();
m_rotate = val;
//以y轴进行旋转,位置为负的rect.height()/4
QTransform transform;
transform.translate(rect.x(), rect.y() - rect.height()/4);
transform.rotate(m_rotate, Qt::YAxis);
transform.translate(-rect.x(), -rect.y() + rect.height()/4);
this->setTransform(transform);
// qDebug() << __FUNCTION__ << "song" << "rotate" << m_rotate;
//转到后面z值变小,不然会档到前面
if(m_rotate > 450 && m_rotate < 630) {
this->setZValue(1);
}
else {
this->setZValue(10);
}
}
qreal PixItem::getRotate() const
{
return m_rotate;
}
旋转动画
QGraphicsItem实现动画常规方法是使用QGraphicsItemAnimation实现,QGraphicsItemAnimation又用不了QParallelAnimationGroup动画组
我们可以通过自定义属性的方式使QGraphicsItem也能使用QPropertyAnimation实现动画,使用Q_PROPERTY宏来声明rotate属性,自定义setRotate和getRotate实现接口,这样就可以通过QPropertyAnimation实现动画了,这样就能够使用QParallelAnimationGroup动画并行组,以便实现三个动画同时运行
以y轴为旋转轴,旋转中心为负的rect.height()/4,以便达到图片的上边在旋转的过程中形成的斜角度符合立方体的角度,具体效果可以自行尝试
图片移动到立体效果的后面时,需要调整item的z值,以免出现后面的图片挡住前面的图片
路径动画
走椭圆的动画使用的是自定义的QPropertyAnimation,尝试走直线的方式效果不好,刚好之前研究过走自定义路径的动画,刚好派上用场,相关代码可以看我写的这篇博客
缩放动画
前大后小,设置不同的进度对应不同的大小
使用QParallelAnimationGroup动画并行组,实现三个QPropertyAnimation动画同时运行
看着代码量不多,用到的Qt的知识点还是挺多的
scene的代码
#include "myscene.h"
#include <QGraphicsPixmapItem>
#include "pixitem.h"
#include <QTimer>
#include <QDebug>
MyScene::MyScene(QObject *parent):
QGraphicsScene (parent)
{
for(int i = 0; i < 8; ++i) {
PixItem *pixItem = new PixItem;
this->addItem(pixItem);
m_itemList.append(pixItem);
}
}
MyScene::MyScene(const QRectF &sceneRect, QObject *parent):
QGraphicsScene (sceneRect, parent)
{
for(int i = 0; i < 8; ++i) {
PixItem *pixItem = new PixItem;
this->addItem(pixItem);
pixItem->setVisible(false);
m_itemList.append(pixItem);
}
}
void MyScene::start()
{
for(int i = 0; i < m_itemList.size(); ++i) {
//延时启动
QTimer::singleShot(3580*i, this, [=]{
m_itemList.at(i)->setVisible(true);
m_itemList.at(i)->start();
qDebug() << __FUNCTION__ << "song" << m_itemList.at(i)->boundingRect();
});
}
}
创建八张图,并隔一段时间开启动画
总的代码量不多,实现粗略的效果
demo运行效果
完整代码下载
https://download.csdn.net/download/a137748099/15453292
整个例子还包括雪花效果和艺术字
雪花效果和艺术字用的网上的代码,所以整体代码风格会不统一,将就着看吧,完整代码放在github,有需要可以再加音乐播放和艺术字闪烁,整个效果就更完美了,拿来表白还不是手到擒来,哈哈哈哈哈哈
https://github.com/a137748099/Valentine-s-day-animation-effect-
补充
最近发现一个好东西,Qt for WebAssembly能够实现浏览器运行Qt程序,试着把这个例子编译了在浏览器上运行,发现是OK的。虽然咱不会web前端,但我们仍然可以通过浏览器展示我们写的Qt界面,是不是更加方便表白了,哈哈哈哈哈。Qt for WebAssembly的使用可以翻看我的博客。