qt开关控件设计(手把手从零开始)

1 说明

1.1 显示效果

显示效果看下面的动图:
(1)普通模式,一般都是用这种模式就行
在这里插入图片描述
(2)等待模式,有的比较耗时且不确定是否能够开启的操作使用该模式
在这里插入图片描述

1.2 控件特性

具体特性如下图所示:
在这里插入图片描述

1.3 设计方法

实现自定义控件的方法有很多,除了自己绘制外,还可以使用qt的控件+样式表堆一个新的控件。
我现在只推荐继承qwidget类进行绘制,虽然前期可能麻烦点,原因如下:

  • 需要高度自定义,很多细节把控;
  • 后续功能和样式拓展方便灵活;
  • 执行效率;
  • 通用性和独立性;
  • 项目如果非常复杂庞大的话,样式表将会是个非常忌讳的东西。

在后文提供一种控件的通用设计思路,可供参考。

2 控件需求分析

需求分析主要是要明确控件做成什么样子,从而在开发中避免做无用功,最主要的是防止设计方向出现偏差导致漏掉的需求很难添加上去。
这个步骤还是非常有必要的,在我们的工作项目中开发做需求分析后,一个是可以对工作上做风险把控,确定工作难度和影响,另外一个就是可以做好时间规划,防止规划工作的时间过长或过短,最后就是这个过程可以顺便把概要设计完成,总之益处多多。

2.1 必要需求

(1)控件开、关状态;
(2)开、关、圆形按钮颜色自定义;
(3)圆形按钮带移动动画;
(4)添加等待模式;
(5)圆形按钮阴影;
(6)可获取开关状态;
(7)状态切换后发出信号。

2.1 顺带需求(锦上添花)

(1)自适应缩放,就是可大可小;
(2)边缘颜色自定义(一般为透明就行);
(3)颜色渐变动画;
(4)添加使、失能状态。

3 功能设计

3.1 设计思路(重点内容)

对于自定义控件实现中,每个功能的支持,在代码上我们一般可总结为接口、操作、数据、绘制几个部分,关系如下图:
在这里插入图片描述
使用失能状态为例子:
(1)首先是数据支持,每个功能第一步应该是需要构建好支持的数据,这样就有个中心,接下来围绕这个中心进行开发,从上图可以看出来,最后数据最好有个默认初始状态。

bool mEnable{1};    //使能状态

(2)、读取、写入接口,接口定义可以优于开发的,一般在项目需求分析的时候定义好了。

    void setEnabled(bool enable);    /// 设置使能状态,default:1
    bool getEnabled();    /// 获取使能状态

(3)操作,由于这个功能非常简单,所以基本没有什么操作,所以直接写在接口函数内部,如下:

void WBSwitchButton::setEnabled(bool enable){
    QWidget::setEnabled(enable);
    mEnable = enable;
    emit sigEnableChanged(mEnable);
    update();
}

(3-2)操作相关影响,失能状态下是不可点击切换开关状态的,所以在点击事件中进行过滤,代码如下:

    if(!mEnable)    return ;

注:这段代码需要写在事件函数最前面。
(4)绘制,这步主要是要在显示上区分使能和失能,我的思路是直接在按钮上面盖上一层暗色的蒙层,表示处于失能状态,代码如下所示:

    /// 失能显示,添加一层暗色的蒙层
    if(!mEnable){
        QColor disable(Qt::black);
        disable.setAlphaF(0.5);
        painter.setBrush(disable);
        painter.drawRoundedRect(this->rect(),mRadius,mRadius);
    }

注:这部分代码是写在绘制函数最后面。
失能效果如下,可对比前面的使能状态。
在这里插入图片描述

3.2 自适应大小

因为控件按照上面的思路设计进行设计,所以在自适应大小的时候,不涉及到接口和操作,直接调整(·计算)背后支持的数据即可,然后直接update()重新绘制一下。

