QPoint类
QPoint类用于表示平面中的一个点,采用整数精度存储x、y坐标,是Qt中处理坐标、位置的基础类,支持坐标直接操作、加减乘除等向量运算,常用于描述控件位置、鼠标坐标、动画位移等场景。
核心方法与代码示例
cpp
/**
* @brief QPoint构造函数:创建指定坐标的点对象
* @param xpos x轴坐标(整数),水平方向位置,向右为正
* @param ypos y轴坐标(整数),垂直方向位置,向下为正
* @return 无返回值,构造对象
* @note 默认构造函数QPoint()创建(0,0)的空点,isNull()返回true
*/
QPoint point(100, 200); // 创建x=100,y=200的点
/**
* @brief x():获取x轴坐标
* @param 无参数
* @return int 返回当前点的x坐标值
* @note 配套setX(int x)可修改x坐标,rx()返回x坐标的引用,支持直接修改
*/
int x = point.x(); // x = 100
point.setX(150); // 修改x坐标为150
point.rx()++; // 直接操作x引用,x变为151
/**
* @brief y():获取y轴坐标
* @param 无参数
* @return int 返回当前点的y坐标值
* @note 配套setY(int y)可修改y坐标,ry()返回y坐标的引用,支持直接修改
*/
int y = point.y(); // y = 200
point.setY(250); // 修改y坐标为250
point.ry()--; // 直接操作y引用,y变为249
/**
* @brief manhattanLength():计算曼哈顿长度(x绝对值+y绝对值)
* @param 无参数
* @return int 返回曼哈顿长度值,是欧几里得长度的快速近似
* @note 常用于判断鼠标移动距离、简化碰撞检测等场景,计算效率远高于平方根运算
*/
int mLen = point.manhattanLength(); // |151| + |249| = 400
/**
* @brief operator+=:点的加法运算(向量相加)
* @param point 待相加的QPoint对象
* @return QPoint& 返回当前点对象的引用(自身)
* @note 对应operator-=为向量相减,operator*=为坐标乘系数(整数/浮点数)
*/
QPoint offset(10, 20);
point += offset; // point变为(161, 269)
/**
* @brief dotProduct:静态方法,计算两个点的点积(向量点乘)
* @param p1 第一个QPoint操作数
* @param p2 第二个QPoint操作数
* @return int 点积结果(p1.x*p2.x + p1.y*p2.y)
* @note 点积可用于判断向量夹角、投影计算等几何运算
*/
QPoint p(3,7), q(-1,4);
int dot = QPoint::dotProduct(p, q); // 3*(-1) +7*4 = 25
QEvent类
QEvent是Qt所有事件的基类,封装了事件的类型、接受状态等核心信息,Qt的事件循环会将系统事件转换为QEvent对象,再分发给对应的QObject对象处理。事件可分为系统事件(如鼠标、键盘、窗口变化)和自定义事件,支持事件的接受/忽略、事件过滤等机制。
核心成员
- Type枚举:定义所有事件类型,如鼠标事件(MouseMove/Press/Release)、窗口事件(Move/Resize/Close)、自定义事件(User/MaxUser区间);
- accepted属性:标记事件是否被处理,accept()设为true,ignore()设为false,未处理的事件会向上传递给父对象;
- 核心方法:type()获取事件类型、spontaneous()判断是否为系统原生事件、registerEventType()注册自定义事件类型。
代码示例(事件处理与自定义事件)
slideevent.h(自定义事件头文件)
cpp
#ifndef SLIDEEVENT_H
#define SLIDEEVENT_H
// 头文件保护宏:防止重复包含
#include <QEvent>
#include <QPoint>
// 声明自定义事件类型(extern保证跨文件可见)
extern const int CustomSlideEvent;
/**
* @brief 自定义滑动事件类:继承QEvent,携带滑动偏移量
* @note 仅声明类和核心方法,实现直接写在头文件(简单类无需单独cpp)
*/
class SlideEvent : public QEvent {
public:
// 构造函数:初始化事件类型和滑动偏移量
SlideEvent(QPoint delta) : QEvent((QEvent::Type)CustomSlideEvent), m_delta(delta) {}
// 获取滑动偏移量的接口
QPoint delta() const { return m_delta; }
private:
QPoint m_delta; // 存储滑动偏移量
};
#endif // SLIDEEVENT_H
mywidget.h(自定义窗口头文件)
cpp
#ifndef MYWIDGET_H
#define MYWIDGET_H
#include <QWidget>
#include <QMouseEvent>
// 引入自定义事件头文件
#include "slideevent.h"
/**
* @brief 自定义窗口类:处理鼠标事件和自定义滑动事件
*/
class MyWidget : public QWidget {
Q_OBJECT // Qt的信号槽/元对象系统必须的宏
public:
// 构造函数:显式声明,方便后续扩展
explicit MyWidget(QWidget *parent = nullptr);
protected:
// 重写事件总入口:处理自定义SlideEvent和系统MouseMove
bool event(QEvent *event) override;
// 重写鼠标按下事件:记录鼠标偏移
void mousePressEvent(QMouseEvent *event) override;
// 重写鼠标释放事件:触发自定义SlideEvent
void mouseReleaseEvent(QMouseEvent *event) override;
private:
QPoint m_lastMousePos; // 鼠标按下时与窗口的偏移
QPoint m_pressGlobalPos; // 鼠标按下时的全局坐标
};
#endif // MYWIDGET_H
mywidget.cpp(自定义窗口实现文件)
cpp
#include "mywidget.h"
#include <QCoreApplication>
#include <QDebug>
// 定义自定义事件类型(赋值,与头文件的extern对应)
const int CustomSlideEvent = QEvent::registerEventType(QEvent::User + 1);
/**
* @brief MyWidget构造函数:初始化成员变量
*/
MyWidget::MyWidget(QWidget *parent) : QWidget(parent) {
// 可选:设置窗口默认属性
setWindowTitle("自定义滑动事件示例");
resize(300, 200);
}
/**
* @brief 事件总处理函数:识别并处理自定义事件和系统事件
*/
bool MyWidget::event(QEvent *event) {
// 处理自定义SlideEvent
if (event->type() == (QEvent::Type)CustomSlideEvent) {
SlideEvent *slideEvent = static_cast<SlideEvent*>(event);
QPoint delta = slideEvent->delta();
// 核心逻辑:根据偏移量移动窗口
move(pos() + delta);
qDebug() << "SlideEvent触发!窗口偏移量:" << delta;
event->accept(); // 标记事件已处理
return true;
}
// 处理系统MouseMove事件
if (event->type() == QEvent::MouseMove) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
QPoint mousePos = mouseEvent->pos();
qDebug() << "鼠标位置(相对窗口):" << mousePos;
// 不拦截,交给父类处理(保证鼠标事件正常传递)
return QWidget::event(event);
}
// 其他事件交给父类处理
return QWidget::event(event);
}
/**
* @brief 鼠标按下事件:记录鼠标位置偏移
*/
void MyWidget::mousePressEvent(QMouseEvent *event) {
if (event->button() == Qt::LeftButton) {
m_lastMousePos = event->globalPos() - pos(); // 鼠标与窗口的偏移
m_pressGlobalPos = event->globalPos(); // 鼠标全局坐标
event->accept();
}
// 交给父类处理(保证其他按键逻辑正常)
QWidget::mousePressEvent(event);
}
/**
* @brief 鼠标释放事件:手动发送自定义SlideEvent
*/
void MyWidget::mouseReleaseEvent(QMouseEvent *event) {
if (event->button() == Qt::LeftButton) {
// 计算鼠标滑动的总偏移量
QPoint delta = event->globalPos() - m_pressGlobalPos;
// 核心:手动发送自定义事件(同步发送,栈对象)
SlideEvent slideEvent(delta);
QCoreApplication::sendEvent(this, &slideEvent);
}
// 交给父类处理
QWidget::mouseReleaseEvent(event);
}
main.cpp(程序入口文件)
cpp
#include <QApplication>
#include "mywidget.h"
/**
* @brief 主函数:Qt程序入口
*/
int main(int argc, char *argv[]) {
// 创建Qt应用对象
QApplication a(argc, argv);
// 创建自定义窗口并显示
MyWidget w;
w.show();
// 运行应用事件循环
return a.exec();
}

