Qt拖拽、添加删除动画的实现

Qt拖拽、添加删除动画的实现

说明:效果和部分代码参考了必剪

为保护公司知识产权,仅给出头文件伪代码

1. 先看效果

2. 类图

3. 底层动画组件 AnimeListWidget

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class AnimeListWidget : public QFrame {
Q_OBJECT
public:
explicit AnimeListWidget(QMargins margins, bool delay_resize, QWidget* parent = nullptr);
void setWidgetPosition(const std::vector<std::pair<QWidget*, QPoint>>& widget_and_position) {
// 修改输入控件的父控件
// 复制QWidget[]
// 计算自身大小
startAnimation();
// 更新自身控件列表widgets_
}
private:
std::vector<QWidget*> widgets_;
void startAnimation(const std::vector<std::pair<QWidget*, QPoint>>& widget_and_position, const QSize& new_size) {
// 创建QParallelAnimationGroup
// 设置自身大小
for (const auto& x : widget_and_position) {
// 已在列表中的控件执行移动
auto animation = new QPropertyAnimation(x.first, "pos", animation_group);
// 未在列表中的控件执行淡入
auto animation = new QPropertyAnimation(x.first->graphicsEffect(), "opacity");
}
animation_group->start(QAbstractAnimation::DeleteWhenStopped);
}
};

4. 拖拽按钮DraggableButton

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class DraggableButton : public QLabel {
Q_OBJECT
public:
DraggableButton(QWidget* parent, QWidget* drag_widget, bool need_hand = true, QString mimedata = "");
QPoint getPosOffset();
void setMimeData(QString mimedata);
protected:
void mouseMoveEvent(QMouseEvent* event) override {
DragManager::Instance().onMouseMoved(drag_widget, event, this, mimedata);
}
private:
QWidget* drag_widget = nullptr;
QPoint pos_offset;
bool need_hand;
QString mimedata;
};

5. 可拖拽列表DraggableListWidget

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
class DraggableListWidget : public QScrollArea {
Q_OBJECT
public:
DraggableListWidget(QWidget* parent, QString mimetype, int padding = 19, QMargins margins = QMargins(0,0,0,0),
QListView::Flow direction = QListView::TopToBottom);

int getSize();

void addItem(QWidget* item);
void insertItem(int index, QWidget* item);
void removeItem(int index);
QWidget* getItem(int index);
void moveItem(int source, int target);
std::vector<QWidget*>& getItems();

signals:
void swapItem(int source, int target, int parent_index);

protected:
void dragEnterEvent(QDragEnterEvent * event) override;
void dragMoveEvent(QDragMoveEvent* event) override;
void dropEvent(QDropEvent* event) override;
void dragLeaveEvent(QDragLeaveEvent* event) override;

public slots:
std::vector<std::pair<QWidget*, QPoint>> update_() {
if (is_dragging) { // 拖动状态下刷新
// 放置插入的占位控件
}
else {
// 正常状态刷新
}
list_widget->setWidgetPosition(widget_and_position);
}
void asyncUpdate() {
QTimer::singleShot(0, this, [this]() {
update_();
});
}

private:
std::vector<QWidget*> items;
AnimeListWidget* list_widget = nullptr;
std::unique_ptr<DropPlaceholder> drop_placeholder;
QString mimetype;
// 根据鼠标位置,计算当前的插入位置,插入位置为排除掉拖动控件的位置
int calculateDropIndex(const QPoint& position) {
// 复制控件列表,但排除被隐藏的控件
// 根据鼠标位置和中心高度/宽度计算插入位置
}
};

6. 拖拽管理器DragManager

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class DragManager : public QObject {
Q_OBJECT
public:
static DragManager& Instance();
QPointer<QWidget> getDraggingWidget() const;
// 处理鼠标事件
void onMouseMoved(QPointer<QWidget> widget, QMouseEvent* event, DraggableButton* btn, QString mimedata = "") {
emit beginDrag(widget);
QDrag::exec(Qt::MoveAction);
emit endDrag(widget);
}

signals:
void beginDrag(QWidget* dragging_widget);
void endDrag(QWidget* dragging_widget);

private:
explicit DragManager(QObject* parent = nullptr) {}
QPointer<QWidget> dragging_widget; // 处于拖动状态的控件
};

7. 占位符DropPlaceholder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class DropPlaceholder : public QFrame {
Q_OBJECT
public:
explicit DropPlaceholder(QWidget* parent = nullptr) {
static const QString kStyleSheet = QStringLiteral(
"#dropPlaceholder {"
" border-radius: 8px;"
"}"
"#mainWidget {"
" border: 2px dashed rgba(112,134,233,0.8);"
" border-radius: 8px;"
" background: rgba(112,134,233,0.16);"
"}"
);
}

protected:
void resizeEvent(QResizeEvent* event) override;

private:
QFrame* main_widget_;
};