void WBSwitchButton::resizeEvent(QResizeEvent *event){
    Q_UNUSED(event)
    /// 更新按钮大小、圆角大小、动画两个位置
    int size = qMin(this->width(),this->height());
    mRadius = size/2;
    float width = size * 3 / 4;
    float border = (size - width) / 2;
    mLeftPos = QPoint(border,border);
    mRightPos = QPoint(this->width() - border - width,border);
    mButtonRect.setWidth(width);
    mButtonRect.setHeight(width);
    mButtonRect.moveTo(mOnOff ? mRightPos : mLeftPos);
    mBackColor = mOnOff ? mBackOnColor : mBackOffColor ;
    update();
}

3.3 开关动画

对于动画,其实都是属于设计思路中的操作部分,此部分只要使用qt的动画对象动态修改数据即可,位置动画就修改按钮位置,颜色动画就修改颜色位置,具体代码如下:
开关按钮位置移动动画

    /// 动画-开关按钮位置
    QVariantAnimation* posAnimation = new QVariantAnimation(this);
    posAnimation->setDuration(mAnimationPeriod);
    posAnimation->setStartValue(mButtonRect.topLeft());
    posAnimation->setEndValue(mOnOff ?  mRightPos : mLeftPos);
    connect(posAnimation,&QPropertyAnimation::valueChanged,[=](const QVariant &value){
        mButtonRect.moveTo(value.toPointF());
        update();
    });
    posAnimation->start(QAbstractAnimation::DeletionPolicy::DeleteWhenStopped);   //停止后删除

开关状态颜色渐变动画

    /// 动画-背景颜色
    QPropertyAnimation * colorAnimation = new QPropertyAnimation(this,"pBackColor");
    colorAnimation->setDuration(mAnimationPeriod);
    colorAnimation->setStartValue(mBackColor);
    colorAnimation->setEndValue(mOnOff ?  mBackOnColor: mBackOffColor);
    colorAnimation->start(QAbstractAnimation::DeletionPolicy::DeleteWhenStopped);   //停止后删除

3.4 控件绘制

从设计思路中可以看出,我们的绘制主要按照外观,读取支持的数据直接绘制即可,这样的好处就是绘制中不带复杂的操作逻辑(比如动画)和计算操作,防止每次绘制都会进行没必要的重复计算。

绘制函数只拿数据在必要的时候进行绘制,这样大大提高控件的执行效率和流畅度(本控件可能看不出来,大的复杂控件就能体验出来)。

void WBSwitchButton::paintEvent(QPaintEvent *event){
    Q_UNUSED(event)
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing, true);	//抗锯齿
    painter.setPen(Qt::NoPen);

    /// 绘制边缘颜色
    QPainterPath path;
    path.addRect(this->rect());
    path.addRoundedRect(this->rect(),mRadius,mRadius);
    path.setFillRule(Qt::OddEvenFill);
    painter.setBrush(mEdgeColor);
    painter.drawPath(path);

    /// 绘制背景颜色
    painter.setBrush(mBackColor);
    painter.drawRoundedRect(this->rect(),mRadius,mRadius);

    /// 绘制圆形按钮
    painter.setBrush(mButtonColor);
    painter.drawEllipse(mButtonRect);

    /// 绘制按钮阴影
    painter.setBrush(Qt::NoBrush);
    QColor color(Qt::black);
    int count = (this->height() - mButtonRect.height())/2;
    float stepColor = (0.15-0.0)/count;
    for (int i = mButtonRect.height()/2 + 1; i < this->height()/2; i++){
        color.setAlphaF(0.15 - stepColor*(i - mButtonRect.height()/2));
        painter.setPen(color);
        painter.drawEllipse(mButtonRect.center(),i,i);
    }

    /// 失能显示,添加一层蒙层
    if(!mEnable){
        QColor disable(Qt::black);
        disable.setAlphaF(0.5);
        painter.setBrush(disable);
        painter.drawRoundedRect(this->rect(),mRadius,mRadius);
    }
}