QPropertyAnimation类
QPropertyAnimation是Qt的属性动画类,用于对QObject的属性进行平滑动画(如位置、大小、透明度等),继承自QVariantAnimation,支持设置动画时长、起始/结束值、插值方式等,是Qt动画框架的核心类。使用前提:目标对象必须是QObject子类,且动画的属性必须有对应的setter方法(如geometry属性对应setGeometry())。
触摸事件逻辑(用开发板时替换下述示例中的鼠标事件)
cpp
// 重写event
bool Widget::event(QEvent *event)
{
if(event->type() == QEvent::TouchBegin)
{
QTouchEvent *touchEvent = static_cast<QTouchEvent*>(event);
startPos = touchEvent->touchPoints().first().pos().toPoint();
// 处理事件
return true;
}
else if(event->type() == QEvent::TouchUpdate &&m_isSliding)
{
QTouchEvent *touchEvent = static_cast<QTouchEvent*>(event);
QPoint currentPos = touchEvent->touchPoints().first().pos().toPoint();
// 处理事件
return true;
}
else if(event->type() == QEvent::TouchEnd)
{
// 处理事件
return true;
}
return QWidget::event(event);
}
场景:屏幕滑动交互(仿手机列表滑动)
slidewidget.h(头文件)
cpp
#ifndef SLIDEWIDGET_H
#define SLIDEWIDGET_H
#include <QWidget>
#include <QPropertyAnimation>
#include <QScrollArea>
class SlideWidget : public QWidget {
Q_OBJECT
public:
explicit SlideWidget(QWidget *parent = nullptr);
~SlideWidget() override; // 析构函数释放动画对象
protected:
// 重写鼠标事件(逻辑改为操作滚动区域)
void mousePressEvent(QMouseEvent *event) override; //
void mouseMoveEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
private:
QPropertyAnimation *m_anim; // 动画对象(改为操作滚动条)
QScrollArea *m_scrollArea; // 列表的滚动容器
bool m_isPress = false; // 鼠标是否按下
QPoint m_startMousePos; // 鼠标按下时的全局位置
int m_startScrollValue = 0; // 按下时滚动条的初始值
};
#endif // SLIDEWIDGET_H
slidewidget.cpp(实现文件)
cpp
#include "slidewidget.h"
#include <QMouseEvent>
#include <QVBoxLayout>
#include <QLabel>
#include <QScrollBar>
#include <QtMath>
SlideWidget::SlideWidget(QWidget *parent) : QWidget(parent) {
// 1. 窗口基础设置(固定大小,模拟手机屏幕,位置固定)
setFixedSize(300, 400);
setWindowTitle("列表滑动示例");
move(200, 200); // 固定窗口位置,不再随鼠标移动
// 2. 创建滚动容器(核心:列表放在QScrollArea里,实现内容滚动)
m_scrollArea = new QScrollArea(this);
m_scrollArea->setWidgetResizable(true); // 自适应子控件大小
m_scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // 隐藏水平滚动条
// 3. 创建列表内容容器
QWidget *contentWidget = new QWidget(m_scrollArea);
QVBoxLayout *contentLayout = new QVBoxLayout(contentWidget);
// 添加列表项(10个,高度50,总高度500,超出窗口400的高度,可滚动)
for (int i = 0; i < 10; i++) {
QLabel *label = new QLabel(QString("列表项 %1").arg(i+1), contentWidget);
label->setFixedHeight(50);
contentLayout->addWidget(label);
}
contentWidget->setLayout(contentLayout);
m_scrollArea->setWidget(contentWidget);
// 4. 将滚动容器铺满整个窗口
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(0, 0, 0, 0); // 去掉边距
mainLayout->addWidget(m_scrollArea);
setLayout(mainLayout);
// 5. 创建动画对象(改为操作滚动条的value属性)
/**
* @brief QPropertyAnimation构造函数:绑定滚动条的value属性
* @param target 目标:滚动区域的垂直滚动条
* @param propertyName 属性名:"value"(滚动条的数值)
* @param parent 父对象,用于内存管理
* @return 无返回值
*/
m_anim = new QPropertyAnimation(m_scrollArea->verticalScrollBar(), "value", this);
m_anim->setDuration(300); // 动画时长300ms,贴近原生滑动
}
SlideWidget::~SlideWidget() {
// 释放动画对象(可选,父对象也会自动释放,但显式释放更规范)
delete m_anim;
}
/**
* @brief 鼠标按下事件:记录初始状态(滚动条位置+鼠标位置)
* @param event 鼠标事件对象
* @return void
* @note 停止正在进行的动画,避免拖动和动画冲突
*/
void SlideWidget::mousePressEvent(QMouseEvent *event) {
if (event->button() == Qt::LeftButton) {
m_isPress = true;
m_startMousePos = event->globalPos(); // 鼠标全局位置
// 记录按下时滚动条的当前值(核心:替代原来的窗口位置)
m_startScrollValue = m_scrollArea->verticalScrollBar()->value();
m_anim->stop(); // 停止惯性动画,优先响应手动拖动
}
QWidget::mousePressEvent(event);
}
/**
* @brief 鼠标移动事件:实时更新滚动条位置,实现列表跟随滑动
* @param event 鼠标事件对象
* @return void
* @note 鼠标y轴偏移量反向映射到滚动条(鼠标上滑=列表下滑,符合直觉)
*/
void SlideWidget::mouseMoveEvent(QMouseEvent *event) {
if (m_isPress) {
// 计算鼠标y轴偏移量(全局坐标差)
int deltaY = m_startMousePos.y() - event->globalPos().y();
// 新的滚动条值 = 初始值 + 偏移量(反向映射,符合滑动直觉)
int newScrollValue = m_startScrollValue + deltaY;
// 限制滚动范围(0 ~ 滚动条最大值,避免滑出边界)
int scrollMax = m_scrollArea->verticalScrollBar()->maximum();
/**
* @brief qBound - Qt通用数值范围限制函数(模板函数)
* @headerfile <QtGlobal> (Qt核心头文件已间接包含,通常无需手动引入)
* @template T 支持所有数值类型(int/double/float/qint32等),要求min/val/max类型一致
* @param min 数值区间的最小值(下限)
* @param val 需要被限制的目标数值
* @param max 数值区间的最大值(上限)
* @return const T& 返回值规则:
* 1. 若val < min → 返回min(低于下限则取下限)
* 2. 若val > max → 返回max(高于上限则取上限)
* 3. 若min ≤ val ≤ max → 返回val(在区间内则保留原值)
* @note 核心优势:替代繁琐的if-else判断,一行代码实现数值边界限制,是Qt数值限制的最佳实践
*/
newScrollValue = qBound(0, newScrollValue, scrollMax);
// 更新滚动条位置(列表滑动,窗口不动)
m_scrollArea->verticalScrollBar()->setValue(newScrollValue);
}
QWidget::mouseMoveEvent(event);
}
/**
* @brief 鼠标释放事件:实现列表惯性滑动动画
* @param event 鼠标事件对象
* @return void
* @note 基于拖动速度计算惯性偏移,让滑动更自然
*/
void SlideWidget::mouseReleaseEvent(QMouseEvent *event) {
if (event->button() == Qt::LeftButton && m_isPress) {
m_isPress = false;
// 计算鼠标释放时的偏移量(y轴)
int deltaY = m_startMousePos.y() - event->globalPos().y();
// 惯性偏移:放大偏移量(0.8倍,可调整),模拟惯性
int inertiaDelta = deltaY * 0.8;
// 目标滚动值 = 当前值 + 惯性偏移
int currentValue = m_scrollArea->verticalScrollBar()->value();
int endValue = currentValue + inertiaDelta;
// 限制惯性滚动范围(0 ~ 滚动条最大值)
int scrollMax = m_scrollArea->verticalScrollBar()->maximum();
endValue = qBound(0, endValue, scrollMax);
// 设置动画:滚动条从当前值滑到目标值(惯性效果)
m_anim->setStartValue(currentValue);
m_anim->setEndValue(endValue);
/**
* @brief start:启动惯性动画
* @param mode 默认KeepWhenStopped,动画结束后保留最终值
* @return void
*/
m_anim->start();
}
QWidget::mouseReleaseEvent(event);
}
main.cpp(程序入口)
cpp
#include <QApplication>
#include "slidewidget.h"
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
SlideWidget w;
w.show();
return a.exec();
}

