前些天看了Qt的拖拽,觉得Qt的拖拽还是蛮灵活;但widgets在移动端类似的app应用场景下,确实显得有一些笨拙;但同时它功能强大,灵活性非常高。
我在这里实现的是一个类似于android手机卸载软件时的动态垃圾箱。先看下效果是这样的。
支持自动缩放,自适应啰。
被拖动的目标显然用QListWidget来存放最方便,这也是拖拽的一个典型应用。垃圾箱的动画使用了QPropertyAnimation,其它的就是拖拽的事件监听了;下面请看代码
#include "widget.h"
#include <QApplication>
#include <QListWidget>
#include <QPropertyAnimation>
#include <QVBoxLayout>
#include <QPushButton>
#include <QBitmap>
#include <QPainter>
#include <QResizeEvent>
#include <QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
QVBoxLayout *lay = new QVBoxLayout(this);
listWidget = new QListWidget;
listWidget->setViewMode(QListWidget::IconMode); //设置显示方式
listWidget->addItem(new QListWidgetItem(QIcon(":/img/test.png"),QStringLiteral("test"))); //放一个item到ListWidget
listWidget->addItem(new QListWidgetItem(QIcon(":/img/trash.png"),QStringLiteral("trash")));//放一个item到ListWidget
listWidget->installEventFilter(this); //为ListWidget添加事件监听,用于检测item项是否被拖动
lay->addWidget(listWidget); //主布局
reRect = new QLabel(this); //顶上垃圾箱背景label
reRect->setAutoFillBackground(true);
QPalette pal = reRect->palette();
pal.setBrush(QPalette::Background,Qt::lightGray); //设置背景色
reRect->setPalette(pal);
QPixmap pix(":/img/trash.png");
reRect->resize(width(),pix.height() + 20); //重设label尺寸
reRect->setMinimumHeight(pix.height() + 20);
reLabel = new QLabel; //垃圾桶
reLabel->resize(pix.size()); //重调垃圾桶label尺寸
reLabel->setPixmap(pix);
reLabel->setMask(pix.mask());
reLabel->setAcceptDrops(true); //使能够接受拖拽的落下
reLabel->installEventFilter(this); //添加事件监听
QHBoxLayout *hlay = new QHBoxLayout(reRect);
hlay->addWidget(reLabel,0,Qt::AlignCenter); //在背景label中居中
ani = new QPropertyAnimation(reRect, "geometry"); //新建动画,绑定属性为背景label的geometry
ani->setEasingCurve(QEasingCurve::InCurve); //设置动画插值方式
ani->setDuration(150); //动画持续时间
connect(ani,&QPropertyAnimation::finished,[&]{ani->setDirection(QAbstractAnimation::Direction(!ani->direction()));}); //改变方向
}
void Widget::resizeEvent(QResizeEvent *event)
{
reRect->resize(event->size().width(),reRect->height()); //当窗口发生变化时,重调背景label尺寸
if(initFlag){ //当第一次初始化时,移动到窗体外面
initFlag = false;
reRect->move(reRect->x(),-reRect->height());
}
ani->setStartValue(QRect(reRect->x(),-reRect->height(),reRect->width(),reRect->height())); //设置动画起点值
ani->setEndValue(reRect->rect()); //设置动画终点值
return QWidget::resizeEvent(event);
}
Widget::~Widget()
{
}
bool Widget::eventFilter(QObject *watched, QEvent *event)
{
if(reLabel = qobject_cast<QLabel*>(watched)){
if(event->type() == QEvent::DragEnter){
QDragEnterEvent *drageEnterE{nullptr};
try{
drageEnterE = dynamic_cast<QDragEnterEvent*>(event);
}catch(std::bad_cast &e){
qDebug() << e.what();
}
if(drageEnterE){
trashPainted = false;
drageEnterE->acceptProposedAction(); //当拖拽事件进入时,启用接收拖拽
}
return true;
}else if(event->type() == QEvent::Drop){
QDropEvent *dropE{nullptr};
try{
dropE = dynamic_cast<QDropEvent*>(event);
}catch(std::bad_cast &e){
qDebug() << e.what();
}
if(dropE){
listWidget->takeItem(listWidget->row(listWidget->currentItem())); //当在垃圾箱落下时,从ListWidget中删除当前项,并恢复垃圾箱颜色
QPixmap *pix = const_cast<QPixmap*>(reLabel->pixmap());
*pix = QPixmap(":/img/trash.png");
reLabel->repaint();
}
return true;
}else if(event->type() == QEvent::DragMove){
QDragMoveEvent *mvEvent{nullptr};
try{
mvEvent = dynamic_cast<QDragMoveEvent*>(event);
}catch(std::bad_cast &e){
qDebug() << e.what();
}
if(mvEvent && !trashPainted){ //当item项在垃圾箱上漂荡时,改变垃圾箱颜色
QPixmap *pix = const_cast<QPixmap*>(reLabel->pixmap());
QPainter p(pix);
p.initFrom(pix);
p.fillRect(pix->rect(),QColor(200,0,0,50));
reLabel->repaint();
trashPainted = !trashPainted;
}
return true;
}else if(event->type() == QEvent::DragLeave){
QDragLeaveEvent *leEvent{nullptr};
try{
leEvent = dynamic_cast<QDragLeaveEvent*>(event);
}catch(std::bad_cast &e){
qDebug() << e.what();
}
if(leEvent){
QPixmap *pix = const_cast<QPixmap*>(reLabel->pixmap()); //当拖拽事件离开时,恢复垃圾箱颜色
*pix = QPixmap(":/img/trash.png");
reLabel->repaint();
}
return true;
}
}else if(listWidget == qobject_cast<QListWidget*>(watched)){
QChildEvent *childEvent{nullptr};
switch (event->type()) {
case QEvent::ChildAdded:
try{
childEvent = dynamic_cast<QChildEvent*>(event);
}catch(std::bad_cast &e){
qDebug() << e.what();
}
if(childEvent){ //当item项被视为拖动时,启动动画
ani->start();
}
break;
case QEvent::ChildRemoved:
try{
childEvent = dynamic_cast<QChildEvent*>(event);
}catch(std::bad_cast &e){
qDebug() << e.what();
}
if(childEvent){ //当item拖动结束时,启动动画
ani->start();
}
break;
}
}
return QWidget::eventFilter(watched,event);
}
我做得比较简单,如果完美一点,应该在最后那段代码中;当item被拖动时应该判断动画执行方向,如果此时动画还是离开窗体中,就应该先停止动画,然后改变方向,再启动动画;当拖动结束时也是一样的。不过我这里设置的动画持续时间比较短,这个现象问题不大。
需要源码可到处下载