注:可以重点看一下上面的绘制阴影实现思路。

4 总体代码

(1)h文件

#ifndef WBSWITCHBUTTON_H
#define WBSWITCHBUTTON_H

#include <QWidget>
#include <QPropertyAnimation>
#include <QPainterPath>
#include <QPainter>
#include <QRadialGradient>
#include <QMouseEvent>

///
/// \brief 基础控件-Switch开关按钮
///
class WBSwitchButton : public QWidget
{
    Q_OBJECT
public:
    Q_PROPERTY(QColor pBackColor MEMBER mBackColor)  //新增背景颜色属性,用于动画

    explicit WBSwitchButton(QWidget *parent = nullptr);
    bool getSwitch();   /// 获取开关状态

public slots:
    void setSwitch(bool onoff);    /// 设置开关状态,default:0
    void setEnabled(bool enable);    /// 设置使能状态,default:1
    bool getEnabled();    /// 获取使能状态
    void setAnimationPeriod(int period);    /// 设置切换状态周期
    void setPrecisionClick(bool flag);    /// 设置精确点击,即只有点中按钮的时候才开关
    void setWaitModel(bool flag);    /// 设置等待模式,点击后不会主动切换开关,需要setSwitch
    void setSwitchForWaitModel(bool onoff);    /// 设置开关状态,default:0

    void setButtonColor(QColor color);    /// 设置开关(圆形按钮)颜色
    void setBackOnColor(QColor color);    /// 设置背景颜色-开
    void setBackOffColor(QColor color);    /// 设置背景颜色-关
    void setEdgeColor(QColor color);    /// 设置边缘颜色,默认透明

signals:
    void sigEnableChanged(bool enable);    /// 使能状态变化信号
    void sigSwitchChanged(bool onoff);    /// 开关状态变化信号

protected:
    void paintEvent(QPaintEvent *event);
    void resizeEvent(QResizeEvent *event);
    void mouseReleaseEvent(QMouseEvent *event);
    void enterEvent(QEvent *event);
    void leaveEvent(QEvent *event);

private:
    bool mOnOff{0};     //开关状态
    bool mEnable{1};    //使能状态
    bool mPrecisionClickFlagh{0};   //精确点击标志位
    bool mWaitSigModel{1};  //等待模式,点击后按钮位置会进行切换,但是颜色需要等待外部信号变动
    bool mAnimationOnOff{1};    //动画开关,default:1
    bool mHover{0};

    QColor mButtonColor{Qt::white};    //开关(圆形按钮)颜色
    QColor mBackColor{Qt::red};
    QColor mEdgeColor{Qt::transparent};  //边缘颜色
    QRectF mButtonRect; //开关按钮rect
    int mRadius{8}; // 开关外观边缘圆角

    int mAnimationPeriod{200};  //动画周期
    QPointF mRightPos;       // 动画位置-开
    QPointF mLeftPos;        // 动画位置-关
    QColor mBackOnColor{Qt::green};  //背景颜色-开
    QColor mBackOffColor{Qt::darkGray};  //背景颜色-关
};

#endif // WBSWITCHBUTTON_H

(2)cpp文件

#include "wbswitchbutton.h"
#include <QDebug>

WBSwitchButton::WBSwitchButton(QWidget *parent)
    : QWidget{parent}
{

}

bool WBSwitchButton::getSwitch(){
    return mOnOff;
}