场景:上滑关闭打开的软件窗口(仿手机上滑关闭App)
SlideCloseHelper.h
cpp
#ifndef SLIDECLOSEHELPER_H
#define SLIDECLOSEHELPER_H
// 仅包含必要的前置声明/头文件(减少依赖)
#include <QObject>
#include <QPoint>
#include <QWidget>
#include <QPropertyAnimation>
// 前置声明(避免包含过多头文件)
class QElapsedTimer;
class QTouchEvent;
class QMouseEvent;
class QEasingCurve;
class SlideCloseHelper : public QObject
{
Q_OBJECT // Qt元对象系统必需宏
public:
// 构造函数声明
explicit SlideCloseHelper(QWidget *target, QObject *parent = nullptr);
// 公有配置接口声明
void setSlideThreshold(int threshold);
void setTimeThreshold(int threshold);
void setFollowRatio(int ratio);
protected:
// 事件过滤器声明
bool eventFilter(QObject *watched, QEvent *event) override;
private:
// 私有方法声明(内部复用逻辑)
void moveTargetWithRatio(int dy);
void checkSlide(const QPoint &endPos);
private:
// 成员变量声明
QWidget *m_target = nullptr; // 目标窗口
QPoint m_startPos; // 滑动起始点
QPoint m_originPos; // 窗口初始位置(回弹用)
QElapsedTimer *m_timer = nullptr; // 滑动计时(改为指针,cpp中初始化)
bool m_isPress = false; // 是否按下/触摸
int m_slideThreshold = 80; // 滑动距离阈值
int m_timeThreshold = 300; // 滑动时间阈值
int m_followRatio = 5; // 滑动跟随比例
QPropertyAnimation *m_anim = nullptr; // 回弹动画对象
};
#endif // SLIDECLOSEHELPER_H
SlideCloseHelper.cpp
cpp
#include "SlideCloseHelper.h"
// 实现中需要的头文件(集中引入)
#include <QElapsedTimer>
#include <QTouchEvent>
#include <QMouseEvent>
#include <QEasingCurve>
#include <QtGlobal> // qBound需要
/**
* @brief 构造函数实现:初始化目标窗口和动画对象
* @param target 需添加上滑关闭的目标窗口
* @param parent 父对象(内存管理)
*/
SlideCloseHelper::SlideCloseHelper(QWidget *target, QObject *parent)
: QObject(parent), m_target(target) {
if (m_target) {
// 开启触摸事件支持(移动端)
m_target->setAttribute(Qt::WA_AcceptTouchEvents, true);
// 安装事件过滤器,拦截目标窗口事件
m_target->installEventFilter(this);
// 记录窗口初始位置(用于回弹)
m_originPos = m_target->pos();
// 初始化计时对象
m_timer = new QElapsedTimer();
// 初始化回弹动画
m_anim = new QPropertyAnimation(m_target, "pos", this);
m_anim->setDuration(200);
m_anim->setEasingCurve(QEasingCurve::InOutCubic);
}
}
/**
* @brief 设置滑动距离阈值
* @param threshold 阈值(像素)
*/
void SlideCloseHelper::setSlideThreshold(int threshold) {
m_slideThreshold = threshold;
}
/**
* @brief 设置滑动时间阈值
* @param threshold 阈值(毫秒)
*/
void SlideCloseHelper::setTimeThreshold(int threshold) {
m_timeThreshold = threshold;
}
/**
* @brief 设置滑动跟随比例
* @param ratio 比例(值越大,滑动跟随越慢)
*/
void SlideCloseHelper::setFollowRatio(int ratio) {
m_followRatio = ratio;
}
/**
* @brief 事件过滤器实现:拦截并处理目标窗口的触摸/鼠标事件
* @param watched 被监控的对象
* @param event 事件对象
* @return bool 是否拦截事件(false=不拦截,交给目标窗口处理)
*/
bool SlideCloseHelper::eventFilter(QObject *watched, QEvent *event) {
// 非目标窗口/目标窗口为空,直接返回false
if (watched != m_target || !m_target) return false;
// 处理触摸事件(移动端)
if (event->type() == QEvent::TouchBegin) {
QTouchEvent *touchEvent = static_cast<QTouchEvent*>(event);
m_startPos = touchEvent->touchPoints().first().pos().toPoint();
m_timer->restart(); // 重启计时器
m_isPress = true;
if (m_anim) m_anim->stop(); // 停止正在进行的动画
} else if (event->type() == QEvent::TouchUpdate) {
if (m_isPress) {
QTouchEvent *touchEvent = static_cast<QTouchEvent*>(event);
QPoint currentPos = touchEvent->touchPoints().first().pos().toPoint();
int dy = currentPos.y() - m_startPos.y();
moveTargetWithRatio(dy); // 触摸滑动跟随
}
} else if (event->type() == QEvent::TouchEnd) {
if (!m_isPress) return false;
QTouchEvent *touchEvent = static_cast<QTouchEvent*>(event);
QPoint endPos = touchEvent->touchPoints().first().pos().toPoint();
checkSlide(endPos); // 判断是否触发关闭
m_isPress = false;
}
// 处理鼠标事件(桌面端)
else if (event->type() == QEvent::MouseButtonPress) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
if (mouseEvent->button() == Qt::LeftButton) {
m_startPos = mouseEvent->pos();
m_timer->restart(); // 重启计时器
m_isPress = true;
if (m_anim) m_anim->stop(); // 停止正在进行的动画
}
} else if (event->type() == QEvent::MouseMove) {
if (m_isPress) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
int dy = mouseEvent->pos().y() - m_startPos.y();
moveTargetWithRatio(dy); // 鼠标滑动跟随
}
} else if (event->type() == QEvent::MouseButtonRelease) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
if (mouseEvent->button() == Qt::LeftButton && m_isPress) {
checkSlide(mouseEvent->pos()); // 判断是否触发关闭
m_isPress = false;
}
}
// 不拦截事件,交给目标窗口的默认处理逻辑
return QObject::eventFilter(watched, event);
}
/**
* @brief 滑动跟随逻辑:限制垂直滑动范围,更新窗口位置
* @param dy y轴偏移量
*/
void SlideCloseHelper::moveTargetWithRatio(int dy) {
if (!m_target) return;
// 计算新的y坐标(按比例跟随,避免滑动过快)
int newY = m_target->y() + dy / m_followRatio;
// 限制y轴范围:避免滑出屏幕过多
newY = qBound(m_originPos.y() - 50, newY, m_originPos.y() + 100);
// 仅更新y轴(固定x轴,垂直滑动)
m_target->move(m_target->x(), newY);
}
/**
* @brief 滑动判断逻辑:满足阈值则关闭窗口,否则回弹
* @param endPos 滑动结束位置
*/
void SlideCloseHelper::checkSlide(const QPoint &endPos) {
if (!m_target || !m_timer) return;
// 计算y轴偏移量(dy<0=上滑)和滑动耗时
int dy = endPos.y() - m_startPos.y();
int elapsed = m_timer->elapsed();
// 满足上滑关闭条件:距离阈值 + 时间阈值
if (dy < -m_slideThreshold && elapsed < m_timeThreshold) {
// 创建透明度动画,关闭窗口(更顺滑)
QPropertyAnimation *opacityAnim = new QPropertyAnimation(m_target, "windowOpacity");
opacityAnim->setDuration(200);
opacityAnim->setStartValue(1.0); // 动画开始时,窗口透明度为1.0(完全不透明)
opacityAnim->setEndValue(0.0); // 动画结束时,窗口透明度为0.0(完全透明)
// 动画结束后关闭窗口+释放动画对象
connect(opacityAnim, &QPropertyAnimation::finished, m_target, &QWidget::close);
connect(opacityAnim, &QPropertyAnimation::finished, opacityAnim, &QPropertyAnimation::deleteLater);
opacityAnim->start();
} else {
// 不满足条件:回弹到初始位置
if (m_anim) {
m_anim->setStartValue(m_target->pos());
m_anim->setEndValue(m_originPos);
m_anim->start();
}
}
}
main.cpp
cpp
#include <QApplication>
#include <QWidget>
#include <QLabel>
#include <QVBoxLayout>
#include "SlideCloseHelper.h"
// 示例1:给普通QWidget添加上滑关闭
void createNormalWidget() {
QWidget *w = new QWidget();
w->setWindowTitle("普通窗口-上滑关闭");
w->setFixedSize(300, 500);
w->move(200, 100);
// 一行代码添加上滑关闭功能(核心复用逻辑)
new SlideCloseHelper(w);
w->show();
}
// 示例2:给自定义内容窗口添加上滑关闭(自定义阈值)
void createCustomContentWidget() {
QWidget *w = new QWidget();
w->setWindowTitle("自定义内容窗口-上滑关闭");
w->setFixedSize(300, 500);
w->move(600, 100);
// 自定义窗口内容
QVBoxLayout *layout = new QVBoxLayout(w);
for (int i = 0; i < 10; i++) {
layout->addWidget(new QLabel(QString("列表项 %1").arg(i+1), w));
}
w->setLayout(layout);
// 复用Helper,自定义阈值(更严格的关闭条件)
SlideCloseHelper *helper = new SlideCloseHelper(w);
helper->setSlideThreshold(100); // 距离阈值改为100px
helper->setTimeThreshold(250); // 时间阈值改为250ms
w->show();
}
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
// 给两个不同窗口复用"上滑关闭"功能,无需修改窗口类代码
createNormalWidget();
createCustomContentWidget();
return a.exec();
}

