。。。先上一张效果图,感兴趣的就耐心往下看^_^
设计灵感参考了这篇文章。之前看了这篇文章,因为作者并未告诉实现代码,不得已,只能自己想办法搞一个。
之前以为直接用QConicalGradient就能实现效果了,实际操作之后,发现
如果控件的宽高相等(正方形),则只会在控件的拐角处,感觉特效的那个线段变长了,但如果,控件宽高差异很大时,离中心点越远的地方,效果越尴尬o(╯□╰)o
QConicalGradient是绕中心点旋转,最完美的效果应该是使用于圆形控件
要让边框动画看起来自然,需要解决的问题是能绘制出边框轮廓(提取轮廓好像QPainterPathStroker可以用),然后取其中一小段,绘制出来,本文就是这样的实现思路
直角的好实现,圆角的地方就稍微画个图顺一下就OK
废话不多说,代码就是我的思路,上代码
我也先贴个头
#ifndef BORDERWIDGET_H
#define BORDERWIDGET_H
#include <QWidget>
class QPropertyAnimation;
class QColor;
class BorderWidget :public QWidget
{
Q_OBJECT
Q_PROPERTY(qreal border_len READ BorderLength WRITE SetBorderLength)
public:
explicit BorderWidget(QWidget *parent= nullptr);
//特效占比
void SetEffectPercent(qreal percent){border_effect_percent_= percent;}
//边框颜色
void SetBorderColor(QColor cor){border_color_ = cor;}
//特效颜色
void SetEffectColor(QColor cor){border_effect_color_= cor;}
//边框宽度
void SetBorderWidth(int width){border_width_=width;}
//圆角半径
void SetBorderRadius (int radius){border_round_radius_ =radius;}
//特效使能
void SetEffectWork(bool effectwork);
//控件
void SetWidget(QWidget*);
signals:
public slots:
protected:
virtual void paintEvent(QPaintEvent *event);
qreal BorderLength() {return border_len_;}
void SetBorderLength(double v);
private:
QWidget* wdg_; //需要加特效的控件
qreal border_len_;
QPropertyAnimation* animation_;
qreal border_effect_percent_; //特效占比(0~1) 默认值0.08
QColor border_color_; //边框颜色 默认绿色
QColor border_effect_color_; //边框特效颜色 默认红色
int border_width_; //边框宽度 默认4
int border_round_radius_/*[4]*/;
//边框圆角半径 0时为直角 默认4
bool effect_work_; //默认true
};
#endif // BORDERWIDGET_H
不过,我还能贴个尾^_^
#include <QPainter>
#include <QDebug>
#include <QPropertyAnimation>
#ifndef MY_DBLEPSILON
#define MY_DBLEPSILON 1e-13
#endif
#ifndef MY_FLTEPSILON
#define MY_FLTEPSILON 1e-6
#endif
#ifndef PI
#define PI 3.14159265
#endif
//等于0.
bool DoubleIsZERO(double v)
{
if(v<0.)v*=-1.;
return v<=MY_DBLEPSILON;
}
bool DoubleLess(double d1, double d2)
{return d1-d2<0;}
bool DoubleLessEqual(double d1, double d2)
{
d1-=d2;
if(d1<0.) return true;
return DoubleIsZERO(d1);
}
QRect DrawRectOnGeometry(QRect geometry)
{
int off = 1;
geometry.adjust(off, off, -off,-off);
return geometry;
}
QRect EffectRectOnGeometry(QRect geometry, int border_width)
{
border_width =border_width/2;
geometry.adjust(border_width, border_width, -border_width,-border_width);
return geometry;
}
//用正弦获取角度
qreal DegreeOnSinX(qreal sinx)
{
if(DoubleLessEqual(1.,sinx))return 90.;//浮点运算出的结果有时距离/圆角半径的结果>1.0
return asin(sinx)*180./PI;
}
//从矩形中获取圆角矩形的路径
QPainterPath RoundPathOnRect(QRectF wdgrect, int border_round_radius)
{
qreal l= wdgrect.left(), t=wdgrect.top(), r=wdgrect.right(), b=wdgrect.bottom();
qreal arc_w=2*border_round_radius;
QPainterPath paintpath;
paintpath.moveTo(l+border_round_radius, t);
paintpath.lineTo(r - border_round_radius, t);
paintpath.arcTo(r-arc_w, t, arc_w, arc_w,90,-90.);
paintpath.lineTo(r, b-border_round_radius);
paintpath.arcTo(r-arc_w, b-arc_w, arc_w,arc_w, 0.,-90.);
paintpath.lineTo(l+border_round_radius, b);
paintpath.arcTo(l, b-arc_w,arc_w, arc_w,270.,-90.);
paintpath.lineTo(l, t+border_round_radius);
paintpath.arcTo(l, t, arc_w, arc_w,180.,-90.);
return paintpath;
}
//边界(圆角)
QPainterPath BorderEffectRoundRectPath(QRectF srcrect,
qreal start_percent,
qreal stop_percent,
int border_radius)
{
QPainterPath srcpath = RoundPathOnRect(srcrect, border_radius);
QPointF pt1 = srcpath.pointAtPercent(start_percent),pt2 =srcpath.pointAtPercent(stop_percent);
QPainterPath painterpath;
qreal l = srcrect.left(), t = srcrect.top(), r = srcrect.right(), b = srcrect.bottom();
qreal x1 = pt1.x(), y1 = pt1.y(), x2 = pt2.x(), y2 = pt2.y(), border_radius_=border_radius;
if(!DoubleIsZERO(x1-x2) &&
!DoubleIsZERO(y1-y2))
{
qreal start_deg= 0., end_deg =0.;
qreal pos_judge = 0., arc_width = 2.*border_radius_;
if(x1 < x2 &&
y1 < y2)
{//右上角
pos_judge = r - border_radius_;
painterpath.moveTo(pt1);
if(DoubleLessEqual(x1, pos_judge))
{
//起点在弧形左边
painterpath.lineTo(pos_judge,y1);
//终点在弧形上或弧形外都画弧
if(DoubleLess(pos_judge, x2))//终点在弧形上
{
end_deg = DegreeOnSinX((x2-pos_judge)/border_radius_);
painterpath.arcTo(r-arc_width, t, arc_width, arc_width, 90., -end_deg);
}
}
else//if(DoubleLess(pos_judge,x1)
{
//起点在弧形上
//终点在弧形上或外都画弧
start_deg = DegreeOnSinX((x1 -pos_judge)/border_radius_);
end_deg = DegreeOnSinX((x2-pos_judge)/border_radius_)-start_deg;
start_deg = 90.-start_deg;
painterpath.arcTo(r-arc_width, t, arc_width, arc_width, start_deg, -end_deg);
}
if(DoubleLess(t + border_radius_, y2))//y2 > t + border_radius_
{
painterpath.lineTo(x2, y2);//终点在弧形外需要多画一条线
}
}
else if(x1>x2&&
y1<y2)//右下角
{
pos_judge = b - border_radius_;
painterpath.moveTo(pt1);
if(DoubleLessEqual(y1, pos_judge))
{
painterpath.lineTo(x1, pos_judge);
if(DoubleLess(pos_judge, y2))
{
end_deg = DegreeOnSinX((y2-pos_judge)/border_radius_);
painterpath.arcTo(r-arc_width, b- arc_width, arc_width, arc_width, 0., -end_deg);
}
}
else
{
start_deg = DegreeOnSinX((y1 - pos_judge)/border_radius_);
end_deg = DegreeOnSinX((y2 - pos_judge)/border_radius_) - start_deg;
start_deg = 0. - start_deg;
painterpath.arcTo(r -arc_width, b -arc_width, arc_width, arc_width, start_deg, -end_deg);
}
if(DoubleLess(x2, r -border_radius_))painterpath.lineTo(x2,y2);
}
else if(x1>x2 &&
y1>y2)//左下角
{
pos_judge = l + border_radius_;
painterpath.moveTo(pt1);
if(DoubleLessEqual(pos_judge, y1))
{
painterpath.lineTo(pos_judge, y1);
if(DoubleLessEqual(x2, pos_judge))
{
end_deg = DegreeOnSinX((pos_judge-x2)/border_radius_);
painterpath.arcTo(l, b - arc_width, arc_width, arc_width, 270., -end_deg);
}
}
else
{
start_deg = DegreeOnSinX((pos_judge - x1)/border_radius_);
end_deg = DegreeOnSinX((pos_judge - x2)/border_radius_) - start_deg;
start_deg = 270.-start_deg;
painterpath.arcTo(l, b - arc_width, arc_width, arc_width,start_deg, -end_deg);
}
if(DoubleLess(y2, b - border_radius_))painterpath.lineTo(pt2);
}
else if(x1<x2&&
y1>y2)//左上角
{
pos_judge = t + border_radius_;
painterpath.moveTo(pt1);
if(DoubleLessEqual(pos_judge, y1))
{
painterpath.lineTo(x1, pos_judge);
if(DoubleLess(y2, pos_judge))
{
end_deg = DegreeOnSinX((pos_judge - y2)/border_radius_);
painterpath.arcTo(l, t, arc_width, arc_width, 180., -end_deg);
}
}
else
{
start_deg = DegreeOnSinX((pos_judge-y1)/border_radius_);
end_deg = DegreeOnSinX((pos_judge - y2)/border_radius_)-start_deg;
start_deg = 180.-start_deg;
painterpath.arcTo(l, t, arc_width, arc_width, start_deg, -end_deg);
}
if(DoubleLess( l+border_radius_,x2))painterpath.lineTo(pt2);
}
}
else
{
painterpath.moveTo(pt1);
painterpath.lineTo(pt2);
}
return painterpath;
}
//边界(直角)
QPainterPath BorderEffectRectPath(QRectF srcrect,
qreal start_percent,
qreal stop_percent)
{
QPainterPath srcpath;
srcpath.addRect(srcrect);
QPointF pt1 = srcpath.pointAtPercent(start_percent), pt2 = srcpath.pointAtPercent(stop_percent);
QPainterPath painterpath;
qreal x1 = pt1.x(), y1= pt1.y(), x2 = pt2.x(), y2 = pt2.y();
painterpath.moveTo(pt1);
if(!DoubleIsZERO(x1-x2)&&
!DoubleIsZERO(y1-y2))
{
if(x1<x2&&
y1<y2)//右上角
{
painterpath.lineTo(x2, y1);
}
else if(x1>x2&&
y1<y2)//右下角
{
painterpath.lineTo(x1,y2);
}
else if(x1>x2&&
y1>y2)//左下角
{
painterpath.lineTo(x2,y1);
}
else if(x1<x2&&
y1>y2)
{
painterpath.lineTo(x1,y2);
}
}
painterpath.lineTo(pt2);
return painterpath;
}
BorderWidget::BorderWidget(QWidget*parent):QWidget(parent),
wdg_(nullptr),
border_len_(0.),
animation_(nullptr),
border_effect_percent_(0.08),
border_color_(QColor(0,255,0)),
border_effect_color_(QColor(255,0,0)),
border_width_(4),
border_round_radius_(4),
effect_work_(true)
{
animation_ = new QPropertyAnimation(this);
}
void BorderWidget::SetEffectWork(bool effectwork)
{
effect_work_ = effectwork;
if(wdg_&&
animation_)
{
if(effect_work_)
{
if(animation_->state()==QAbstractAnimation::Stopped)animation_->start();
}
else if(animation_->state()!=QAbstractAnimation::Stopped)animation_->stop();
}
update();
}
void BorderWidget::SetWidget(QWidget *x)
{
wdg_=x;
setVisible(wdg_);
QRect wdgrect = wdg_->geometry();
if(wdg_)
{
wdgrect.adjust(-border_width_,-border_width_,border_width_,border_width_);
setGeometry(wdgrect);
wdgrect.moveTo(0,0);
QRect drawrect = DrawRectOnGeometry(wdgrect);
QPainterPath paintpath;
if(border_round_radius_>0)
{
paintpath = RoundPathOnRect(drawrect, border_round_radius_);
}
else
{
paintpath.addRect(drawrect);
}
stackUnder(wdg_);
}
if(animation_)
{
if(animation_->state()!=QAbstractAnimation::Stopped)animation_->stop();
if(wdg_)
{
animation_->setTargetObject(this);
animation_->setPropertyName("border_len");
animation_->setEasingCurve(QEasingCurve::Linear);
animation_->setStartValue(0.);
QRect effectrect = EffectRectOnGeometry(wdgrect, border_width_);
QPainterPath effectpath;
if(border_round_radius_>0)
{
effectpath = RoundPathOnRect(effectrect, border_round_radius_);
}
else
{
effectpath.addRect(effectrect);
}
animation_->setEndValue(effectpath.length());
animation_->setDuration(4000);
animation_->setLoopCount(-1);
animation_->start();
}
}
}
void BorderWidget::paintEvent(QPaintEvent *event)
{
(void)event;//
if(wdg_&&animation_)
{
QRect geo = geometry();
geo.moveTo(0,0);
QPen pen(Qt::SolidLine);
pen.setCapStyle(Qt::RoundCap);//这种风格圆角看起来更丝滑
pen.setWidth(1);
pen.setColor(border_color_);
QBrush qbr(border_color_);
QPainter pt(this);
pt.setPen(pen);
pt.setBrush(qbr);
QRect drawrect = DrawRectOnGeometry(geo);
QPainterPath p;
if(border_round_radius_>0)
{
p = RoundPathOnRect(drawrect, border_round_radius_);
}
else
{
p.addRect(drawrect);
}
pt.drawPath(p);
if(!effect_work_)return;
qreal lnlen = animation_->endValue().toDouble();
qreal ln1 = border_len_/lnlen, ln2 = ln1 + border_effect_percent_;
if(ln2 >= 1.)ln2 -= 1.;
if(ln1 >= 1.)
{
ln1 -=1.;
border_len_ = ln1;
}
QPainterPath painterpath;
QRect effectrect = EffectRectOnGeometry(geo, border_width_);
if(border_round_radius_>0)painterpath = BorderEffectRoundRectPath(effectrect, ln1, ln2, border_round_radius_);
else painterpath = BorderEffectRectPath(effectrect, ln1, ln2);
pen.setColor(border_effect_color_);
pen.setWidth(border_width_ + 2);//暂不知为啥直接用border_width_不能完全挡住边框背景
//#define USE_GRADIENT
#ifdef USE_GRADIENT
//渐变效果
QLinearGradient lngt(drawrect.topLeft(), drawrect.bottomRight());
lngt.setColorAt(0.,QColor(255,0,255));
lngt.setColorAt(0.25, QColor(255,0,0));
lngt.setColorAt(0.5, QColor(255,255,0));
lngt.setColorAt(0.75,QColor(0,255,255));
lngt.setColorAt(1.,QColor(0,0,255));
pen.setBrush(QBrush(lngt));
#endif
pt.setPen(pen);
pt.setBrush(Qt::NoBrush);
pt.drawPath(painterpath);
}
}
void BorderWidget::SetBorderLength(double v)
{
border_len_ = v;
update();
}