描述:
有时候ui给我们的动画是一序列的图片,播放动画需要一张一张图片读取,显得有点麻烦,存储的资源目录也显得比较凌乱。为解决这个问题,又不想使用gif,可以使用如下自定义序列帧播放组件,一次读取,通过计算当前帧的位置从内存读取当前帧。可以控制顺时针/逆时针播放、是否循环、播放每一帧的事件间隔。希望对大家有用。
.hpp文件
#include <QWidget>
#include <QPointer>
class QTimer;
class FramePalyWidget : public QWidget{
Q_OBJECT
public:
explicit FramePalyWidget (QWidget* parent = nullptr);
virtual ~FramePalyWidget ();
/**
************************************************************
* @brief: 设置动画图标
* @param[in]: 图标实例
* @param[in]: 图标实例动画帧数
* @return:
************************************************************
*/
void SetAnimation(const QPixmap& pix, int count = 1);
/**
************************************************************
* @brief: 设置定时器时间间隔(毫秒级)
************************************************************
*/
void SetInterval(int msec);
/**
************************************************************
* @brief: 开始动画
************************************************************
*/
void Start();
/**
************************************************************
* @brief: 停止动画
************************************************************
*/
void Stop();
/**
************************************************************
* @brief: 设置是否循环播放
* @param[in]: isLoop : true 循环播放, false 不循环播放
************************************************************
*/
void SetLoop(bool isLoop);
/**
************************************************************
* @brief: 设置是否顺时针播放
* @param[in]: bClockwise: true 顺时针, false: 逆时针
************************************************************
*/
void SetClockwise(bool bClockwise);
protected:
/**
************************************************************
* @brief: 初始化ui
************************************************************
*/
void InitUi();
/**
************************************************************
* @brief: 信号绑定
************************************************************
*/
void ConnectSignals();
signals:
/**
************************************************************
* @brief: 播放完成信号
************************************************************
*/
void sigPlayFinished();
private slots:
/**
************************************************************
* @brief: 更新帧槽函数
************************************************************
*/
void slotUpdateFrame();
protected:
void paintEvent(QPaintEvent*) override;
private:
bool m_bClockwise; // 是否顺时针播放
bool m_bIsLoop; // 是否循环
int m_nFrameCount; // 帧数
int m_nCurrentIndex; // 当前展示图片下标
int m_nInterval; // 时间间隔(毫秒)
QPointer<QTimer> m_pClockTimer; // 更新定时器
QPixmap m_currentPix; // 当前帧
QPixmap m_originalPix; // 原始位图
QList<QPixmap> m_pixList; // 位图列表
};
.cpp文件
#include "FramePalyWidget.hpp"
#include <QTimer>
#include <QPainter>
FramePalyWidget::FramePalyWidget(QWidget* parent)
: QWidget(parent)
, m_bIsLoop(true)
, m_bClockwise(true){
InitUi();
ConnectSignals();
}
FramePalyWidget::~FramePalyWidget() {
}
/**
************************************************************
* @brief: 初始化ui
************************************************************
*/
void FramePalyWidget::InitUi(){
m_pClockTimer = new QTimer(this);
m_pClockTimer->setInterval(10);
}
/**
************************************************************
* @brief: 信号绑定
************************************************************
*/
void FramePalyWidget::ConnectSignals() {
connect(m_pClockTimer, SIGNAL(timeout()), this, SLOT(slotUpdateFrame()));
}
/**
************************************************************
* @brief: 设置定时器时间间隔(毫秒级)
************************************************************
*/
void FramePalyWidget::SetInterval(int msec) {
if (m_pClockTimer) {
m_pClockTimer->setInterval(msec);
}
}
/**
************************************************************
* @brief: 设置动画图标
* @param[in]: 图标实例
* @param[in]: 图标实例动画帧数
* @param[out]: 动画切帧速度 (毫秒级)
* @return:
************************************************************
*/
void FramePalyWidget::SetAnimation(const QPixmap& pix, int count) {
m_nFrameCount = count;
m_nCurrentIndex = 0;
if(m_nFrameCount <= 0) {
return;
}
if (!m_pixList.empty()) {
m_pixList.clear();
}
if(m_pClockTimer->isActive()){
m_pClockTimer->stop();
}
// 帧获取
for (short i = 0; i < count; ++i) {
m_pixList.append(pix.copy(i * (pix.width() / count), 0,
pix.width() / count, pix.height()));
}
m_currentPix = m_pixList.at(0);
update();
}
/**
************************************************************
* @brief: 开始动画
************************************************************
*/
void FramePalyWidget::Start() {
if(m_pClockTimer){
if (m_pClockTimer->isActive()) {
m_pClockTimer->stop();
}
m_pClockTimer->start();
}
}
/**
************************************************************
* @brief: 停止动画
************************************************************
*/
void FramePalyWidget::Stop() {
if (m_pClockTimer) {
m_pClockTimer->stop();
}
}
/**
************************************************************
* @brief: 设置是否循环播放
* @param[in]: isLoop : true 循环播放, false 不循环播放
************************************************************
*/
void FramePalyWidget::SetLoop(bool isLoop) {
m_bIsLoop = isLoop;
}
/**
************************************************************
* @brief: 设置是否顺时针播放
* @param[in]: bClockwise: true 顺时针, false: 逆时针
************************************************************
*/
void FramePalyWidget::SetClockwise(bool bClockwise) {
m_bClockwise = bClockwise;
}
/**
************************************************************
* @brief: 更新帧槽函数
************************************************************
*/
void FramePalyWidget::slotUpdateFrame() {
if (m_nCurrentIndex < m_nFrameCount && m_nCurrentIndex >= 0) {
// 更新帧
m_currentPix = m_pixList.at(m_nCurrentIndex);
update();
if (m_bClockwise) {
// 判断帧数
if (m_nCurrentIndex >= (m_nFrameCount - 1)) {
if (m_bIsLoop) {
m_nCurrentIndex = 0;
return;
}
m_pClockTimer->stop();
m_nCurrentIndex = 0;
emit sigPlayFinished();
return;
}
// 跳帧
++m_nCurrentIndex;
}
else {
// 判断帧数
if (m_nCurrentIndex <= 0) {
if (m_bIsLoop) {
m_nCurrentIndex = m_nFrameCount - 1;
return;
}
m_pClockTimer->stop();
m_nCurrentIndex = m_nFrameCount - 1 ;
emit sigPlayFinished();
return;
}
// 跳帧
--m_nCurrentIndex;
}
}
}
void FramePalyWidget::paintEvent(QPaintEvent*) {
QPainter painter(this);
painter.drawPixmap(rect(), m_currentPix);
}
使用样例:
FramePalyWidget* m_pTestAnimal = new FramePalyWidget(this);
m_pTestAnimal ->setFixedSize(70, 140);
m_pTestAnimal ->SetAnimation(QPixmap(":res/img/test/test.png"), 50);
m_pTestAnimal ->SetClockwise(true); // 顺时针读取
m_pTestAnimal ->SetInterval(40); // 帧读取事件间隔
m_pTestAnimal ->SetLoop(false); // 是否循环
connect(m_pTestAnimal , &FramePalyWidget::sigPlayFinished,
this, [&](){
// TODO finished
});
序列帧合成地址:
地址1:https://www.toptal.com/developers/css/sprite-generator
地址2:https://cdkm.com/cn/merge-image