场景:滑动显示 / 隐藏顶部栏、侧边栏
slidebarwidget.h
cpp
#ifndef SLIDEBARWIDGET_H
#define SLIDEBARWIDGET_H
#include <QWidget>
#include <QEvent>
#include <QPropertyAnimation>
#include <QMouseEvent>
#include <QTouchEvent>
#include <QShowEvent>
#include <QResizeEvent>
// 滑动阈值:滑动距离超过该值才触发显示/隐藏(避免误触)
#define SLIDE_THRESHOLD 50
// 动画时长(毫秒):控制过渡动画的顺滑度
#define ANIM_DURATION 300
/**
* @class SlideBarWidget
* @brief 滑动交互控件类
* @details 支持触摸/鼠标滑动操作,实现全屏高度的顶部栏(隐藏在顶部外)、侧边栏(隐藏在左侧外)的动画显示/隐藏
* 纯代码创建控件,无UI文件依赖,顶部栏高度与窗口高度保持一致
*/
class SlideBarWidget : public QWidget
{
Q_OBJECT
public:
/**
* @brief 构造函数
* @param parent 父窗口指针(用于内存管理)
*/
explicit SlideBarWidget(QWidget *parent = nullptr);
/**
* @brief 析构函数
* @details 释放动态创建的控件、动画对象,避免内存泄漏
*/
~SlideBarWidget() override;
protected:
/**
* @brief 事件分发函数(重写)
* @details 处理触摸事件(触屏设备),包括触摸开始、更新、结束
* @param event 事件对象指针
* @return bool 事件是否被处理(true=已处理,false=交给父类处理)
*/
bool event(QEvent *event) override;
/**
* @brief 鼠标按下事件(重写)
* @details 电脑测试用,记录滑动起始位置,标记开始滑动状态
* @param event 鼠标事件对象
*/
void mousePressEvent(QMouseEvent *event) override;
/**
* @brief 鼠标移动事件(重写)
* @details 电脑测试用,计算滑动距离,判断是否触发显示/隐藏逻辑
* @param event 鼠标事件对象
*/
void mouseMoveEvent(QMouseEvent *event) override;
/**
* @brief 鼠标释放事件(重写)
* @details 电脑测试用,重置滑动状态
* @param event 鼠标事件对象
*/
void mouseReleaseEvent(QMouseEvent *event) override;
/**
* @brief 窗口大小变化事件(重写)
* @details 保证顶部栏宽高、侧边栏高度跟随窗口大小自适应
* @param event 大小变化事件对象
*/
void resizeEvent(QResizeEvent *event) override;
/**
* @brief 窗口显示事件(重写)
* @details 窗口首次显示时,同步顶部栏高度为窗口真实高度,并修正初始隐藏位置
* @param event 显示事件对象
*/
void showEvent(QShowEvent *event) override;
private slots:
/**
* @brief 显示顶部栏
* @details 通过属性动画将顶部栏从顶部外(隐藏位置)移动到窗口顶部(显示位置)
*/
void showTopBar();
/**
* @brief 隐藏顶部栏
* @details 通过属性动画将顶部栏从窗口顶部(显示位置)移动到顶部外(隐藏位置)
*/
void hideTopBar();
/**
* @brief 显示侧边栏
* @details 通过属性动画将侧边栏从左侧外(隐藏位置)移动到窗口左侧(显示位置)
*/
void showSideBar();
/**
* @brief 隐藏侧边栏
* @details 通过属性动画将侧边栏从窗口左侧(显示位置)移动到左侧外(隐藏位置)
*/
void hideSideBar();
private:
// 控件对象(纯代码创建,替代UI文件)
QWidget *m_topBar; // 顶部栏控件(全屏高度)
QWidget *m_sideBar; // 侧边栏控件
// 滑动状态变量
QPoint m_startPos; // 滑动起始位置(触摸/鼠标按下时的坐标)
bool m_isSliding; // 是否正在滑动(防止重复触发)
// 控件尺寸配置
int m_sideBarWidth; // 侧边栏宽度(固定值)
// 动画对象
QPropertyAnimation *m_topBarAnim; // 顶部栏动画
QPropertyAnimation *m_sideBarAnim; // 侧边栏动画
/**
* @brief 初始化控件
* @details 创建顶部栏、侧边栏,设置样式、初始位置、大小等属性
*/
void initWidgets();
/**
* @brief 初始化动画
* @details 创建属性动画,设置默认时长、缓动曲线(让动画更顺滑)
*/
void initAnimations();
};
#endif // SLIDEBARWIDGET_H
slidebarwidget.cpp
cpp
#include "slidebarwidget.h"
SlideBarWidget::SlideBarWidget(QWidget *parent)
: QWidget(parent)
, m_topBar(nullptr) // 初始化控件指针
, m_sideBar(nullptr)
, m_isSliding(false) // 初始无滑动
, m_sideBarWidth(200) // 侧边栏宽度200px
, m_topBarAnim(nullptr)
, m_sideBarAnim(nullptr)
{
// 开启鼠标追踪(无需按下鼠标,也能捕获鼠标移动事件)
this->setMouseTracking(true);
// 初始化控件和动画
initWidgets();
initAnimations();
}
SlideBarWidget::~SlideBarWidget()
{
// 释放动态创建的对象,避免内存泄漏
delete m_topBarAnim;
delete m_sideBarAnim;
delete m_topBar;
delete m_sideBar;
}
void SlideBarWidget::initWidgets()
{
// ========== 创建顶部栏 ==========
m_topBar = new QWidget(this); // 父对象设为当前窗口,自动管理内存
// 初始化时仅设置宽度为窗口初始宽度,高度延迟到showEvent中同步
m_topBar->setFixedWidth(this->width());
// 初始位置:临时隐藏在窗口顶部外(后续在showEvent中修正为真实高度)
m_topBar->move(0, -100);
// 设置样式:蓝色背景、白色文字、20px字体
m_topBar->setStyleSheet(R"(
QWidget {
background-color: #3498db;
color: white;
font-size: 20px;
}
)");
// ========== 创建侧边栏 ==========
m_sideBar = new QWidget(this);
m_sideBar->setFixedWidth(m_sideBarWidth); // 固定宽度
m_sideBar->setFixedHeight(this->height());// 高度跟随窗口
// 初始位置:隐藏在窗口左侧外(x轴=-宽度)
m_sideBar->move(-m_sideBarWidth, 0);
// 设置样式:绿色背景、白色文字、20px字体
m_sideBar->setStyleSheet(R"(
QWidget {
background-color: #2ecc71;
color: white;
font-size: 20px;
}
)");
// 侧边栏隐藏时忽略鼠标事件(避免遮挡底层控件)
m_sideBar->setAttribute(Qt::WA_TransparentForMouseEvents);
}
void SlideBarWidget::initAnimations()
{
// ========== 顶部栏动画 ==========
m_topBarAnim = new QPropertyAnimation(m_topBar, "pos", this);
m_topBarAnim->setDuration(ANIM_DURATION); // 动画时长300ms
// 缓动曲线:OutCubic(先快后慢,动画更自然)
m_topBarAnim->setEasingCurve(QEasingCurve::OutCubic);
// ========== 侧边栏动画 ==========
m_sideBarAnim = new QPropertyAnimation(m_sideBar, "pos", this);
m_sideBarAnim->setDuration(ANIM_DURATION);
m_sideBarAnim->setEasingCurve(QEasingCurve::OutCubic);
}
bool SlideBarWidget::event(QEvent *event)
{
// 处理触摸开始事件(触屏设备)
if (event->type() == QEvent::TouchBegin) {
QTouchEvent *touchEvent = static_cast<QTouchEvent*>(event);
// 记录第一个触摸点的起始位置
m_startPos = touchEvent->touchPoints().first().pos().toPoint();
m_isSliding = true; // 标记开始滑动
return true; // 事件已处理,不再传递
}
// 处理触摸更新事件(触屏滑动中)
else if (event->type() == QEvent::TouchUpdate && m_isSliding) {
QTouchEvent *touchEvent = static_cast<QTouchEvent*>(event);
QPoint currentPos = touchEvent->touchPoints().first().pos().toPoint();
int deltaY = currentPos.y() - m_startPos.y(); // 上下滑动距离
int deltaX = currentPos.x() - m_startPos.x(); // 左右滑动距离
// 优先处理左右滑动(侧边栏):滑动距离超过阈值、横向滑动>纵向、起始位置在窗口左半区
if (qAbs(deltaX) > SLIDE_THRESHOLD && qAbs(deltaX) > qAbs(deltaY) && (m_startPos.x() < this->width()/2)) {
if (deltaX > 0) { // 向右滑:显示侧边栏
showSideBar();
} else { // 向左滑:隐藏侧边栏
hideSideBar();
}
m_isSliding = false; // 重置滑动状态,避免重复触发
}
// 处理上下滑动(顶部栏):滑动距离超过阈值
else if (qAbs(deltaY) > SLIDE_THRESHOLD) {
if (deltaY > 0) { // 向下滑:显示顶部栏
showTopBar();
} else { // 向上滑:隐藏顶部栏
hideTopBar();
}
m_isSliding = false;
}
return true;
}
// 处理触摸结束事件
else if (event->type() == QEvent::TouchEnd) {
m_isSliding = false; // 重置滑动状态
return true;
}
// 未处理的事件交给父类处理
return QWidget::event(event);
}
void SlideBarWidget::mousePressEvent(QMouseEvent *event)
{
// 记录鼠标按下的起始位置(电脑测试用)
m_startPos = event->pos();
m_isSliding = true;
// 调用父类函数,保留默认行为(如焦点变化)
QWidget::mousePressEvent(event);
}
void SlideBarWidget::mouseMoveEvent(QMouseEvent *event)
{
if (!m_isSliding) { // 未开始滑动,直接返回
QWidget::mouseMoveEvent(event);
return;
}
QPoint currentPos = event->pos();
int deltaY = currentPos.y() - m_startPos.y();
int deltaX = currentPos.x() - m_startPos.x();
// 逻辑同触摸事件(电脑测试用)
if (qAbs(deltaX) > SLIDE_THRESHOLD && qAbs(deltaX) > qAbs(deltaY) && (m_startPos.x() < this->width()/2)) {
if (deltaX > 0) {
showSideBar();
} else {
hideSideBar();
}
m_isSliding = false;
}
else if (qAbs(deltaY) > SLIDE_THRESHOLD) {
if (deltaY > 0) {
showTopBar();
} else {
hideTopBar();
}
m_isSliding = false;
}
QWidget::mouseMoveEvent(event);
}
void SlideBarWidget::mouseReleaseEvent(QMouseEvent *event)
{
m_isSliding = false; // 重置滑动状态
QWidget::mouseReleaseEvent(event);
}
void SlideBarWidget::showEvent(QShowEvent *event)
{
if (m_topBar) {
// 窗口首次显示时,同步顶部栏高度为窗口真实高度
m_topBar->setFixedHeight(this->height());
// 修正初始隐藏位置:y轴=-窗口高度(完全隐藏在顶部外)
m_topBar->move(0, -this->height());
}
QWidget::showEvent(event);
}
void SlideBarWidget::resizeEvent(QResizeEvent *event)
{
// 窗口大小变化时,同步顶部栏宽高为窗口尺寸
if (m_topBar) {
m_topBar->setFixedWidth(this->width());
m_topBar->setFixedHeight(this->height());
// 修正隐藏位置:保持当前显示/隐藏状态,适配新高度
int currentY = m_topBar->y();
if (currentY < 0) {
m_topBar->move(0, -this->height());
}
}
// 同步侧边栏高度为窗口高度
if (m_sideBar) {
m_sideBar->setFixedHeight(this->height());
}
QWidget::resizeEvent(event);
}
void SlideBarWidget::showTopBar()
{
// 动画起始位置:顶部外(隐藏,使用实时窗口高度)
m_topBarAnim->setStartValue(QPoint(0, -this->height()));
// 动画结束位置:窗口顶部(显示)
m_topBarAnim->setEndValue(QPoint(0, 0));
m_topBarAnim->start(); // 启动动画
}
void SlideBarWidget::hideTopBar()
{
// 动画起始位置:窗口顶部(显示)
m_topBarAnim->setStartValue(QPoint(0, 0));
// 动画结束位置:顶部外(隐藏,使用实时窗口高度)
m_topBarAnim->setEndValue(QPoint(0, -this->height()));
m_topBarAnim->start();
}
void SlideBarWidget::showSideBar()
{
// 取消鼠标事件透明(显示后可交互)
m_sideBar->setAttribute(Qt::WA_TransparentForMouseEvents, false);
// 动画起始位置:左侧外(隐藏)
m_sideBarAnim->setStartValue(QPoint(-m_sideBarWidth, 0));
// 动画结束位置:窗口左侧(显示)
m_sideBarAnim->setEndValue(QPoint(0, 0));
m_sideBarAnim->start();
}
void SlideBarWidget::hideSideBar()
{
// 动画起始位置:窗口左侧(显示)
m_sideBarAnim->setStartValue(QPoint(0, 0));
// 动画结束位置:左侧外(隐藏)
m_sideBarAnim->setEndValue(QPoint(-m_sideBarWidth, 0));
m_sideBarAnim->start();
// 隐藏后设置鼠标事件透明(不遮挡底层控件)
m_sideBar->setAttribute(Qt::WA_TransparentForMouseEvents, true);
}
main.cpp
cpp
#include <QApplication>
#include "slidebarwidget.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// 创建滑动控件窗口
SlideBarWidget w;
w.resize(800, 600); // 设置窗口大小
w.setWindowTitle("滑动显示/隐藏栏示例");
w.show();
return a.exec();
}

