最后了!
是这样,大佬们查看了框架,然后自己简化了一套出来,融合进了代码。
而我,跟随这这套框架继续拧螺丝,开始压根不用管框架做了什么,直接调用即可。
现在慢慢理解框架,看看框架处理了什么东西吧。
当然,在运行完整的demo和运行自己随便添加效果的demo相比,差距还是很大的。
那么,其实可以理解成有框架和没框架的差距,就能加深理解框架究竟在干嘛。
暴露问题
从效果上看,没了框架,动画资源的管理很不方便。就在上一节中,我们只有一个动画资源,绑定的只有一个target即一个button,那时设置是每次点击都把上一个资源给释放掉,重新分配给当前点击的控件。这样问题就很大了呀:
- 当点完一个按钮立刻点另一个按钮,上一个按钮的动画还没结束就delete掉了?
- 当连续点击一个按钮,资源也是释放得太快甚至都没开始?
- 绘制元素函数是不区分button的统一绘制入口,分不清楚当前播放动画的究竟是哪一个button?
从图中看出问题所在,先直面效果中出现的问题,在代码中解释如下:
- 点击一个按钮后,移动鼠标到另一个按钮2,显然2虽然没点击但动画资源有数据,由于数据没有绑定按钮2,所以按钮2只有一刻的圆;
- 点击按钮1速度再点按钮2,按钮1的波纹立刻没了;
- 连续点击按钮1,效果很差懒得描述;
问题解决
框架很复杂,但主要包含两个方面:
- 让控件和动画资源绑定,建立起联系,让每个控件都有独立的动画资源;
- 允许每个控件拥有多个动画资源,同时需要管理多个动画资源;
这样,当我们连续点击同一个按钮,产生了多个按钮1的动画资源,动画1在跑同时动画2也在跑,互不影响。或者连续切换按钮点,按钮1的动画和按钮2的动画独立的跑也互不影响了。
开始吧阅读理解
代码实在太多太杂。只能按照步骤一个一个来,去除一些不太关键的资源释放的步骤吧。
引擎管理
这边我理解是数据的触发,就像一个引擎:
Animations::Animations( QObject* parent ): QObject( parent )
{
registerEngine( _widgetStateEngine = new WidgetStateEngine( this ) );
}
Animations::~Animations(){
}
WidgetStateEngine& Animations::widgetStateEngine( void ) const{
return *_widgetStateEngine;
}
void Animations::registerWidget( QWidget* widget ) const
{
if( !widget ) return;
if( qobject_cast<QPushButton *>(widget) ) {
_widgetStateEngine->registerWidget( widget);
}
return;
}
这里就是新建了一个引擎,让引擎注册了button控件,接下来就是引擎在干嘛?
绑定控件
bool WidgetStateEngine::registerWidget(QWidget* widget)
{
if( !widget ) return false;
if(!m_data.contains( widget ) ) { m_data.insert( widget, new WidgetStateData( this, widget)); }
connect( widget, SIGNAL(destroyed(QObject*)), this, SLOT(unregisterWidget(QObject*)), Qt::UniqueConnection );
return true;
}
m_data是一个封装了一些函数的qmap数据结构,以输入的widget为键,数据类为键值。
那么,这个数据类WidgetStateData又在干嘛呢?
设置触发条件,设置动画参数
WidgetStateData::WidgetStateData(QObject* parent, QWidget* target):
GenericData(parent, target){
target->installEventFilter(this);
}
WidgetStateData::~WidgetStateData(){
}
bool WidgetStateData::eventFilter(QObject *object, QEvent *event){
if(target() != object)
return GenericData::eventFilter( object, event );
if (object->inherits("QPushButton")){
switch( event->type() )
{
case QEvent::MouseButtonPress:
startRipple(static_cast<QMouseEvent *>(event), target());
break;
default: break;
}
return GenericData::eventFilter( object, event );
}
}
void WidgetStateData::startRipple(QMouseEvent *event, QWidget *target){
QPoint pos = target->rect().center();
qreal radiusEndValue = static_cast<qreal>(target->width());
MaterialRipple *ripple = new MaterialRipple(pos, target);
ripple->setOpacityStartValue(0.35);
ripple->setOpacityEndValue(0);
ripple->setRadiusStartValue(0);
ripple->setRadiusEndValue(radiusEndValue);
ripple->radiusAnimation()->setDuration(1600);
ripple->opacityAnimation()->setDuration(1300);
addRipple(ripple);
}
这里为注册的控件重载了事件过滤,在这里设置触发,至于这个动画类MaterialRipple上一节已经理解过了。
而这个数据类的父类GenericData就是曾经提到的管理,它的作用主要是管理多个动画资源:
void GenericData::addRipple(MaterialRipple *ripple){
m_animations.push_back(ripple);
ripple->start();
connect(ripple, SIGNAL(finished()), this, SLOT(removeRipple()));
connect(this, SIGNAL(destroyed(QObject*)), ripple, SLOT(stop()));
connect(this, SIGNAL(destroyed(QObject*)), ripple, SLOT(deleteLater()));
}
void GenericData::removeRipple(){
MaterialRipple *ripple = qobject_cast<MaterialRipple *>(sender());
if(m_animations.removeOne(ripple)){
ripple->deleteLater();
}
}
void GenericData::removeAllRipple(){
foreach(MaterialRipple *ripple, m_animations){
m_animations.removeOne(ripple);
ripple->deleteLater();
}
}
QWidget * GenericData::target() const{
return m_target;
}
QList<RippleData> GenericData::ripples(){
QList<RippleData> result;
qCritical() << m_animations.count();
foreach(MaterialRipple *ripple, m_animations){
RippleData data;
data.radius = ripple->radius();
data.opacity = ripple->opacity();
result.append(data);
}
return result;
}
m_animations保存着多个动画资源,同时也可控制删除,数据可通过ripples访问;
调用与回顾
在mystyle中新建animation类,在polish(QWidget *widget)中注册widget:_animation->registerWidget(widget);
在绘制button元素的最后加上数据即可:
QList<RippleData> rippleDatas = _animation->widgetStateEngine().ripples(widget);
if (enabled){
foreach (RippleData data, rippleDatas) {
QPointF center = rect.center();
painter->setOpacity(data.opacity);
QBrush brush;
brush.setColor(palette.color(QPalette::Highlight));
brush.setStyle(Qt::SolidPattern);
painter->setBrush(brush);
painter->drawEllipse(center, data.radius, data.radius);
}
}
回顾一下,在这个注册的时候,所有按钮该绑定的已经绑定好了,触发条件该设置的已经设置好了。
在调用_animation->widgetStateEngine().ripples(widget)的时候,就是根据widget为键在qmap数据结构中寻找键值,键值是数据类,它的ripple()即是GenericData类保存的数据了。
这时抛开代码,简化过程:
绑定
建立引擎 -> 引擎建立qmap数据结构,键为widget键值为WidgetStateData类 -> WidgetStateData类设置鼠标点击事件要产生动画类MaterialRipple -> MaterialRipple类生成一堆数据 -> MaterialRipple类的数据以及自己在GenericData管理。
触发
点击按钮 -> 去引擎查找当前widget为键的键值 -> 访问到对应WidgetStateData类 -> 访问到WidgetStateData类的父类GenericData类保存的数据
落幕
最终的效果,显示出来的就应该是丝滑的感觉。