baizy77 发表于 2019-7-7 10:11:35

KS09-15 菜单-动画效果

本帖最后由 baizy77 于 2019-7-11 21:16 编辑

版权声明----------------------------------------------------------------------------------------------------------------------------该文章原创于Qter开源社区(www.qter.org)作者: 女儿叫老白转载请注明出处!----------------------------------------------------------------------------------------------------------------------------课程目录: 【独家连载】《Qt入门与提高-GUI产品开发》目录
网页版课程源码 提取码:1uy7
引言------------------------------------------------------------------------------------我们经常在网页上看到有些菜单弹出时带有动画效果,看上去让人耳目一新,那么在Qt中能否实现类似效果呢?本节我们为读者介绍如何实现菜单的动画弹出效果。
正文------------------------------------------------------------------------------------菜单的动画效果可以依赖Qt的动画机制实现。Qt提供了QPropertyAnimation类,该类依赖Qt的property机制实现动画。Qt的类体系中,只要从QObject类派生而来,就都支持属性访问,比如下面的QMenu类:代码清单09-15-01qmenu.h
class Q_WIDGETS_EXPORT QMenu : public QWidget
{
private:
    Q_OBJECT
    Q_DECLARE_PRIVATE(QMenu)

    Q_PROPERTY(bool tearOffEnabled READ isTearOffEnabled WRITE setTearOffEnabled)
    Q_PROPERTY(QString title READ title WRITE setTitle)
    Q_PROPERTY(QIcon icon READ icon WRITE setIcon)
    Q_PROPERTY(bool separatorsCollapsible READ separatorsCollapsible WRITE setSeparatorsCollapsible)
Q_PROPERTY(bool toolTipsVisible READ toolTipsVisible WRITE setToolTipsVisible)
// ......
};
代码清单09-15-01摘自Qt的头文件qmenu.h。可以看出,第7~14行用宏Q_PROPERTY描述了QMenu类的属性,比如第7行的tearOffEnabled属性是个bool值,它的读访问接口为isTearOffEnabled,它的写访问接口为setTearOffEnabled。当然,QMenu还有继承自父类QWidget的属性,在此我们不再一一介绍。Qt的动画类QPropertyAnimation就是通过修改类的属性值达到动画效果。比如在本节中我们如果想实现菜单动画弹出和动画退出的效果,就要修改它的pos属性,该属性在QWidget中提供,见代码清单09-15-02的第15行。代码清单09-15-02qwidget.hclass Q_WIDGETS_EXPORT QWidget : public QObject, public QPaintDevice
{
    Q_OBJECT
    Q_DECLARE_PRIVATE(QWidget)

    Q_PROPERTY(bool modal READ isModal)
    Q_PROPERTY(Qt::WindowModality windowModality READ windowModality WRITE setWindowModality)
    Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled)
    Q_PROPERTY(QRect geometry READ geometry WRITE setGeometry)
    Q_PROPERTY(QRect frameGeometry READ frameGeometry)
    Q_PROPERTY(QRect normalGeometry READ normalGeometry)
    Q_PROPERTY(int x READ x)
    Q_PROPERTY(int y READ y)
    Q_PROPERTY(QPoint pos READ pos WRITE move DESIGNABLE false STORED false)
    // ......
};

要实现菜单的动画效果,我们还要把菜单从menubar()移除并且改为使用一个按钮弹出和隐藏菜单(见图09-15-01中的file Menu按钮)。本节分为两步介绍菜单的动画效果。
首先,构造菜单、触发菜单的按钮(file Menu按钮)    构造菜单和按钮跟普通的主窗体程序开发基本一样,只是构造菜单时为其指定的parent为NULL。    我们先来看头文件。代码清单09-15-03mainwindow.hclass CMainWindow : public QMainWindow
{      
private slots:
    void slot_fileMenu();
    void slot_openFile();
    void slot_saveFile();
    void slot_closeFile();
    void slot_exit();
private:
      void createMenus();                        /// 构建菜单
private:
      QMenu *m_pFileMenu;                        /// 文件菜单
      QAction *m_pFileMenuAct;      /// 文件菜单
      QAction *m_pOpenFileAct;      /// 打开文件
    QAction *m_pSaveFileAct;      /// 保存文件
    QAction *m_pCloseFileAct;      /// 关闭文件
    QAction *m_pExitAct;            /// 退出
};
在代码清单09-15-03中,我们只列出了新增菜单、菜单项目、触发按钮的定义(粗体、斜体部分的代码)。    其中第12行是文件菜单对象(也就是要执行动画效果的菜单);    第13行是触发文件菜单显示、隐藏的按钮对应的QAction对象;    第14~17行是文件菜单的菜单项,用来演示;    第4行是触发按钮单击时对应的槽函数;    第5~8行是各个菜单项对应的槽函数;    第10行是构建菜单的接口;
    接着,看一下CMainWindow实现。