场景:窗口拖拽停靠(仿桌面软件窗口拖拽到边缘停靠)
DockWidget.h
cpp
#ifndef DOCKWIDGET_H
#define DOCKWIDGET_H
// 必要头文件(仅包含类声明所需的核心头文件)
#include <QWidget>
#include <QPropertyAnimation>
// 前置声明(避免包含过多头文件,减少依赖)
class QMouseEvent;
class QRect;
class QPoint;
class QScreen;
/**
* @brief DockWidget类:实现窗口拖拽停靠功能
* @details 支持鼠标拖拽窗口到屏幕左/右/上/下边缘,触发自动停靠(占对应边缘一半屏幕区域);
* 停靠时带顺滑动画,拖拽过程中实时检测边缘距离,释放鼠标后执行停靠逻辑
* @inherits QWidget 继承自Qt窗口基类,具备窗口基础能力
* @note 核心依赖QPropertyAnimation实现停靠动画,QMouseEvent处理拖拽交互
*/
class DockWidget : public QWidget {
Q_OBJECT // Qt元对象系统必需宏,支持属性动画/信号槽
public:
/**
* @brief 构造函数:初始化窗口属性和停靠动画对象
* @param parent 父窗口指针(默认nullptr,作为顶级窗口)
* @return 无返回值
*/
explicit DockWidget(QWidget *parent = nullptr);
protected:
/**
* @brief 重写鼠标按下事件:记录拖拽初始状态
* @param event 鼠标事件对象,包含按下位置、按键类型等信息
* @return void
* @note 仅处理左键按下,记录鼠标相对窗口的偏移量,停止正在进行的停靠动画
*/
void mousePressEvent(QMouseEvent *event) override;
/**
* @brief 重写鼠标移动事件:实现窗口拖拽+实时检测停靠边缘
* @param event 鼠标事件对象,包含移动后的全局/相对位置信息
* @return void
* @note 左键按下时跟随鼠标拖拽窗口,同时调用checkDock检测是否靠近屏幕边缘
*/
void mouseMoveEvent(QMouseEvent *event) override;
/**
* @brief 重写鼠标释放事件:执行停靠动画或保持当前位置
* @param event 鼠标事件对象,包含释放位置、按键类型等信息
* @return void
* @note 左键释放时,若检测到停靠区域则执行动画停靠,否则保持当前位置
*/
void mouseReleaseEvent(QMouseEvent *event) override;
private:
/**
* @brief 检测窗口是否靠近屏幕边缘,判断是否触发停靠
* @param pos 窗口当前左上角的坐标(屏幕坐标系)
* @return void
* @note 检测左/右/上/下四个边缘,触发距离为20像素;
* 停靠时窗口自动占对应边缘的一半屏幕区域(如左边缘占屏幕左半区)
*/
void checkDock(const QPoint &pos);
private:
QPropertyAnimation *m_anim; // 停靠动画对象(绑定geometry属性)
bool m_isPress = false; // 鼠标是否左键按下的标记
QPoint m_mouseOffset; // 鼠标相对窗口左上角的偏移量(避免拖拽瞬移)
bool m_isDock = false; // 是否检测到停靠区域的标记
QRect m_dockRect; // 停靠目标区域(屏幕坐标系下的矩形)
};
#endif // DOCKWIDGET_H
DockWidget.cpp
cpp
#include "DockWidget.h"
// 实现中需要的头文件(集中引入,不污染头文件)
#include <QMouseEvent>
#include <QScreen>
#include <QApplication>
#include <QEasingCurve>
/**
* @brief 构造函数实现:初始化窗口和动画对象
*/
DockWidget::DockWidget(QWidget *parent) : QWidget(parent) {
// 设置窗口基础属性
setWindowTitle("窗口拖拽停靠示例");
resize(400, 300); // 初始窗口大小:宽400px,高300px
move(200, 200); // 初始窗口位置:屏幕坐标(200,200)
/**
* @brief 创建停靠动画对象:绑定窗口的geometry属性
* @param target 动画目标:当前窗口(this)
* @param propertyName 动画属性:"geometry"(窗口位置+大小,对应setGeometry())
* @param parent 动画父对象:当前窗口(Qt自动管理内存)
*/
m_anim = new QPropertyAnimation(this, "geometry", this);
m_anim->setDuration(250); // 动画时长250ms,保证停靠顺滑不卡顿
/**
* @brief 设置缓动曲线:OutQuad(先快后慢)
* @note OutQuad曲线让动画开头快、结尾慢,贴近原生系统的停靠动画体验
*/
m_anim->setEasingCurve(QEasingCurve::OutQuad);
}
/**
* @brief 鼠标按下事件实现:记录拖拽初始状态
*/
void DockWidget::mousePressEvent(QMouseEvent *event) {
// 仅处理鼠标左键按下
if (event->button() == Qt::LeftButton) {
m_isPress = true; // 标记鼠标已按下
/**
* @note event->pos() 是鼠标相对窗口左上角的坐标(窗口坐标系)
* 记录该偏移量,避免拖拽时窗口瞬移到鼠标位置
*/
m_mouseOffset = event->pos();
m_anim->stop(); // 停止正在进行的停靠动画,优先响应手动拖拽
}
// 调用父类事件,保证基础鼠标逻辑不丢失
QWidget::mousePressEvent(event);
}
/**
* @brief 鼠标移动事件实现:拖拽窗口+实时检测停靠
*/
void DockWidget::mouseMoveEvent(QMouseEvent *event) {
if (m_isPress) { // 仅处理左键按下后的移动
/**
* 计算窗口新位置:
* event->globalPos() → 鼠标全局坐标(屏幕坐标系)
* m_mouseOffset → 鼠标相对窗口的偏移量
* 新位置 = 鼠标全局位置 - 偏移量 → 保证鼠标始终在拖拽时的相对位置
*/
QPoint newPos = event->globalPos() - m_mouseOffset;
move(newPos); // 更新窗口位置,实现跟随鼠标拖拽
// 实时检测当前位置是否靠近屏幕边缘,判断是否触发停靠
checkDock(newPos);
}
// 调用父类事件,保证基础鼠标逻辑不丢失
QWidget::mouseMoveEvent(event);
}
/**
* @brief 鼠标释放事件实现:执行停靠动画或保持当前位置
*/
void DockWidget::mouseReleaseEvent(QMouseEvent *event) {
if (event->button() == Qt::LeftButton && m_isPress) {
m_isPress = false; // 取消鼠标按下标记
// 如果检测到停靠区域,执行停靠动画
if (m_isDock) {
m_anim->setStartValue(geometry()); // 动画起始值:窗口当前的位置+大小
m_anim->setEndValue(m_dockRect); // 动画结束值:停靠目标区域
m_anim->start(); // 启动停靠动画
}
m_isDock = false; // 重置停靠标记,准备下一次拖拽检测
}
// 调用父类事件,保证基础鼠标逻辑不丢失
QWidget::mouseReleaseEvent(event);
}
/**
* @brief 停靠检测逻辑实现:判断是否靠近屏幕边缘,设置停靠目标区域
*/
void DockWidget::checkDock(const QPoint &pos) {
// 获取屏幕可用区域(排除任务栏等系统控件占用的区域)
QRect screenRect = QApplication::primaryScreen()->availableGeometry();
int dockMargin = 20; // 停靠触发距离:窗口边缘距离屏幕边缘<20像素则触发
// 1. 左边缘停靠:窗口左边界(pos.x())距离屏幕左边界<20像素
if (pos.x() < dockMargin) {
m_isDock = true;
// 停靠目标区域:屏幕左半区(x=0, y=0,宽=屏幕一半,高=屏幕全高)
m_dockRect = QRect(0, 0, screenRect.width()/2, screenRect.height());
}
// 2. 右边缘停靠:窗口右边界(pos.x()+窗口宽度)距离屏幕右边界<20像素
else if (pos.x() + width() > screenRect.width() - dockMargin) {
m_isDock = true;
// 停靠目标区域:屏幕右半区(x=屏幕一半,y=0,宽=屏幕一半,高=屏幕全高)
m_dockRect = QRect(screenRect.width()/2, 0, screenRect.width()/2, screenRect.height());
}
// 3. 上边缘停靠:窗口上边界(pos.y())距离屏幕上边界<20像素
else if (pos.y() < dockMargin) {
m_isDock = true;
// 停靠目标区域:屏幕上半区(x=0, y=0,宽=屏幕全宽,高=屏幕一半)
m_dockRect = QRect(0, 0, screenRect.width(), screenRect.height()/2);
}
// 4. 下边缘停靠:窗口下边界(pos.y()+窗口高度)距离屏幕下边界<20像素
else if (pos.y() + height() > screenRect.height() - dockMargin) {
m_isDock = true;
// 停靠目标区域:屏幕下半区(x=0, y=屏幕一半,宽=屏幕全宽,高=屏幕一半)
m_dockRect = QRect(0, screenRect.height()/2, screenRect.width(), screenRect.height()/2);
}
// 未靠近任何边缘,取消停靠标记
else {
m_isDock = false;
}
}
main.cpp
cpp
#include <QApplication>
#include "DockWidget.h"
int main(int argc, char *argv[]) {
// 创建Qt应用对象,管理应用生命周期和事件循环
QApplication a(argc, argv);
// 创建停靠窗口对象并显示
DockWidget w;
w.show(); // Qt窗口默认隐藏,需调用show()显示
// 启动应用事件循环,阻塞直到应用退出
return a.exec();
}
总结
- QPoint 是Qt处理坐标的核心基础类,支持整数坐标的直接操作和向量运算,曼哈顿长度是快速判断位置偏移的高效方式;
- QEvent 是所有事件的基类,Qt通过事件对象分发交互事件,可重写专用事件函数(如mousePressEvent)或自定义事件实现交互逻辑;
- QPropertyAnimation 核心是动画QObject的属性(需有setter方法),结合鼠标事件可实现滑动、上滑关闭、窗口停靠等常见UI交互,缓动曲线能优化动画的原生体验。