前言
最近一直在做控件自绘方面的事情,自己设计了一些控件和界面,下面把过程当中的经验记录下来。
现在写界面的话,一种是用C++,一种是用QML,这两种都有用过,所以一并写出来。
QWidget中的自绘
在QWidget中做自绘一般就是重写void paintEvent(QPaintEvent * event)函数,然后在这个函数里利用QPainter类进行绘制,在这里提供一段代码:
void InfoWidget::paintEvent(QPaintEvent * event)
{
Q_UNUSED(event);
QPainter painter(this);
QPen linePen(QColor(174, 240, 108));
linePen.setWidth(8);
painter.setPen(linePen);
painter.setBrush(QBrush(QColor(57,62,70,150)));
painter.setRenderHint(QPainter::HighQualityAntialiasing);
int mw = this->width();
int mh = this->height();
int ma = 30;
int padding = 10;
QPointF points[16] ={QPointF(padding * 2 + ma + padding, padding), QPointF(mw/2, padding), QPointF(mw/2 + ma + padding, padding + ma + padding),
QPointF(mw - padding, padding + ma + padding), QPointF(mw - padding, mh - padding), QPointF(mw - 3 * padding, mh - padding),
QPointF(mw - 4 * padding, mh - 2 * padding), QPointF(5 * padding, mh - 2 * padding), QPointF(4 * padding, mh - padding),
QPointF(2 * padding, mh - padding), QPointF(2 * padding, mh - padding - mh/4), QPointF(padding, mh- padding - mh/4 - padding),
QPointF(padding, mh/4 + padding), QPointF(2 * padding, mh/4), QPointF(2* padding, padding + ma + padding),
QPointF(padding * 2 + ma + padding, padding)};
painter.drawPolygon(points, 16);
painter.setFont(QFont(QString::fromLocal8Bit("微软雅黑"),42));
painter.drawText(padding * 2 + ma + padding, padding, mw/2-ma-3*padding, ma + padding, Qt::AlignHCenter | Qt::AlignVCenter, QString::fromLocal8Bit("测试信息"));
}
上段代码画出来的是一个不规则的窗体,setPen其实就是画的边框的样式,setBrush就是填充边框区域的样式。drawText的文字是在指定的矩形里进行绘制的,文字的样式通过setFont进行设置。画完之后就相当于有了一个背景,接下来其实加入布局、加其他组件都跟正常的窗口差不多的,只不过这时的布局位置需要你自己去调整,要根据坐标去调整相应的布局位置。
QML中的自绘
我个人其实是非常喜欢用QML来写界面的,因为搭建界面真的非常方便快捷,而且与C++的交互方式也是非常强大,还有一个原因就是用Canvas做组件自绘非常地方便,搭配动画效果就可以做出很酷炫的界面了。先上一段示例代码:
import QtQuick 2.7
import QtQuick.Window 2.0
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import QtQuick.Controls.Material 2.2
import QtQuick.Controls.Styles 1.4
import "../CustomComponent"
Rectangle{
id: rhomboidButton
implicitWidth: mw
implicitHeight: mh
border.width: 0
color: Qt.rgba(0.2, 0.2, 0.2, 0);
property int mw : 150 //边框宽度
property int mh : 40 //边框高度
property int padding: 4
property int gap : (mh-2*padding)/2;
property string outlineColor: "#FF8100"
property string shadowColor: "#FF9800"
property string textColor: "#64FF98"
property string title: "展示线路"
signal buttonClicked()
states: [
State {
name: "default"
PropertyChanges { target: titleText; opacity: 0.5 }
PropertyChanges { target: outline; opacity: 0.5 }
PropertyChanges { target: aniCircle1; opacity: 0.5 }
PropertyChanges { target: aniCircle2; opacity: 0.5 }
},
State {
name: "hovered"
PropertyChanges { target: titleText; opacity: 1 }
PropertyChanges { target: outline; opacity: 1 }
PropertyChanges { target: aniCircle1; opacity: 1 }
PropertyChanges { target: aniCircle2; opacity: 1 }
}
]
state: "default"
MouseArea{
anchors.fill: parent
acceptedButtons: Qt.LeftButton
hoverEnabled: true
onClicked: {
buttonClicked();
}
onPressed: {
titleText.topPadding = padding/2;
}
onReleased: {
titleText.topPadding = 0;
}
onEntered: {
rhomboidButton.state = "hovered";
}
onExited: {
rhomboidButton.state = "default";
}
}
Text{
id: titleText
anchors.fill: parent
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
text: title
font.family: "微软雅黑"
font.pointSize: 12
color: textColor
z: 10
}
Canvas {
id: outline
anchors.fill: parent
contextType: "2d"
z: 5
onPaint: {
var ctx = outline.getContext("2d");
ctx.strokeStyle = outlineColor;
ctx.fillStyle = Qt.rgba(0.2, 0.2, 0.2, 0.5);
ctx.lineWidth = 2;
ctx.shadowBlur = 2;
ctx.shadowColor = shadowColor;
ctx.beginPath();
ctx.moveTo(padding, padding);
ctx.lineTo(mw - padding - gap, padding);
ctx.lineTo(mw - padding, mh - padding);
ctx.lineTo(padding + gap, mh - padding);
ctx.lineTo(padding, padding);
ctx.stroke();
ctx.fill();
}
}
Canvas {
id: aniCircle1
x:0
y:0
width: rhomboidButton.width
height: rhomboidButton.height
contextType: "2d"
z: 10
onPaint: {
var ctx = aniCircle1.getContext("2d");
ctx.strokeStyle = "#EEF4F2";
ctx.fillStyle = "#EEF4F2";
ctx.lineWidth = 4;
ctx.shadowBlur = 2;
ctx.shadowColor = shadowColor;
ctx.beginPath();
ctx.arc(padding, padding, padding/2, 0, Math.PI*2, false);
ctx.stroke();
ctx.fill();
}
}
PathAnimation{
id: pathAnim1
target: aniCircle1
running: true
duration: 4500
anchorPoint: Qt.point(padding,padding)
path: Path{
startX: padding
startY: padding
PathLine{x: mw - padding - gap; y: padding ;}
PathLine{x: mw - padding; y: mh - padding }
PathLine{x: padding + gap; y: mh - padding }
PathLine{x: padding; y: padding }
}
easing.type: Easing.InOutSine
loops: Animation.Infinite
}
Canvas {
id: aniCircle2
x:0
y:0
width: rhomboidButton.width
height: rhomboidButton.height
contextType: "2d"
z: 10
onPaint: {
var ctx = aniCircle2.getContext("2d");
ctx.strokeStyle = "#EEF4F2";
ctx.fillStyle = "#EEF4F2";
ctx.lineWidth = 4;
ctx.shadowBlur = 2;
ctx.shadowColor = shadowColor;
ctx.beginPath();
ctx.arc(mw - padding, mh - padding, padding/2, 0, Math.PI*2, false);
ctx.stroke();
ctx.fill();
}
}
PathAnimation{
id: pathAnim2
target: aniCircle2
running: true
duration: 4500
anchorPoint: Qt.point(mw - padding,mh - padding)
path: Path{
startX: mw - padding
startY: mh - padding
PathLine{x: padding + gap; y: mh - padding }
PathLine{x: padding; y: padding }
PathLine{x: mw - padding - gap; y: padding ;}
PathLine{x: mw - padding; y: mh - padding }
}
easing.type: Easing.InOutSine
loops: Animation.Infinite
}
}
上面的动图就是做出来的效果了,主要是使用状态机来实现鼠标悬浮时样式的改变,同时使用Canvas绘制了简单的平行四边形和两个动态的小圆圈。QML的代码非常简单易懂,所以就不作过多的解释了。
后话
其实不管是用QPainter还是用Canvas,我觉得绘制原理都是差不多的,熟悉一种之后另一种自然也很容易掌握,不过QML下的动画功能在QWidget中实现起来确实不太方便,总的来说还是推荐使用QML进行组件的自绘。