代码清单09-15-04
mainwindow.cppCMainWindow::CMainWindow(QWidget* parent) :
    QMainWindow(parent),
    m_pAnimaMenuShow(NULL),
    m_bShowMenu(true)
{
      initialize();

      createActions();
    createMenus();
      createToolBars();
      createStatusBar();
      connectSignalAndSlot();

      setWindowTitle(tr("Demo"));
      setMinimumSize(160, 160);
      //resize(480, 320);
    showMaximized();
}
在代码清单09-15-04的构造函数中,我们在第9行增加了构建菜单接口createMenus()的调用。    封掉了第16行的resize()调用,改为第17行的最大化显示窗口。最大化的目的是为了美观,否则一个非最大化的窗体使用动画弹出一个菜单感觉有点怪,当然,其实最主要的目的是为了简化坐标的计算,这一点后面我们会介绍。
代码清单09-15-05mainwindow.cppvoid CMainWindow::createActions()
{
   // ......
   // file operation action
    m_pFileMenuAct = new QAction(tr("file Menu"), this);
    m_pFileMenuAct->setStatusTip(tr("file Menu"));
    connect(m_pFileMenuAct, &QAction::triggered, this, &CMainWindow::slot_fileMenu);
   
    // file operation action
    m_pOpenFileAct = new QAction(tr("openFile"), this);
    m_pOpenFileAct->setStatusTip(tr("openFile"));
    connect(m_pOpenFileAct, &QAction::triggered, this, &CMainWindow::slot_openFile);

    m_pSaveFileAct = new QAction(tr("saveFile"), this);
    m_pSaveFileAct->setStatusTip(tr("saveFile"));
    connect(m_pSaveFileAct, &QAction::triggered, this, &CMainWindow::slot_saveFile);
   
    m_pCloseFileAct = new QAction(tr("closeFile"), this);
    m_pCloseFileAct->setStatusTip(tr("closeFile"));
    connect(m_pCloseFileAct, &QAction::triggered, this, &CMainWindow::slot_closeFile);

    m_pExitAct = new QAction(tr("exit"), this);
    m_pExitAct->setStatusTip(tr("exit"));
    connect(m_pExitAct, &QAction::triggered, this, &CMainWindow::slot_exit);
}
void CMainWindow::createToolBars()
{
      // ......
    m_pEditToolBar->addAction(m_pFileMenuAct);
    // ......
}
void CMainWindow::createMenus()
{
    m_pFileMenu = new QMenu(tr("&File"), this);
    m_pFileMenu->setMouseTracking(true);//接收鼠标事件
    //flag设置为tool和无边框,消除qmenu的popup效果
    m_pFileMenu->setWindowFlags(Qt::CustomizeWindowHint | Qt::Tool | Qt::FramelessWindowHint);

    m_pFileMenu->addAction(m_pOpenFileAct);
    m_pFileMenu->addAction(m_pCloseFileAct);
    m_pFileMenu->addAction(m_pSaveFileAct);
    m_pFileMenu->addAction(m_pExitAct);      
}
代码清单09-15-05中,我们修改了createActions()接口增加了各个菜单项的构造代码。    并且,在第34行,我们在createToolBars()中将触发菜单显示、隐藏的按钮对应的m_pFileMenuAct对象添加到pEditToolBar工具栏。    第37~49行是新增的createMenus(),在该接口中,我们构造了m_pFileMenu并且将各个菜单项追加到菜单m_pFileMenu。需要注意的是在第42行,我们将m_pFileMenu的窗体风格设置为Qt::CustomizeWindowHint | Qt::Tool | Qt::FramelessWindowHint,目的是取消它的Popup属性,防止单击其他位置时导致菜单消失(因为我们希望在单击按钮时才隐藏菜单)。
代码清单09-15-06
mainwindow.cppvoid CMainWindow::slot_openFile()
{
    QMessageBox::information(this, "menu", "openFile triggered!");
}
void CMainWindow::slot_saveFile()
{
}
void CMainWindow::slot_closeFile()
{
}
void CMainWindow::slot_exit()
{
}

在代码清单09-15-06中,我们提供了各个菜单项对应的槽函数,显然这些槽函数大部分都是假的(空实现)。    至此为止,我们的第一步构建菜单的工作结束。下面进入第二步。
然后,利用QPropertyAnimation实现动画效果
代码清单09-15-07mainwindow.cppclass QPropertyAnimation;
class CMainWindow : public QMainWindow
{
    // ......
    QPropertyAnimation* m_pAnimaMenuShow; /// 菜单动画
    bool   m_bShowMenu;       /// 显示菜单
};
    在代码清单09-15-07中,我们在第1行对类QPropertyAnimation进行声明,因为后面要用该类定义对象。使用声明而非引入头文件的目的是减少头文件依赖,只有在对象真正构造时才需要引入头文件;    在第5行,定义QPropertyAnimation对象;    第6行定义一个bool量用来表明现在处于显示菜单状态还是隐藏菜单状态。