void WBSwitchButton::setSwitch(bool onoff){
    if(mWaitSigModel) return ;
    /// 状态切换
    mOnOff = onoff;
    /// 发送信号
    sigSwitchChanged(mOnOff);
    /// 动画-背景颜色
    QPropertyAnimation * colorAnimation = new QPropertyAnimation(this,"pBackColor");
    colorAnimation->setDuration(mAnimationPeriod);
    colorAnimation->setStartValue(mBackColor);
    colorAnimation->setEndValue(mOnOff ?  mBackOnColor: mBackOffColor);
    colorAnimation->start(QAbstractAnimation::DeletionPolicy::DeleteWhenStopped);   //停止后删除
    /// 动画-开关按钮位置
    QVariantAnimation* posAnimation = new QVariantAnimation(this);
    posAnimation->setDuration(mAnimationPeriod);
    posAnimation->setStartValue(mButtonRect.topLeft());
    posAnimation->setEndValue(mOnOff ?  mRightPos : mLeftPos);
    connect(posAnimation,&QPropertyAnimation::valueChanged,[=](const QVariant &value){
        mButtonRect.moveTo(value.toPointF());
        update();
    });
    posAnimation->start(QAbstractAnimation::DeletionPolicy::DeleteWhenStopped);   //停止后删除
}

void WBSwitchButton::setSwitchForWaitModel(bool onoff)
{
    if(!mWaitSigModel) return ;
    if(mOnOff == onoff){
        /// 表示值未改变先运行按钮位置动画
        QVariantAnimation* posAnimation = new QVariantAnimation(this);
        posAnimation->setDuration(mAnimationPeriod);
        posAnimation->setStartValue(mOnOff ? mLeftPos : mRightPos);
        posAnimation->setEndValue(mOnOff ?  mRightPos : mLeftPos);
        connect(posAnimation,&QVariantAnimation::valueChanged,[=](const QVariant &value){
            mButtonRect.moveTo(value.toPointF());
            update();
        });
        posAnimation->start(QAbstractAnimation::DeletionPolicy::DeleteWhenStopped);   //停止后删除
        return ;
    }
    /// 状态切换
    mOnOff = onoff;
    /// 发送信号
    sigSwitchChanged(mOnOff);
    /// 后运行背景颜色动画
    QPropertyAnimation * colorAnimation = new QPropertyAnimation(this,"pBackColor");
    colorAnimation->setDuration(mAnimationPeriod);
    colorAnimation->setStartValue(mBackColor);
    colorAnimation->setEndValue(mOnOff ?  mBackOnColor: mBackOffColor);
    colorAnimation->start(QAbstractAnimation::DeletionPolicy::DeleteWhenStopped);   //停止后删除
    connect(colorAnimation,&QPropertyAnimation::valueChanged,[=](const QVariant &value){
        update();
    });
}

void WBSwitchButton::setEnabled(bool enable){
    QWidget::setEnabled(enable);
    mEnable = enable;
    emit sigEnableChanged(mEnable);
    update();
}

void WBSwitchButton::setAnimationPeriod(int period){
    mAnimationPeriod = period;
}

void WBSwitchButton::setPrecisionClick(bool flag){
    mPrecisionClickFlagh = flag;
}

void WBSwitchButton::setWaitModel(bool flag)
{
    mWaitSigModel = flag;
}

void WBSwitchButton::setButtonColor(QColor color){
    mButtonColor = color;
    update();
}

void WBSwitchButton::setBackOnColor(QColor color){
    mBackOnColor = color;
    update();
}

void WBSwitchButton::setBackOffColor(QColor color){
    mBackOffColor = color;
    update();
}

void WBSwitchButton::setEdgeColor(QColor color){
    mEdgeColor = color;
    update();
}

void WBSwitchButton::paintEvent(QPaintEvent *event){
    Q_UNUSED(event)
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing, true);
    painter.setPen(Qt::NoPen);

    /// 绘制边缘颜色
    QPainterPath path;
    path.addRect(this->rect());
    path.addRoundedRect(this->rect(),mRadius,mRadius);
    path.setFillRule(Qt::OddEvenFill);
    painter.setBrush(mEdgeColor);
    painter.drawPath(path);

    /// 绘制背景颜色
    painter.setBrush(mBackColor);
    painter.drawRoundedRect(this->rect(),mRadius,mRadius);

    /// 绘制圆形按钮
    painter.setBrush(mButtonColor);
    painter.drawEllipse(mButtonRect);

    /// 绘制按钮阴影
    painter.setBrush(Qt::NoBrush);
    QColor color(Qt::black);
    int count = (this->height() - mButtonRect.height())/2;
    float stepColor = (0.15-0.0)/count;
    for (int i = mButtonRect.height()/2 + 1; i < this->height()/2; i++){
        color.setAlphaF(0.15 - stepColor*(i - mButtonRect.height()/2));
        painter.setPen(color);
        painter.drawEllipse(mButtonRect.center(),i,i);
    }

    /// 失能显示,添加一层蒙层
    if(!mEnable){
        QColor disable(Qt::black);
        disable.setAlphaF(0.5);
        painter.setBrush(disable);
        painter.drawRoundedRect(this->rect(),mRadius,mRadius);
    }
}

