【Qt C++自绘制界面音乐播放器-6】添加左侧控制栏中的按钮

28 阅读4分钟

最新大型开源项目-云游戏,云桌面系统,欢迎关注

GammaRay源码地址

本项目代码地址

源码

1.单个按钮

我们使用QWidget自己绘制按钮,不使用QPushButton,可以做更漂亮的效果和更灵活的使用。

下图表示按钮的2个状态,左边为未选中状态,右边为选中状态。

2.实现

实现一个SideBarItem类,继承自QWidget。因为之前的文章已经介绍过鼠标进入,离开,按下,抬起以及绘制的事件,我们就直接实现这些方法。

我们希望点击之后,能有一个回调,因此也设置一个回调函数。

// 回调函数定义
using SideBarItemClickCallback = std::function<void()>;

class SideBarItem : public QWidget {
public:
    // 将未选中和选中的颜色传进来。并且把要显示的文字也传进来。
    SideBarItem(int nc, int ec, const QString& title, QWidget* parent = nullptr);
    ~SideBarItem() = default;

    // 实现以下几个方法
    // 绘制
    void paintEvent(QPaintEvent *event) override;
    // 鼠标进入
    void enterEvent(QEvent *event) override;
    // 鼠标离开
    void leaveEvent(QEvent *event) override;
    // 鼠标按下
    void mousePressEvent(QMouseEvent *event) override;
    // 鼠标抬起
    void mouseReleaseEvent(QMouseEvent *event) override;
    
    // 设置回调函数
    void SetOnClickCallback(SideBarItemClickCallback&& cbk);

private:
    // 保存回调函数
    SideBarItemClickCallback click_cbk_;
    
    // 未选中的按钮颜色
    int normal_color_ = 0xcc0000;
    // 选中的按钮颜色
    int enter_color_ = 0xdd0000;
    // 鼠标状态
    bool enter_ = false;
    bool pressed_ = false;
    // 为了相对美观一点,左右2侧,流几个像素的空白
    int left_right_padding_ = 8;
    // 要显示的文字
    QString title_ = "";

};
SideBarItem::SideBarItem(int nc, int ec, const QString& title, QWidget* parent) : QWidget(parent) {
    //保存信息
    this->normal_color_ = nc;
    this->enter_color_ = ec;
    this->title_ = title;
}

void SideBarItem::paintEvent(QPaintEvent *event) {
    QPainter painter(this);
    painter.setRenderHint(QPainter::RenderHint::Antialiasing);
    painter.setPen(Qt::NoPen);
    // 鼠标进入和离开不同的状态下,设置不同的画刷,设置不同的颜色
    if (enter_) {
        painter.setBrush(QBrush(this->enter_color_));
    }
    else {
        painter.setBrush(QBrush(this->normal_color_));
    }

    // 将左右2侧的空隙减去,留一点空白,上下填满即可
    // 注意:因为是左右2侧都要留空隙,所以QRect的第3,4个参数,要减去2倍的padding值
    QRect inner_rect(left_right_padding_, 0, this->rect().width()-left_right_padding_*2, this->rect().height());
    // 我们需要圆角矩形的圆角大一些,达到高度的一半后,则可以绘制出一个半圆,可以自己调整需要的大小
    painter.drawRoundedRect(inner_rect, this->height()/2, this->height()/2);

    // 设置画笔和使用的字体,这种方式一定是自己系统上安装的字体,如果是自己定义的字体,需要自己加载。
    QPen pen;
    QFont font = QFont("Microsoft YaHei", 13, QFont::Bold, false);
    // 不同的状态下,设置不同的颜色
    if (enter_) {
        pen.setColor(this->normal_color_);
    }
    else {
        pen.setColor(this->enter_color_);
    }
    painter.setPen(pen);
    painter.setFont(font);
    // 将字体居中绘制
    painter.drawText(this->rect(), Qt::AlignCenter, title_);
}

// 不同的状态下分别设置不同的值,然后刷新UI
void SideBarItem::enterEvent(QEvent *event) {
    enter_ = true;
    repaint();
}