代码清单09-15-08
mainwindow.cppCMainWindow::CMainWindow(QWidget* parent) :
    QMainWindow(parent),
    m_pAnimaMenuShow(NULL),
    m_bShowMenu(true)
{
    // ......
}
在代码清单09-15-08中,在CMainWindow的构造函数的初始化列表中对新增的两个变量初始化。代码清单09-15-09
mainwindow.cppvoid CMainWindow::slot_fileMenu()
{
    static int s_MenubarHeight = menuBar()->height();
    int s_MenuWidth = m_pFileMenu->width();
    if (NULL == m_pAnimaMenuShow)    {
      m_pAnimaMenuShow = new QPropertyAnimation(m_pFileMenu, "pos");
      connect(m_pAnimaMenuShow, SIGNAL(finished()), this, SLOT(slot_animationMenuFinished()));
      m_pAnimaMenuShow->setDuration(400);
      m_pAnimaMenuShow->setEasingCurve(QEasingCurve::Linear);
    }
    if (m_bShowMenu) { // 单击时弹出菜单
      m_pFileMenu->show();
      int offsetY = s_MenubarHeight + m_pEditToolBar->height();
      m_pAnimaMenuShow->setStartValue(QPoint(x()- s_MenuWidth, y()+offsetY));
      m_pAnimaMenuShow->setEndValue(QPoint(x(), y() + offsetY));
      m_pAnimaMenuShow->start();
   }
    else   { // 再次单击时隐藏菜单
         int offsetY = s_MenubarHeight + m_pEditToolBar->height();
      m_pAnimaMenuShow->setStartValue(QPoint(x(), y() + offsetY));
      m_pAnimaMenuShow->setEndValue(QPoint(x()- s_MenuWidth, y()+offsetY));
      m_pAnimaMenuShow->start();
   }
}

代码清单09-15-09是本节的核心。    第3行的代码是为了保存menubar的高度,方便后续使用:因为计算菜单的弹出位置时需要用到,以便将菜单在menubar和工具栏的下方弹出;    第4行将菜单的宽度保存到一个变量中,同样是为了后面方便计算;    第5~11行用来初始构建动画对象,其中:      第6行构建m_pAnimaMenuShow,并提供了操作对象m_pFileMenu和操作的属性"pos"(也就是位置,我们通过修改菜单的位置实现动画移动菜单的效果)      第7~8行将动画对象的finished信号绑定到槽函数,以便动画结束时执行额外的操作;      第9行设置了动画持续时间,单位是ms(毫秒);      第10行设置了动画进展曲线为QEasingCurve::Linear(与时间成线性关系);    第12~19行规定了显示菜单时的动画参数:      第13行先将菜单显示出来(因为,关闭菜单时会将菜单隐藏);      第14行计算高度偏移量,菜单弹出时应该在menubar(标题栏)和工具栏的下方;      第15行,设置动画的起始值,也就是菜单的"pos"(构造动画对象时的第二个参数,见第6行),将其设置为菜单默认位置向左、向下移动指定偏移量。读者如果不明白,可以尝试仅使用x()、y()看看效果。      第17行指明动画结束时,菜单的位置。    第20~26行设置了菜单隐藏时的动画参数,跟菜单显示时正好相反,在此不再赘述。
    下面,我们看一下动画结束时的槽函数。
代码清单09-15-10
mainwindow.cppvoid CMainWindow::slot_animationMenuFinished()
{
    if (!m_bShowMenu) {
      m_pFileMenu->hide();
    }
    m_bShowMenu = !m_bShowMenu;
}
    代码清单09-15-10是动画结束时触发的槽函数,在该槽函数中,首先判断是否需要将菜单隐藏,然后将变量m_bShowMenu取反,以便达到显示-隐藏-显示-隐藏的循环效果。
    最后,为了让界面看起来更酷,我们使用了样式文件,样式不是本节的重点,我们仅列出代码:代码清单09-15-11
main.cppint main(int argc, char * argv[])
{
    // ......
    QFile file(":/qss/black.qss");
    bool bok = file.open(QFile::ReadOnly);
    if (bok) {
      QString styleSheet = QString::fromLatin1(file.readAll());
      qApp->setStyleSheet(styleSheet);
    }
    file.close();
    return app.exec();
}

结语------------------------------------------------------------------------------------
本文介绍了使用QPropertyAnimation实现菜单以动画效果显示、隐藏的方法,重点在代码清单09-15-09中:QPropertyAnimation对象的构造、设置起止参数、设置持续时间等。
页: [1]
查看完整版本: KS09-15 菜单-动画效果