void WBSwitchButton::resizeEvent(QResizeEvent *event){
    Q_UNUSED(event)
    /// 更新按钮大小、圆角大小、动画两个位置
    int size = qMin(this->width(),this->height());
    mRadius = size/2;
    float width = size * 3 / 4;
    float border = (size - width) / 2;
    mLeftPos = QPoint(border,border);
    mRightPos = QPoint(this->width() - border - width,border);
    mButtonRect.setWidth(width);
    mButtonRect.setHeight(width);
    mButtonRect.moveTo(mOnOff ? mRightPos : mLeftPos);
    mBackColor = mOnOff ? mBackOnColor : mBackOffColor ;
    update();
}

void WBSwitchButton::mouseReleaseEvent(QMouseEvent *event){
    if(mWaitSigModel){
        /// 先运行按钮位置动画
        QVariantAnimation* posAnimation = new QVariantAnimation(this);
        posAnimation->setDuration(mAnimationPeriod);
        posAnimation->setStartValue(mOnOff ? mRightPos : mLeftPos);
        posAnimation->setEndValue(mOnOff ?  mLeftPos : mRightPos);
        connect(posAnimation,&QVariantAnimation::valueChanged,[=](const QVariant &value){
            mButtonRect.moveTo(value.toPointF());
            update();
        });
        posAnimation->start(QAbstractAnimation::DeletionPolicy::DeleteWhenStopped);   //停止后删除
        return ;
    }
    if(!mEnable)    return ;
    if(mButtonRect.contains(event->pos()) || !mPrecisionClickFlagh){
        setSwitch(!mOnOff);
    }
}

void WBSwitchButton::enterEvent(QEvent *event){
    Q_UNUSED(event)
    mHover = true;
}

void WBSwitchButton::leaveEvent(QEvent *event){
    Q_UNUSED(event)
    mHover = false;
}

  • 14
    点赞
  • 61
    收藏
    觉得还不错? 一键收藏
  • 13
    评论
Qt开关按钮可以通过使用QPainter绘制来实现自定义的外观。可以根据需求设置开关背景色、圆形按钮背景色以及状态切换动画时长。可以使用如下代码示例来创建和设置开关按钮的状态和样式: ``` SwitchControl *MRSwitchControl = new SwitchControl(this); MRSwitchControl->move(570,735); // 设置开关按钮的位置 MRSwitchControl->setToggled(true); // 设置按钮的初始状态为开 connect(MRSwitchControl, &SwitchControl::toggled, this, &SystemSetting::setMaskRecog); // 设置按钮状态切换时的槽函数 ``` 这段代码创建了一个名为`MRSwitchControl`的SwitchControl对象,并设置了它的位置和初始状态。当按钮的切换状态改变时,会调用SystemSetting类中的`setMaskRecog`槽函数来处理相应的逻辑操作。通过自定义SwitchControl类的绘制函数,你可以在界面上显示出你想要的开关按钮图片。<span class="em">1</span><span class="em">2</span> #### 引用[.reference_title] - *1* [基于Qt的仿手机开关按钮](https://download.csdn.net/download/qq_40162965/13065266)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [Qt自定义开关按钮控件](https://blog.csdn.net/nchu_zhangyiqing/article/details/109715135)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值