void SideBarItem::leaveEvent(QEvent *event) {
    enter_ = false;
    repaint();
}

void SideBarItem::mousePressEvent(QMouseEvent *event) {
    pressed_ = true;
    repaint();
}

// 鼠标抬起时,触发回调
void SideBarItem::mouseReleaseEvent(QMouseEvent *event) {
    pressed_ = false;
    repaint();
    if (this->click_cbk_) {
        this->click_cbk_();
    }
}

// 将回调函数保存起来
void SideBarItem::SetOnClickCallback(SideBarItemClickCallback&& cbk) {
    this->click_cbk_ = std::move(cbk);
}

3.添加选中,未选中的状态

因为时多个按钮排列在一起,所以要指定哪个按钮被按下了,同时其他的按钮要置为未选中的状态。

class SideBarItem : public QWidget {
public:
...
    void Select();
    void Unselect();
private:
...
    bool selected_ = false;
};
void SideBarItem::Select() {
    selected_ = true;
    repaint();
}

void SideBarItem::Unselect() {
    selected_ = false;
    repaint();
}

同时要将selected_的状态与界面关联起来,在这里我们将选中和鼠标进入设置为相同的状态。

void SideBarItem::paintEvent(QPaintEvent *event) {
...
    // 这里再加一个条件即可
    if (enter_ || selected_) {
        painter.setBrush(QBrush(this->enter_color_));
    }
    else {
        painter.setBrush(QBrush(this->normal_color_));
    }
...
    // 这里再加一个条件即可
    if (enter_ || selected_) {
        pen.setColor(this->normal_color_);
    }
    else {
        pen.setColor(this->enter_color_);
    }
...
}

4.添加到SideBar中

我们需要将4个按钮都保存起来,以便随时切换或者其他操作。SideBar添加一个vector保存它们。

因为4个按钮时互斥的,所以只有一个可以选中,所以再SideBar里控制这个逻辑

class SideBar : public QWidget
{
...
    // 控制选中哪个按钮
    void Select(int idx);
private:
    std::vector<SideBarItem*> side_bar_items_;
};

因为时互斥的,所以选中一个,另外三个必然是未选中的状态

void SideBar::Select(int idx) {
    if (idx < 0 || idx >= side_bar_items_.size()) {
        return;
    }

    for (int i = 0; i < side_bar_items_.size(); i++) {
        if (i == idx) {
            side_bar_items_[i]->Select();
        }
        else {
            side_bar_items_[i]->Unselect();
        }
    }
}

接下来修改SideBar的构造函数,添加四个按钮

SideBar::SideBar(QWidget *parent) : QWidget(parent) {
    this->setFixedSize(QSize(Settings::kSideBarWidth, 530));

    // item layout
    auto item_layout = new QVBoxLayout();
    LayoutHelper::ClearMarginSpacing(item_layout);
    // Logo ...

    item_layout->addSpacing(30);
    // 添加4个按钮到布局中
    // 还记得吗,用一对大括号包起来,相似的代码,看起来更直观。
    // 当然这里更好的做法是用for循环来做,现在是demo,所以写的更直观一点,方便理解。
    {
        auto item = new SideBarItem(Settings::kSideBarNormalColor, Settings::kSideBarEnterColor, "睡眠", this);
        item->setFixedSize(QSize(Settings::kSideBarWidth, 35));
        item_layout->addWidget(item);
        side_bar_items_.push_back(item);
        item->SetOnClickCallback([=]() {
            Select(0);
        });
    }
    {
        auto item = new SideBarItem(Settings::kSideBarNormalColor, Settings::kSideBarEnterColor, "专注",this);
        item->setFixedSize(QSize(Settings::kSideBarWidth, 35));
        item_layout->addSpacing(5);
        item_layout->addWidget(item);
        side_bar_items_.push_back(item);
        item->SetOnClickCallback([=]() {
            Select(1);
        });
    }
    //相同的代码,不再展示
    ...
    ...
    item_layout->addStretch();

    setLayout(item_layout);

    // 默认选中第一个
    Select(0);
}

现在运行代码,就可以看见效果了: