1.界面实现效果
以下是具体的项目需要用到的效果展示,用于验证字母。
2.简介
自定义captchamovablelabel,继承自qlabel类:
中间的4个字母,就是captchamovablelabel类来实例化的对象。
主要功能如下:
1.显示字母;
2.实现了鼠标移动事件,使字母可拖动;
3.存在定时器,不断改变字母颜色;
4.绘制字母时,可旋转一定角度;
#ifndef captchamovablelabel_h #define captchamovablelabel_h #include#include #include #include #include #include #include #include #include #include #include #define captcha_refresh_duration 300 // 刷新的动画时长 #define captcha_char_angle_max 20 // 最大旋转角:20° #define captcha_shadow_blur_max 80 // 最大的阴影模糊半径 class captchamovablelabel : public qlabel { q_object q_property(int refreshprogress read getrefreshprogress write setrefreshprogress) q_property(int pressprogress read getpressprogress write setpressprogress) public: captchamovablelabel(qwidget* parent); void setangle(int angle); void setcolor(qcolor color); void settext(qstring ch); void startrefreshanimation(); void setmoveborder(qrect rect); qstring text(); protected: void paintevent(qpaintevent *) override; void mousepressevent(qmouseevent *ev) override; void mousemoveevent(qmouseevent *ev) override; void mousereleaseevent(qmouseevent *ev) override; private: void startpressanimation(int end); void setrefreshprogress(int g); int getrefreshprogress(); inline bool isnoani(); void setpressprogress(int g); int getpressprogress(); private slots: //设置rgb颜色 void slotmovepos(); private: qpoint press_pos; bool dragging = false; bool moved = false; qgraphicsdropshadoweffect effect; qstring ch; qcolor color; int angle = 0; int refreshprogress = 100; qstring prevch; qcolor prevcolor; int prevangle = 0; qstring prevchar; int pressprogress = 0; bool inited = false; qtimer movingtimer; int mover, moveg, moveb; }; #endif // captchamovablelabel_h #include "captchamovablelabel.h" captchamovablelabel::captchamovablelabel(qwidget *parent) : qlabel(parent) { effect.setoffset(0, 0); // effect.setblurradius(8); setgraphicseffect(&effect); movingtimer.setinterval(30); movingtimer.setsingleshot(false); connect(&movingtimer, signal(timeout()), this, slot(slotmovepos())); } void captchamovablelabel::setangle(int angle) { this->prevangle = this->angle; this->angle = angle; } void captchamovablelabel::setcolor(qcolor color) { this->prevcolor = this->color; this->color = color; mover = qrand() % 5; moveg = qrand() % 5; moveb = qrand() % 5; movingtimer.start(); } void captchamovablelabel::settext(qstring text) { this->prevch = this->ch; this->ch = text; // 计算合适的高度 qfontmetrics fm(this->font()); double w = fm.horizontaladvance(text) 2; double h = fm.height(); const double pi = 3.141592; int xiehalf = sqrt(w*w/4 h*h/4); // 斜边的一半 double a = atan(w/h) captcha_char_angle_max * pi / 180; // 最大的倾斜角度 int w2 = xiehalf * sin(a) * 2; a = atan(w/h) - captcha_char_angle_max * pi / 180; int h2 = xiehalf * cos(a) * 2; resize(w2, h2); } void captchamovablelabel::startrefreshanimation() { if (!inited) // 第一次,直接显示,取消动画 { inited = true; return ; } qpropertyanimation* ani = new qpropertyanimation(this, "refreshprogress"); ani->setstartvalue(0); ani->setendvalue(100); ani->setduration(qrand() % (captcha_refresh_duration / 3) captcha_refresh_duration / 3); ani->start(); connect(ani, signal(finished()), ani, slot(deletelater())); connect(ani, signal(finished()), &movingtimer, slot(start())); } qstring captchamovablelabel::text() { return ch; } void captchamovablelabel::paintevent(qpaintevent *) { qpainter painter(this); painter.setfont(this->font()); painter.setrenderhint(qpainter::smoothpixmaptransform); int w2 = width()/2, h2 = height()/2; painter.translate(w2, h2); // 平移到中心,绕中心点旋转 if (isnoani()) // 不在动画中,直接绘制 { painter.setpen(color); painter.rotate(angle); painter.drawtext(qrect(-w2, -h2, width(), height()), qt::aligncenter, ch); return ; } // 动画里面,前后渐变替换 double newprop = refreshprogress / 100.0; double oldprop = 1.0 - newprop; double a = prevangle * oldprop angle * newprop 0.5; painter.save(); painter.rotate(a); qcolor c = prevcolor; c.setalpha(c.alpha() * oldprop); // 旧文字渐渐消失 painter.setpen(c); painter.drawtext(qrect(-w2,-h2,width(),height()), qt::aligncenter, prevch); c = this->color; c.setalpha(c.alpha() * newprop); // 新文字渐渐显示 painter.setpen(c); painter.drawtext(qrect(-w2, -h2, width(), height()), qt::aligncenter, ch); painter.restore(); } void captchamovablelabel::mousepressevent(qmouseevent *ev) { if (ev->button() == qt::leftbutton) { // 开始拖拽 press_pos = ev->pos(); dragging = true; moved = false; this->raise(); movingtimer.stop(); startpressanimation(200); return ev->accept(); } qlabel::mousepressevent(ev); } void captchamovablelabel::mousemoveevent(qmouseevent *ev) { if (dragging && ev->buttons() & qt::leftbutton) { if (!moved && (ev->pos() - press_pos).manhattanlength() < qapplication::startdragdistance()) { return qlabel::mousemoveevent(ev); // 还没到这时候 } moved = true; move(this->pos() ev->pos() - press_pos); ev->accept(); return ; } qlabel::mousemoveevent(ev); } void captchamovablelabel::mousereleaseevent(qmouseevent *ev) { if (dragging) { // 结束拖拽 dragging = false; movingtimer.start(); startpressanimation(0); } if (moved) return ev->accept(); qlabel::mousereleaseevent(ev); } void captchamovablelabel::startpressanimation(int end) { qpropertyanimation* ani = new qpropertyanimation(this, "pressprogress"); ani->setstartvalue(pressprogress); ani->setendvalue(end); ani->setduration(captcha_refresh_duration << 1); ani->start(); connect(ani, signal(finished()), ani, slot(deletelater())); } void captchamovablelabel::setrefreshprogress(int g) { this->refreshprogress = g; update(); } int captchamovablelabel::getrefreshprogress() { return refreshprogress; } bool captchamovablelabel::isnoani() { return refreshprogress == 100; } void captchamovablelabel::setpressprogress(int g) { this->pressprogress = g; double off = g / 100; effect.setblurradius(g / 20.0); effect.setoffset(-off, off); } int captchamovablelabel::getpressprogress() { return pressprogress; } void captchamovablelabel::slotmovepos() { if (refreshprogress < 100) return ; int val = color.red() mover; if ( val > 255) { val = 255; mover = - qrand() % 5; } else if (val < 0) { val = 0; mover = - qrand() % 5; } color.setred(val); val = color.green() moveg; if ( val > 255) { val = 255; moveg = - qrand() % 5; } else if (val < 0) { val = 0; moveg = - qrand() % 5; } color.setgreen(val); val = color.blue() moveb; if ( val > 255) { val = 255; moveb = - qrand() % 5; } else if (val < 0) { val = 0; moveb = - qrand() % 5; } color.setblue(val); }
自定义captchalabel类,此类继承qwidget用于存在上面的4个字母。
主要功能如下:
1.画噪音点,背景上绘制无数个随机颜色的点;
2.画噪音线,这个线条是动态的,随时间更改起渐变颜色,线条位置;
3.鼠标点击,生成随机字母。
#ifndef captchalabel_h #define captchalabel_h #include "captchamovablelabel.h" #include#define captchar_count 4 // 验证码字符数量 class captchalabel : public qwidget { q_object q_property(int refreshprogress read getrefreshprogress write setrefreshprogress) public: captchalabel(qwidget* parent = nullptr); void refresh(); bool match(qstring input); private: void initview(); void initdata(); void setrefreshprogress(int g); int getrefreshprogress(); bool isnoani(); private slots: void movenoiselines(); protected: void paintevent(qpaintevent* ) override; void mousereleaseevent(qmouseevent *event) override; private: captchamovablelabel* charlabels[captchar_count]; // label控件 qlist noisepoints; // 噪音点 qlist pointcolors; // 点的颜色 qlist linestarts; // 噪音线起始点 qlist lineends; // 噪音先结束点 qlist startsv; // 起始点的移动速度(带方向) qlist endsv; // 结束点的速度(带方向) qlist linecolor1s; // 线的渐变色1 qlist linecolor2s; // 线的渐变色2 qlist linewidths; qtimer movingtimer; int refreshprogress = 100; qlist noisepoints2; // 新的位置 int autorefreshmax = 2; // match错误几次后就自动刷新 int matchfailcount = 0; // match错误次数 int matchfailandrefreshcount = 0; // 失败且导致刷新的次数,强行刷新 }; #endif // captchalabel_h #include "captchalabel.h" captchalabel::captchalabel(qwidget *parent) : qwidget(parent) { initview(); // 这里延迟,等待布局结束 qtimer::singleshot(0, [=]{ initdata(); refresh(); }); } void captchalabel::initview() { // 初始化控件 for (int i = 0; i < captchar_count; i ) { charlabels[i] = new captchamovablelabel(this); charlabels[i]->move(0, 0); } // 初始化时钟 movingtimer.setinterval(30); movingtimer.setsingleshot(false); movingtimer.start(); connect(&movingtimer, signal(timeout()), this, slot(movenoiselines())); } void captchalabel::initdata() { // 初始化噪音线 auto getrandomcolor = [=]{ return qcolor(qrand() % 255, qrand() % 255, qrand() % 255); }; int w = width(), h = height(); int count = 20/*w * h / 400*/; int penw = qmin(w, h) / 15; for (int i = 0; i < count; i ) { linestarts.append(qpointf(qrand() % w, qrand() % h)); lineends.append(qpointf(qrand() % w, qrand() % h)); startsv.append(qpointf((qrand() % 30 - 15) / 10.0, (qrand() % 30 - 15) / 10.0)); endsv.append(qpointf((qrand() % 30 - 15) / 10.0, (qrand() % 30 - 15) / 10.0)); linewidths.append(qrand() % penw 1); linecolor1s.append(getrandomcolor()); linecolor2s.append(getrandomcolor()); } } void captchalabel::setrefreshprogress(int g) { this->refreshprogress = g; update(); } int captchalabel::getrefreshprogress() { return refreshprogress; } bool captchalabel::isnoani() { return refreshprogress == 100; } void captchalabel::movenoiselines() { int w = width(), h = height(); double vbase = 100.0; // 大概最快要3秒钟走完 for (int i = 0; i < linestarts.size(); i ) { qpointf& pos = linestarts[i]; pos = startsv.at(i); if (pos.x() < 0) startsv[i].setx(qrand() % w / vbase); else if (pos.x() > w) startsv[i].setx(- qrand() % w / vbase); if (pos.y() < 0) startsv[i].sety(qrand() % h / vbase); else if (pos.y() > h) startsv[i].sety(- qrand() % h / vbase); } for (int i = 0; i < lineends.size(); i ) { qpointf& pos = lineends[i]; pos = endsv.at(i); if (pos.x() < 0) endsv[i].setx(qrand() % w / vbase); else if (pos.x() > w) endsv[i].setx(- qrand() % w / vbase); if (pos.y() < 0) endsv[i].sety(qrand() % h / vbase); else if (pos.y() > h) endsv[i].sety(- qrand() % h / vbase); } update(); } void captchalabel::refresh() { int width = this->width(); int height = this->height(); // 清空全部内容 for (int i = 0; i < captchar_count; i ) charlabels[i]->hide(); refreshprogress = -1; update(); // 获取背景底色 qpixmap rend(this->size()); render(&rend); qcolor bgcolor = rend.toimage().pixelcolor(width/2, height/2); int br = bgcolor.red(), bg = bgcolor.green(), bb = bgcolor.blue(); // 开始随机生成 const int border = 10; int leftest = width / border; int topest = height / border; int wid = width - leftest * 2; int hei = height - topest * 2; for (int i = 0; i < captchar_count; i ) { auto label = charlabels[i]; // 随机大小 qfont font; font.setpointsize( qrand() % 8 22 ); label->setfont(font); // 随机旋转 label->setangle( qrand() % (captcha_char_angle_max*2) - captcha_char_angle_max); // 生成随机字符 const qstring pool = "qwertyuiopasdfghjklzxcvbnm"; qchar rc = pool.at(qrand() % pool.size()); // 此时会调整大小,settext必须在setfont之后 label->settext(rc); // 生成随机位置(排除边缘) int left = leftest wid * i / captchar_count; int right = leftest wid * (i 1) / captchar_count - label->width(); int x = qrand() % qmax(right-left, 1) left; int y = qrand() % qmax(hei - label->height(), 1) topest; label->show(); // 之前是hide状态 qpropertyanimation * ani = new qpropertyanimation(label, "pos"); ani->setstartvalue(label->pos()); ani->setendvalue(qpoint(x, y)); ani->setduration(qrand() % (captcha_refresh_duration/2) captcha_refresh_duration/2); ani->seteasingcurve(qeasingcurve::outquart); ani->start(); connect(ani, signal(finished()), ani, slot(deletelater())); // 生成随机颜色,且必须和背景颜色有区分度 qcolor color; while (true) { int r = qrand() % 255; int g = qrand() % 255; int b = qrand() % 255; if (abs(r-br) abs(g-bg) abs(b-bb) > 383) { color = qcolor(r, g, b); break; } } label->setcolor(color); label->startrefreshanimation(); } // 生成噪音点 int count = wid * hei / border; // 点的数量 if (noisepoints.size() == 0) // 第一次 { for (int i = 0; i < count; i ) { int x = qrand() % width; int y = qrand() % height; noisepoints.append(qpoint(x, y / 2)); noisepoints2.append(qpoint(x, y)); pointcolors.append(qcolor(qrand() % 255, qrand() % 255, qrand() % 255)); } } else { noisepoints = noisepoints2; count = noisepoints.size(); noisepoints2.clear(); for (int i = 0; i < count; i ) { noisepoints2.append(qpoint(qrand() % width, qrand() % height)); } } // 生成噪音线 qpropertyanimation* ani = new qpropertyanimation(this, "refreshprogress"); ani->setstartvalue(0); ani->setendvalue(100); ani->setduration(qrand() % (captcha_refresh_duration) captcha_refresh_duration); ani->start(); connect(ani, signal(finished()), ani, slot(deletelater())); } /** * 判断能否匹配 */ bool captchalabel::match(qstring input) { // 根据label的位置排序 std::sort(charlabels, charlabels captchar_count, [=](qlabel* a, qlabel* b){ if (a->pos().x() == b->pos().x()) return a->pos().y() < b->pos().y(); return a->pos().x() < b->pos().x(); }); // 按顺序组合成新的字符串 qstring captcha; for (int i = 0; i < captchar_count; i ) captcha = charlabels[i]->text(); // 进行比较 if (input.toupper() == captcha) return true; // 记录失败 matchfailcount ; if (matchfailcount >= autorefreshmax // 达到刷新的次数 || matchfailandrefreshcount > 2) // 多次错误导致刷新 { refresh(); matchfailandrefreshcount ; matchfailcount = 0; } return false; } void captchalabel::paintevent(qpaintevent *) { qpainter painter(this); if (refreshprogress == -1) // 不画,可能需要获取背景颜色 return ; // 画噪音点 if (isnoani()) { // 显示随机的点 for (int i = 0; i < noisepoints2.size(); i ) { painter.setpen(pointcolors.at(i)); painter.drawpoint(noisepoints2.at(i)); } } else { // 动画过程中的点的移动 double newprop = refreshprogress / 100.0; double oldprop = 1.0 - newprop; int count = qmin(noisepoints.size(), noisepoints2.size()); for (int i = 0; i < count; i ) { qpoint pt1 = noisepoints.at(i); qpoint pt2 = noisepoints2.at(i); qpoint pt( pt1.x() * oldprop pt2.x() * newprop, pt1.y() * oldprop pt2.y() * newprop ); painter.setpen(pointcolors.at(i)); painter.drawpoint(pt); } } // 画噪音线 painter.setrenderhint(qpainter::antialiasing); for (int i = 0; i < linestarts.size(); i ) { qlineargradient grad(linestarts.at(i), lineends.at(i)); grad.setcolorat(0, linecolor1s.at(i)); grad.setcolorat(1, linecolor2s.at(i)); painter.setpen(qpen(grad, linewidths.at(i))); painter.drawline(linestarts.at(i), lineends.at(i)); } } void captchalabel::mousereleaseevent(qmouseevent *event) { if (qrect(0,0,width(),height()).contains(event->pos())) refresh(); qwidget::mousereleaseevent(event); }
3.使用
新建mainwindow,拖动一个qwidget,提升为captchalabel即可。