Qt拥有强大丰富的界面库。闲来无事,开发一款炫酷的天气预报程序。模拟天气,讯飞语音播报,卡片布局。现在开始慢慢更新,作为备忘录。
布局基类 QLayout
QLayout是QT所有布局的基类,要编写自己的布局类,您必须定义以下内容:
- 数据结构以存储由布局处理的项。每一项都是QLayoutItem。
- addItem(),如何在布局中添加项目。
- setGeometry(),如何执行布局。
- sizeHint(),布局的首选大小。
- itemAt(),如何遍历布局。
- takeAt(),如何从布局中删除项目。
- 为了支持子Item的高依赖于他们的宽,需要实现hasHeightForWidth() 和heightForWidth()。
- 在大多数情况下,您还将实现minimumSize()。
布局管理器销毁时,也将会销毁它管理的Item。
Example: Border Layout Example, and Flow Layout Example.
为了实现自定义带动画的布局管理器,定义了继承自QLayout的CardStackLayout 。
CardStackLayout 类
CardStackLayout .h
#ifndef CARDSTACKLAYOUT_H
#define CARDSTACKLAYOUT_H
#include <QLayout>
#include <QRect>
#include <QMap>
#include <QDebug>
class CardStackLayout : public QLayout
{
Q_OBJECT
public:
// 定义布局风格,目前支持层叠布局(Cascade )
enum CardStackLayoutStyle { Cascade };
// 定义卡片排放方式
enum Position
{
ZTop = 0x00000001, // 在卡片顶层显示
ZBottom = 0x00000010, // 在卡片底层显示
North = 0x00000100, // 相对前一张卡片North(上方)布局
West = 0x00100000 // 相对前一张卡片West(左侧)布局
};
typedef QMap<Position, int> tyPosValue;
enum SizeType { MinimumSize, SizeHint };
// 定义QLayoutItem包装体
struct ItemWrapper
{
ItemWrapper(QLayoutItem *i)
{
item = i;
}
QLayoutItem *item;
tyPosValue mapPos;
};
// 定义交换结构体
struct ItemSwitch
{
ItemSwitch(){}
CardStackLayout::ItemWrapper* item;
QRect rcfrom;
QRect rcto;
ItemSwitch(const ItemSwitch &rhs)
{
*this = rhs;
}
ItemSwitch &operator= (const ItemSwitch &rhs)
{
if(&rhs == this)
return *this;
item=rhs.item;
rcfrom = rhs.rcfrom;
rcto = rhs.rcto;
return *this;
}
};
explicit CardStackLayout(QWidget *parent = Q_NULLPTR ,CardStackLayoutStyle style = Cascade);
~CardStackLayout();
void addItem(QLayoutItem *item) override;
void addWidget(QWidget *widget, Position position = ZBottom);
void adjustWideget(QWidget *widget,long position, int offset);
void adjustWideget(QWidget *widget,const tyPosValue& posvalue);
void adjustWideget(int index,const tyPosValue& posvalue);
void debugPut(QString tips);
bool isRunningAnimation(){ return m_animation_relevant.runing; }
void setUseAnimation(bool use) { m_animation_relevant.useani = use; }
void setAllAlignment(bool horcenter = true, bool vercenter = true);
void setDirectionSpacing(int north = 20, int west = 20);
Qt::Orientations expandingDirections() const override;
bool hasHeightForWidth() const override;
int count() const override;
QLayoutItem *itemAt(int index) const override;
QLayoutItem *takeAt(int index) override;
QSize minimumSize() const override;
void setGeometry(const QRect &rect) override;
QSize sizeHint() const override;
void setNeedNonFirstTransparent(bool state) { m_non_first_transparent = state; changeNonFirstTransparent(); }
bool needNonFirstTransparent(){ return m_non_first_transparent; }
protected slots:
void reLayer();
void animationFinished();
protected:
void destoryAll();
ItemWrapper *find(QWidget* item, int *index = NULL);
void add(QLayoutItem *item, Position position);
QSize calculateSize(SizeType sizeType) const;
bool calculateItemRect(QRect prerc, ItemWrapper *p, QRect &rcnow) const;
QRect calculateCombRect(QRect rclayout);
void switchOrder(int oldindex, int newindex,ItemWrapper* p);
void adjustItem(int index, ItemWrapper* p,long posflag, int offset);
void geometryAnimation(QList<ItemSwitch> &lstmp);
void changeNonFirstTransparent();
private:
// 定义动画选项
struct AnimationRelevant
{
bool useani;
bool runing;
short anitype; // 0 geometry, 1 opacity
};
AnimationRelevant m_animation_relevant;
bool m_horizontal_center;
bool m_vertical_center;
bool m_non_first_transparent;
int m_north_spacing;
int m_west_spacing;
QList<ItemWrapper*> m_list;
CardStackLayoutStyle m_cardstacklayout_style;
};
#endif // CARDSTACKLAYOUT_H
CardStackLayout .cpp
#include "cardstacklayout.h"
#include <cmath>
#include <QLabel>
#include <QPropertyAnimation>
#include <QParallelAnimationGroup>
#include <QGraphicsOpacityEffect>
CardStackLayout::CardStackLayout(QWidget *parent, CardStackLayout::CardStackLayoutStyle style)
: QLayout(parent)
, m_cardstacklayout_style(style)
, m_horizontal_center(true)
, m_vertical_center(true)
{
m_animation_relevant.runing = false;
m_animation_relevant.anitype = 1;
m_animation_relevant.useani = true;
m_non_first_transparent = true;
m_north_spacing = 20;
m_west_spacing = 20;
setMargin(0);
setSpacing(0);
}
CardStackLayout::~CardStackLayout()
{
destoryAll();
}
void CardStackLayout::destoryAll()
{
QLayoutItem *item = NULL;
while (item = takeAt(0))
{
delete item;
}
}
void CardStackLayout::addItem(QLayoutItem *item)
{
add(item, ZBottom);
}
void CardStackLayout::addWidget(QWidget *widget, CardStackLayout::Position position)
{
add(new QWidgetItem(widget), position);
}
void CardStackLayout::add(QLayoutItem *item, CardStackLayout::Position position)
{
if(Cascade == m_cardstacklayout_style)
{
item->setAlignment(Qt::AlignHCenter);
ItemWrapper *p = new ItemWrapper(item);
if(ZTop == position)
{
m_list.push_front(p);
}
else
{
m_list.append(p);
}
reLayer();
}
// QWidget *w = item->widget();
// if(w)
// {
// w->setSizePolicy(QSizePolicy::Fixed,QSizePolicy::Fixed);
// w->show();
// }
changeNonFirstTransparent();
invalidate();
}
bool CardStackLayout::hasHeightForWidth() const
{
return false;
}
int CardStackLayout::count() const
{
return m_list.size();
}
QLayoutItem *CardStackLayout::itemAt(int index) const
{
ItemWrapper *it = m_list.value(index);
if (it)
return it->item;
else
return 0;
}
QLayoutItem *CardStackLayout::takeAt(int index)
{
QLayoutItem *item = NULL;
if (index >= 0 && index < m_list.size())
{
ItemWrapper *layoutStruct = m_list.takeAt(index);
item = layoutStruct->item;
delete layoutStruct;
}
return item;
}
void CardStackLayout::setGeometry(const QRect &rect)
{
QLayout::setGeometry(rect);
QRect rcpre, rcnow;
QRect rccombination = calculateCombRect(rect);
QPoint ptsrccenter = rccombination.center();
QPoint ptdstcenter = rect.center();
int woffset = abs(ptdstcenter.rx())-abs(ptsrccenter.rx());
int hoffset = abs(ptdstcenter.ry())-abs(ptsrccenter.ry());
if(!m_list.empty())
{
ItemWrapper *p = m_list.at(0);
rcnow = rect;
rcnow.setSize(p->item->geometry().size());
if(m_horizontal_center)
{
rcnow.adjust(woffset,0,woffset,0);
}
if(m_vertical_center)
{
rcnow.adjust(0,hoffset,0,hoffset);
}
p->item->setGeometry(rcnow);
rcpre = rcnow;
}
for (int i = 1; i < m_list.size(); ++i)
{
ItemWrapper *p = m_list.at(i);
bool bok = calculateItemRect(rcpre,p,rcnow);
if(!bok){ continue;}
p->item->setGeometry(rcnow);
rcpre = rcnow;
}
}
QSize CardStackLayout::minimumSize() const
{
return calculateSize(MinimumSize);
}
QSize CardStackLayout::sizeHint() const
{
return calculateSize(SizeHint);
}
QSize CardStackLayout::calculateSize(CardStackLayout::SizeType sizeType) const
{
QSize totalSize(0,0);
QRect rcpre, rcnow,rect(0,0,0,0);
for (int i = 0; i < m_list.size(); ++i)
{
ItemWrapper *wrapper = m_list.at(i);
QSize itemSize;
if (sizeType == MinimumSize)
{
itemSize = wrapper->item->minimumSize();
}
else // (sizeType == SizeHint)
{
itemSize = wrapper->item->sizeHint();
}
ItemWrapper *p = m_list.at(i);
rcpre = (0 == i)? p->item->geometry():rcpre;
bool bok = calculateItemRect(rcpre,p,rcnow);
if(!bok){ continue;}
rcpre = rcnow;
totalSize = totalSize.expandedTo(rcnow.size());
}
return totalSize;
}
bool CardStackLayout::calculateItemRect(QRect rcpre, ItemWrapper *p, QRect &rcnow) const
{
if(NULL == p)
{
Q_ASSERT(NULL!=p);
return false;
}
QWidget *w = p->item->widget();
rcnow = rcpre;
tyPosValue::const_iterator it;
it = p->mapPos.find(North);
if(it!=p->mapPos.end())
{
rcnow.setTop(rcpre.top()+it.value());
}
else
{
rcnow.setTop(rcpre.top() + m_north_spacing);
}
it = p->mapPos.find(West);
if(it!=p->mapPos.end())
{
rcnow.setLeft(rcpre.left()+it.value());
}
else
{
rcnow.setLeft(rcpre.left() + m_west_spacing);
}
if(NULL!=w)
{
rcnow.setRight(rcnow.left()+ w->size().width());
rcnow.setBottom(rcnow.top()+ w->size().height());
}
else
{
QSize si = p->item->geometry().size();
rcnow.setRight(rcnow.left()+ si.width());
rcnow.setBottom(rcnow.top()+ si.height());
}
return true;
}
QRect CardStackLayout::calculateCombRect(QRect rclayout)
{
QRect rcpre = rclayout, rcnow, rcmax = rclayout;
if(!m_list.empty())
{
rcmax.setSize(m_list.at(0)->item->geometry().size());
rcpre = rcmax;
}
for (int i = 1; i < m_list.size(); ++i)
{
ItemWrapper *p = m_list.at(i);
bool bok = calculateItemRect(rcpre,p,rcnow);
if(!bok){ continue;}
rcmax = rcmax.united(rcnow);
rcpre = rcnow;
}
return rcmax;
}
Qt::Orientations CardStackLayout::expandingDirections() const
{
return Qt::Horizontal | Qt::Vertical;
}
void CardStackLayout::adjustWideget(QWidget *widget, long posflag, int offset)
{
if(Q_NULLPTR == widget) return;
int index;
CardStackLayout::ItemWrapper *p = find(widget,&index);
adjustItem(index,p,posflag,offset);
}
void CardStackLayout::adjustWideget(QWidget *widget, const CardStackLayout::tyPosValue &posvalue)
{
if(Q_NULLPTR == widget) return;
int index;
ItemWrapper *p = find(widget,&index);
tyPosValue::const_iterator it = posvalue.begin();
while (it != posvalue.end() )
{
adjustItem(index,p,it.key(),it.value());
++it;
}
}
void CardStackLayout::adjustWideget(int index, const CardStackLayout::tyPosValue &posvalue)
{
if(index<0 || index>=m_list.size())
return;
ItemWrapper *p = m_list.at(index);
tyPosValue::const_iterator it = posvalue.begin();
while (it != posvalue.end() )
{
adjustItem(index,p,it.key(),it.value());
++it;
}
}
void CardStackLayout::adjustItem(int index, CardStackLayout::ItemWrapper *p, long posflag, int offset)
{
if(Q_NULLPTR == p || m_animation_relevant.runing)
return;
if(posflag & ZTop || posflag & ZBottom)
{
int newIndex = offset;
int n = m_list.size();
if(posflag & ZTop)
{
newIndex = n>newIndex? newIndex: n-1;
}
else
{
newIndex = newIndex>0?0:newIndex;
newIndex = n>abs(newIndex)? (n+newIndex-1): 0;
}
switchOrder(index,newIndex,p);
}
if( posflag & North || posflag & West)
{
if(posflag & North)
{
p->mapPos[North] = offset;
}
if(posflag & West)
{
p->mapPos[West] = offset;
}
invalidate();
}
}
void CardStackLayout::changeNonFirstTransparent()
{
QGraphicsOpacityEffect* pOpacityEffect = NULL;
qreal opa = m_non_first_transparent?0.1:1.0;
for(auto it = m_list.begin(); it!=m_list.end();++it)
{
ItemWrapper *tmp = *it;
QWidget *p = tmp->item->widget();
if(NULL == p) continue;
pOpacityEffect = (QGraphicsOpacityEffect*)p->graphicsEffect();
if(NULL == pOpacityEffect) continue;
if(it!=m_list.end()-1)
{
pOpacityEffect->setOpacity(opa);
}
else
{
pOpacityEffect->setOpacity(1.0);
}
}
}
void CardStackLayout::geometryAnimation(QList<ItemSwitch> &lstmp)
{
if(lstmp.empty() || !m_animation_relevant.useani || m_animation_relevant.runing)
return ;
const CardStackLayout::ItemWrapper *tail = m_list.back();
QParallelAnimationGroup *group = new QParallelAnimationGroup;
for(auto it = lstmp.begin(); it!=lstmp.end(); ++it)
{
QWidget *w = it->item->item->widget();
if(NULL==w) continue;
QPropertyAnimation *animation = new QPropertyAnimation(w, "geometry");
animation->setDuration((it->item)==tail?500:500);
animation->setStartValue(it->rcfrom);
animation->setEndValue(it->rcto);
animation->setEasingCurve(QEasingCurve::OutInQuad);
group->addAnimation(animation);
}
connect(group,SIGNAL(finished()),this,SLOT(reLayer()));
connect(group,SIGNAL(finished()),this,SLOT(animationFinished()));
m_animation_relevant.runing = true;
group->start(QAbstractAnimation::DeleteWhenStopped);
}
void CardStackLayout::switchOrder(int oldindex, int newindex, CardStackLayout::ItemWrapper *p)
{
if(newindex<0 || newindex >=m_list.size() || oldindex == newindex)
return;
QList<ItemSwitch> lstmp;
if(true)
{
ItemSwitch stit;
tyPosValue tyfrom,tyto = p->mapPos;
QRect rcfrom,rcto = p->item->geometry();
if(oldindex<newindex)
{
for(int i = oldindex+1; i<=newindex; ++i)
{
ItemWrapper* it = m_list.at(i);
tyfrom = it->mapPos;
rcfrom = it->item->geometry();
it->mapPos = tyto;
stit.item = it;
stit.rcfrom = rcfrom;
stit.rcto = rcto;
lstmp.push_front(stit);
tyto = tyfrom;
rcto = rcfrom;
}
p->mapPos = tyto;
}
else
{
for(int i = oldindex-1;i>=newindex; --i)
{
ItemWrapper* it = m_list.at(i);
tyfrom = it->mapPos;
rcfrom = it->item->geometry();
it->mapPos = tyto;
stit.item = it;
stit.rcfrom = rcfrom;
stit.rcto = rcto;
lstmp.push_front(stit);
tyto = tyfrom;
rcto = rcfrom;
}
p->mapPos = tyto;
}
stit.item = p;
stit.rcfrom = p->item->geometry();
stit.rcto = rcto;
lstmp.push_back(stit);
}
for(auto it = m_list.begin(); it!=m_list.end();)
{
if( *it == p)
{
it = m_list.erase(it);
m_list.insert(newindex,p);
break;
}
++it;
}
if(m_animation_relevant.useani)
{
geometryAnimation(lstmp);
}
else
{
reLayer();
changeNonFirstTransparent();
}
}
void CardStackLayout::reLayer()
{
for(auto it = m_list.begin(); it!=m_list.end();++it)
{
QWidget* p = (*it)->item->widget();
if( NULL!=p)
{
p->raise();
}
}
}
void CardStackLayout::animationFinished()
{
m_animation_relevant.runing = false;
changeNonFirstTransparent();
}
CardStackLayout::ItemWrapper *CardStackLayout::find(QWidget *item, int *index)
{
ItemWrapper *p = Q_NULLPTR;
int i = 0;
for(auto it = m_list.begin(); it!=m_list.end();++it,++i)
{
ItemWrapper *tmp = *it;
if( tmp->item->widget() == item)
{
p = *it;
break;
}
}
if(NULL!=index)
{
*index = i;
}
return p;
}
void CardStackLayout::debugPut(QString tips)
{
qDebug() << tips;
for(int i=0;i<m_list.size();++i)
{
ItemWrapper* it = m_list.at(i);
//QString s = ((QLabel*)(it->item->widget()))->text();
QRect rc = it->item->geometry();
qDebug() << i << " " << it->item << /*s <<*/ rc.topLeft() << " " << rc.bottomRight();
}
}
void CardStackLayout::setDirectionSpacing(int north, int west)
{
m_north_spacing = north;
m_west_spacing = west;
invalidate();
}
void CardStackLayout::setAllAlignment(bool horcenter, bool vercenter)
{
m_horizontal_center = horcenter;
m_vertical_center = vercenter;
invalidate();
}