【Qt C++自绘制界面音乐播放器-8】播放控制区-旋转的logo和播放进度

48 阅读5分钟

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

GammaRay源码地址

本项目代码地址

源码

播放控制区

总共4个部分

1.最左边圆形logo区域,可自动旋转

2.播放进度条

3.暂停,开始按钮

4.音量控制

1.旋转的Logo

主要步骤如下

1.将图片绘制成圆形

2.边上绘制一个暗色的圆环,圆心绘制一个白色的小圆圈

3.启动定时器,使图片按一定速度旋转

绘制这个控件相对简单,唯一需要注意的是,绘制的起始点在左上角,要想让圆形图片绕中心旋转,需要对坐标有所偏移

首先要移动绘制中心,从蓝色点移动到红色点,移动前,蓝色点的坐标为(0,0),移动之后,红色点为(0,0),在此时,蓝色点将变为(-宽度/2,-高度/2).

class RotateCover : public QWidget
{
    Q_OBJECT
public:
    // size: 设置当前控件的大小
    // url: 设置图片的资源地址
    explicit RotateCover(int size, const QString& url, QWidget *parent = nullptr);

    void paintEvent(QPaintEvent *event) override;

private:
    // 保存图片
    QPixmap pixmap_;
    // 定时器,用来更新旋转角度
    QTimer* rotate_timer_ = nullptr;
    // 当前的旋转角度
    float rotate_angel_ = 0;

};
RotateCover::RotateCover(int size, const QString& url, QWidget *parent) : QWidget(parent) {
    // 设置widget大小
    setFixedSize(QSize(size, size));
    // 加载图片并缩放到widget相同的尺寸
    QImage image;
    image.load(url);
    pixmap_ = QPixmap::fromImage(image);
    pixmap_ = pixmap_.scaled(size, size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
   
    // 启动定时器,每隔17ms增加0.8度
    rotate_timer_ = new QTimer(this);
    connect(rotate_timer_, &QTimer::timeout, this, [=]() {
        rotate_angel_ += 0.8f;
        repaint();
    });
    rotate_timer_->start(17);
}

void RotateCover::paintEvent(QPaintEvent *event) {
    QPainter painter(this);

    int pen_width = 4;
    QPen pen;
    pen.setWidth(pen_width);
    pen.setColor(QColor(0x334466));
    painter.setPen(pen);
    painter.setRenderHint(QPainter::RenderHint::Antialiasing);

    painter.save();
    // 移动到中心
    painter.translate(this->width()/2, this->height()/2);
    // 旋转一定的角度
    painter.rotate(rotate_angel_);

    QPainterPath path;
    QRect circle_image_rect;
    //注意此时的X Y坐标,
    circle_image_rect.setX(-this->width()/2);
    circle_image_rect.setY(-this->width()/2);
    circle_image_rect.setWidth(this->width());
    circle_image_rect.setHeight(this->height());
    path.addRoundedRect(circle_image_rect, this->width()/2, this->height()/2);
    painter.setClipPath(path);
    // 绘制图片,注意此时的X Y坐标
    painter.drawPixmap(-this->width()/2, -this->width()/2, pixmap_);
    // 执行restore之后,绘制原点就变回左上角了,接下来按普通绘制即可。
    painter.restore();

    // 绘制一个圆形的边框
    QRect border_rect(pen_width/2, pen_width/2, this->width()-pen_width, this->height()-pen_width);
    painter.drawRoundedRect(border_rect, border_rect.width()/2, border_rect.height()/2);

    // 中心绘制一个小的圆形
    int inner_circle_width = 12;
    painter.setPen(Qt::NoPen);
    painter.setBrush(QBrush(QColor(0xffffff)));
    painter.drawEllipse(QPointF(this->width()/2, this->height()/2), inner_circle_width, inner_circle_width);

}

2.播放进度

主要步骤如下

1.绘制横向的长条,颜色是未激活的底色

2.绘制已经走过的区域,长条变为激活的颜色

3.绘制可以拖动的圆形按钮,颜色为激活的颜色

paintEvent和各种鼠标事件已经多次使用,不在赘述

class Slider : public QWidget
{
    Q_OBJECT
public: 
    // nc 未激活的颜色
    // ac 已经激活的颜色
    Slider(int nc, int ac, QWidget *parent = nullptr);

    void paintEvent(QPaintEvent *event) override;
    void enterEvent(QEvent *event) override;
    void leaveEvent(QEvent *event) override;
    void mousePressEvent(QMouseEvent *event) override;
    void mouseMoveEvent(QMouseEvent *event) override;
    void mouseReleaseEvent(QMouseEvent *event) override;
    //设置当前的进度,0~100
    void SetCurrentProgress(int percent);

private:

    int normal_color_;
    int active_color_;

    bool enter_ = false;
    bool mouse_pressed_ = false;
    // 存储当前圆形按钮的位置,鼠标点击,拖动后会更新
    int circle_handle_x_offset_ = 0;

};
Slider::Slider(int nc, int ac, QWidget *parent) : QWidget(parent) {
    this->normal_color_ = nc;
    this->active_color_ = ac;
    setMouseTracking(true);
}

void Slider::paintEvent(QPaintEvent *event) {
    QPainter painter(this);
    painter.setRenderHint(QPainter::RenderHint::Antialiasing);

    int circle_handle_radius = 6;
    int slider_height = 2;
    painter.setPen(Qt::NoPen);
    painter.setBrush(QBrush(QColor(normal_color_)));

    int slider_x_offset = 0;
    int slider_y_offset = (this->height() - slider_height)/2;
    // 绘制一条横向的长条,占满整个widget的宽度,颜色是未激活的底色
    painter.drawRect(slider_x_offset, slider_y_offset, this->width(), slider_height);

    // 绘制一个从0到circle_handle_x_offset_的长条,为激活的颜色,覆盖一部分未激活的区域,代表已经走过了这段距离
    painter.setBrush(QBrush(QColor(active_color_)));
    painter.drawRect(slider_x_offset, slider_y_offset, circle_handle_x_offset_, slider_height);

    QPen pen;
    pen.setColor(QColor(0xcccccc));
    pen.setWidth(1);
    painter.setPen(pen);
    // 绘制一个圆形的按钮,刚好在当前的进度所在的位置
    painter.drawEllipse(QPointF(circle_handle_x_offset_, this->height()/2), circle_handle_radius, circle_handle_radius);

}

void Slider::enterEvent(QEvent *event) {
    enter_ = true;
    repaint();
}

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

//鼠标按下,更新按钮当前的坐标
void Slider::mousePressEvent(QMouseEvent *event) {
    mouse_pressed_ = true;
    circle_handle_x_offset_ = event->pos().x();
    repaint();
}

//鼠标按下且正在移动,更新按钮当前的坐标
void Slider::mouseMoveEvent(QMouseEvent *event) {
    if (mouse_pressed_) {
        circle_handle_x_offset_ = event->pos().x();
        repaint();
    }
}

void Slider::mouseReleaseEvent(QMouseEvent *event) {
    mouse_pressed_ = false;
    repaint();
}

// 将百分比,映射到x坐标上,然后绘制
void Slider::SetCurrentProgress(int percent) {
    percent = std::min(100, percent);
    circle_handle_x_offset_ = percent * 1.0f / 100.0f * this->width();
    repaint();
}

3.统一管理这个区域的控件

定义一个PlayerController类,用来同一存放这几个控件

class PlayController : public QWidget
{
    Q_OBJECT
public:
    explicit PlayController(QWidget *parent = nullptr);

private:
    // 旋转的Logo
    RotateCover* rotate_cover_ = nullptr;
    // 标题
    QLabel* title_ = nullptr;
    // 播放进度控制
    Slider* slider_ = nullptr;
};

这个就比较简单了,直接添加到布局中即可。

PlayController::PlayController(QWidget *parent) : QWidget(parent) {
    auto root_layout = new QHBoxLayout();
    LayoutHelper::ClearMarginSpacing(root_layout);

    root_layout->addSpacing(2);
    {
        auto item_layout = new QVBoxLayout();
        LayoutHelper::ClearMarginSpacing(item_layout);
        rotate_cover_ = new RotateCover(60, ":/images/resources/preset_1.jpg", this);
        item_layout->addStretch();
        item_layout->addWidget(rotate_cover_);
        item_layout->addStretch();

        root_layout->addSpacing(20);
        root_layout->addLayout(item_layout);
    }
    {
        auto item_layout = new QVBoxLayout();
        LayoutHelper::ClearMarginSpacing(item_layout);
        title_ = new QLabel();
        title_->setText("海浪之声");
        title_->setStyleSheet("font-family:Microsoft YaHei; font-size: 15px; font-weight: bold;");
        item_layout->addSpacing(15);
        item_layout->addWidget(title_);

        slider_ = new Slider(0xbbbbbb, 0x334466, this);
        slider_->setFixedHeight(30);
        slider_->setMinimumWidth(450);
        slider_->SetCurrentProgress(50);
        item_layout->addSpacing(10);
        item_layout->addWidget(slider_);

        item_layout->addStretch();

        root_layout->addSpacing(20);
        root_layout->addLayout(item_layout);
    }

    root_layout->addStretch();

    setLayout(root_layout);
}

4.添加到MainWindow中,放在声音素材展示区下面

MainWindow::MainWindow(QWidget *parent)
    : QWidget(parent) {
...
    title_bar_ = new TitleBar(this);
    side_bar_ = new SideBar(this);
    // 生成
    play_controller_ = new PlayController();

    auto right_content_layout = new QVBoxLayout();
    LayoutHelper::ClearMarginSpacing(right_content_layout);

    content_area_ = new QScrollArea();
    right_content_layout->addWidget(content_area_);

    //添加进去
    play_controller_->setFixedHeight(90);
    right_content_layout->addWidget(play_controller_);
    right_content_layout->addStretch();
...
}

最后运行