1.效果展示
2.简述
模仿一个下面的语音动画,看起来肯定得用贝塞尔曲线,先用Ps钢笔勾出来这个轮廓,研究一下变大变小是什么参数在动。
一截贝塞尔曲线有三个参数,两个手柄和一个锚点,这种形状的规律是手柄1的x坐标是固定的,根据声音值的大小,手柄1的y坐标是上下浮动的,我们把数值定为0~100的浮动。而锚点和手柄2是相同的坐标,这样就很好计算了。只有一个变量,就是手柄1跟着声音大小变就行了。
但是siri每个波的周期还不是一样的,那我们就给一个随机的X偏移,这样看起来就灵动些。
3.代码
工程下载链接https://mp.csdn.net/console/upDetailed
测试用例
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
m_pTimer = new QTimer(this);
connect(m_pTimer,&QTimer::timeout,[=]{
qsrand(QTime::currentTime().msec());
QVector<int> vecData;
vecData.append(2+qrand() % 8);
for(int i = 0; i < 3; i++){
vecData.append(qrand() % 80); //0~100之间都行
}
vecData.append(2+qrand() % 8);
ui->pSonicAnmation->setData(vecData); //模拟声波数据 值在0~100的范围
});
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_pushButton_clicked()
{
}
void MainWindow::on_pushButton_clicked(bool checked)
{
if(checked){
ui->pushButton->setText("停止");
m_pTimer->start(200);
}else{
ui->pushButton->setText("开始");
m_pTimer->stop();
}
}
CSonicAnmation.cpp
#include "CSonicAnmation.h"
#include <QDebug>
#include <QTime>
CSonicAnmation::CSonicAnmation(QWidget *parent) : QWidget(parent)
{
qsrand(QTime::currentTime().msec()); //随机种子 为随机颜色做准备
}
void CSonicAnmation::setData(QVector<int> &vecData)
{
if(m_vecSonic.size() > 2){
m_vecSonic.takeLast();
}
m_vecSonic.append(vecData);
update();
}
void CSonicAnmation::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event)
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing,true); //开启抗锯齿
//描边画笔
painter.setPen(QPen(QBrush(),0));
//00坐标变换到左侧中间位置
painter.translate(0,height()/2);
// painter.drawPoint(0,0); 画坐标原点
foreach (QVector<int> vecData, m_vecSonic) {
painter.setBrush(QBrush(randLineGradient())); //随机渐变填充
painter.drawPath(calcCublic(vecData,painter));
}
}
QPainterPath CSonicAnmation::calcCublic(QVector<int> vecData, QPainter &painter)
{
Q_UNUSED(painter) //调试画锚点坐标用到
m_nSpan = this->width() / vecData.size(); //计算每一节的宽度
//计算手柄和锚点坐标
QVector<StCubic> vecCubic;
vecCubic.resize(vecData.size()*2);
for (int i = 0; i < vecData.count(); i++) {
//手柄1坐标
QPointF ptHandle(m_nSpan * (i+1) - m_nSpan /2,((double)vecData.at(i)/100.00) * this->height());//第一个手柄 另一个手柄等于锚点
QPointF ptAnchor(m_nSpan * (i+1),0); //锚点
vecCubic[i] = StCubic(ptHandle,ptAnchor,ptAnchor);
// qDebug()<<"手柄D:"<<ptHandle<<"锚点D:"<<i<<ptAnchor;
//锚点坐标
ptHandle.setY(-ptHandle.y());
ptAnchor.setX(ptAnchor.x() - m_nSpan);
vecCubic[vecCubic.size() -1 - i] = StCubic(ptHandle,ptAnchor,ptAnchor);//手柄2坐标和锚点相同
// qDebug()<<"手柄U:"<<ptHandle<<"锚点U:"<<vecCubic.size() -1 - i<<ptAnchor;
}
randX(vecCubic);
//勾勒贝塞尔曲线
QPainterPath pathSonic(QPoint(0,0));
for(int i = 0; i < vecCubic.size(); i++) {
StCubic stCubic = vecCubic.at(i);
pathSonic.cubicTo(stCubic.ptfHandle1,stCubic.ptfAnchor,stCubic.ptfAnchor);
//调试显示手柄和锚点 不要删除
// painter.drawText(stCubic.ptfHandle1,QString("手柄%1").arg(i));
// painter.drawEllipse(stCubic.ptfHandle1,3,3);
// QPointF ptf(stCubic.ptfAnchor);
// ptf.setY(i < m_vecCubic.size()/2 ? -10 : 10);
// painter.drawText(ptf,QString("锚点 %1").arg(i));
// painter.drawRect(QRect(stCubic.ptfAnchor.toPoint(),QSize(3,3)));
}
return pathSonic;
}
void CSonicAnmation::randX(QVector<StCubic> &vecCubic)
{
qsrand(QTime::currentTime().msec());
for(int i = 0; i < vecCubic.size()/2 - 1; i++){
//让锚点x坐标保持一定的随机性 看起来不那么机械
int nOffsetX = (qrand() % m_nSpan) * 0.4; //X坐标随机偏移40% 可自由设置
nOffsetX = nOffsetX & 1 ? -nOffsetX : nOffsetX;
qDebug()<<"随机数:"<<nOffsetX;
//设置对应的锚点随机偏移
int nIndexDown = vecCubic.size() - (i+2);
int nIndexUp = i;
vecCubic[nIndexDown].ptfAnchor.setX( vecCubic[nIndexDown].ptfAnchor.x() + nOffsetX);
vecCubic[nIndexUp].ptfAnchor.setX(vecCubic[nIndexUp].ptfAnchor.x() + nOffsetX);
qDebug()<<"锚点Down:"<<nIndexDown<<"="<<"锚点Up:"<<nIndexUp;
}
}
QColor CSonicAnmation::randColor()
{
// int nS = qrand() % 255; //随机透明度
// int nR = qrand() % 255; //随机红色
// int nG = qrand() % 255; //随机绿色
// int nB = qrand() & 255; //随机蓝色
// return QColor(nS,nR,nG,nB);
//随机浅色
QColor colorRand;
colorRand.setHsv(qrand() % 360,255,255,150); //随机色相 固定饱和度、明度、透明度 保持颜色为浅色
return colorRand;
}
QLinearGradient CSonicAnmation::randLineGradient()
{
QLinearGradient linearGradient(0, 0, this->width(), 0);//初始化,设置开始和结束点
linearGradient.setColorAt(0.0, randColor());
linearGradient.setColorAt(0.5, randColor());
linearGradient.setColorAt(1.0, randColor());
return linearGradient;
}
CSonicAnmation.h
#ifndef CSONICANMATION_H
#define CSONICANMATION_H
#include <QWidget>
#include <QPainter>
#include <QPaintEvent>
#include <QPainterPath>
#include <QQueue>
struct StCubic{
StCubic(){}
StCubic(QPointF &ptfHandle1,QPointF &ptfAnchor,QPointF &ptfHandle2)
{
this->ptfHandle1 = ptfHandle1;
this->ptfAnchor = ptfAnchor;
this->ptfHandle2 = ptfHandle2;
}
QPointF ptfHandle1;
QPointF ptfAnchor;
QPointF ptfHandle2; //暂时没用到
};
class CSonicAnmation : public QWidget
{
Q_OBJECT
public:
explicit CSonicAnmation(QWidget *parent = 0);
void setData(QVector<int> &vecData); //设置声波数据 建议五个数即可 数越多波浪越多
protected:
virtual void paintEvent(QPaintEvent *event);
private:
QPainterPath calcCublic(QVector<int> vecData,QPainter &painter);
void randX(QVector<StCubic> &vecCubic); //X坐标随机处理
QColor randColor();
QLinearGradient randLineGradient();
private:
QVector<QVector<int>> m_vecSonic; //存储几行声波数据 目前3层
int m_nSpan; //每个波所占的宽度
#endif // CSONICANMATION_H