通过QML提供的Shape来绘制形状,Shape使用GPU渲染。
Shape中图形形状由ShapePath决定,Shape只是ShapePath的载体。
Shape{
ShapePath{
......
}
}
ShapePath使用不同的Path来描述要绘制的图形形状。
常用的有PathLine,PathArc,PathAngleArc等。
ShapePath
使用ShapePath,除了继承自父类Path的属性外,需要关注ShapePath的特有属性及内部成员的实现(具体的画图操作),以下对ShapePath的属性进行分类:
- 笔画的属性:
- 笔画宽度:strokeWidth
- 笔画颜色:strokeColor
- 笔画端点的形状:capStyle,有以下三种:
- ShapePath.FlatCap:方形端点,不会超出线段的覆盖范围。
- ShapePath.SquareCap:方形端点,超出线段的覆盖范围,超出宽度为一半的线宽。
- ShapePath.RoundCap:圆形端点。
- 笔画的虚实:strokeStyle,有以下两种:
- ShapePath.SolidLine:实线。
- ShapePath.DashLine:虚线。
- 两条线段的连接方式:joinStyle,有以下三种:
- ShapePath.MiterJoin:笔画的外边沿向外延伸,相交后对构成的空白角落进行填充。
- ShapePath.BevelJoin:以三角形填充两条线段相交产生的缺口。
- ShapePath.RoundJoin:以圆形填充两条线段相交产生的缺口。
- 斜交延伸:miterLimit,当joinStyle设置为ShapePath.MiterJoin时,属性规定了斜接的最大长度,默认值是2。经验证,miterLimit指的斜接长度是线宽的倍数,不是像素数。
- 图形的形状,由ShapePath中使用的各种Path实现(PathLine、PathAngleArc、PathArc等)。
- 图形内部的填充:fillRule。
- ShapelPath.OddEvenFill:奇偶填充规则。
- ShapePath.WindingFill:非零填充规则。
ShapePath实践前要了解的内容
PathLine是最容易使用的Path,它用来绘制直线。 使用PathLine便于验证ShapePath的用法。
使用PathLine,需要关注以下属性:
- x:终点x轴的坐标。
- y:终点y轴的坐标。
- relativeX:同x属性。
- relativeY:同y属性。
下一个PathLine会使用上一个PathLine的终点作为自己的起点,可以使用PathMove改变线段的起点:
PathMove{
x:50 //更改后的x坐标。
y:50 //更改后的y坐标。
}
ShapePath的实践---画三角形
使用ShapePath画一个三角形为例,来说明各属性的效果:
import QtQuick
import QtQuick.Window
import QtQuick.Shapes 1.5
Window {
width: 640
height: 480
visible: true
title: qsTr("triangle")
Shape{
id:rootShape
anchors.fill: parent
smooth: true
ShapePath{
strokeWidth: 4 //设置笔画宽度为4个像素。
strokeColor: "red" //设置笔画颜色为红色。
startX: 8 //起点x轴位置。
startY: 8 //起点y轴位置。
//第一条线,直角三角形的斜边。
PathLine{
x:rootShape.width - 16;
y:rootShape.height - 16;
}
//第二条线,直角三角形下侧的直角边。
PathLine{
x:16;
y:rootShape.height - 16;
}
//第三条线,直角三角形左侧的直角边。
PathLine{
x:16;
y:16;
}
//PathLine会默认把上一个PathLine的终点,作为自己的起点。
}
}
}
得到如下三角形:
在以上图形的基础上,更改代码:
import QtQuick
import QtQuick.Window
import QtQuick.Shapes 1.5
Window {
width: 640
height: 480
visible: true
title: qsTr("triangle")
Shape{
id:rootShape
anchors.fill: parent
smooth: true
ShapePath{
strokeWidth: 16 //设置笔画宽度为4个像素。
strokeColor: "red" //设置笔画颜色为红色。
startX: 16 //起点x轴位置。
startY: 16 //起点y轴位置。
strokeStyle: ShapePath.DashLine //笔画为虚线。
capStyle: ShapePath.RoundCap //圆形端点。
//注意:fillGradient不能使用Gradient
fillGradient: LinearGradient {
//起点
x1:16
y1:16
//终点
x2:rootShape.width - 16
y2:rootShape.height - 16
GradientStop{position: 0.0;color: "blue"}
GradientStop{position: 0.5;color: "black"}
GradientStop{position: 1.0;color: "yellow"}
}
//第一条线,直角三角形的斜边。
PathLine{
x:rootShape.width - 16;
y:rootShape.height - 16;
}
//第二条线,直角三角形下侧的直角边。
PathLine{
x:16;
y:rootShape.height - 16;
}
//第三条线,直角三角形左侧的直角边。
PathLine{
x:16;
y:16;
}
}
}
}
得到新的图形:
可以观察到,三角形的各条线段的宽度明显变大,笔画变成了虚线,内部有三种颜色的填充。
线段的相交
线段相交时的填充行为由joinStyle属性决定,默认情况下使用ShapePath.BevelJoin。 笔画的strokeStyle设置为ShapePath.DashLine(虚线)时,joinStyle属性无效。
ShapePath.BevelStyle示例:
ShapePath.RoundJoin示例:
joinStyle设置为ShapePath.MiterJoin时,miterLimit属性规定了斜接的最大长度。 斜接长度指的是在两条线交汇处内角和外角之间的距离:
超过miterLimit规定的长度,两条线段相交时不会进行斜接。
设置miterLimit的值为1, ShapePath.MiterJoin的示例:
如上图所示,右下角的斜接长度超过了miterLimit乘以strackWidth的最大值,所有没有进行斜接,按照ShapePath.BevelJoin的形式来显示。
填充规则
ShapePath提供了两种填充规则:
- ShapelPath.OddEvenFill,奇偶填充规则: 路径包围的区域任意点P向外做一条射线,如果相交的边总数是奇数填充,反之不填充 。
- ShapePath.WindingFill,非零填充规则:路径包围的区域任意点P向外做一条射线,环绕数为0,如果相交的边是从左向右环绕数减1,从右向左环绕数加1,环绕数不为零填充,反之不填充 。
按照两种填充规则的描述,三角形无论使用哪种填充规则,都会被填充。
为了对填充规则进行验证,在大三角形内部画一个小三角形:
import QtQuick
import QtQuick.Window
import QtQuick.Shapes 1.5
Window {
width: 640
height: 480
visible: true
title: qsTr("triangle")
Shape{
id:rootShape
anchors.fill: parent
smooth: true
ShapePath{
strokeWidth: 8 //设置笔画宽度为4个像素。
strokeColor: "red" //设置笔画颜色为红色。
startX: 16 //起点x轴位置。
startY: 16 //起点y轴位置。
joinStyle: ShapePath.MiterJoin
//miterLimit: 1
//strokeStyle: ShapePath.DashLine //笔画为虚线。
capStyle: ShapePath.RoundCap //圆形端点。
//注意:fillGradient不能使用Gradient
fillGradient: LinearGradient {
//起点
x1:16
y1:16
//终点
x2:rootShape.width - 16
y2:rootShape.height - 16
GradientStop{position: 0.0;color: "blue"}
GradientStop{position: 0.5;color: "black"}
GradientStop{position: 1.0;color: "yellow"}
}
//第一条线,直角三角形的斜边。
PathLine{
x:rootShape.width - 16;
y:rootShape.height - 16;
}
//第二条线,直角三角形下侧的直角边。
PathLine{
x:16;
y:rootShape.height - 16;
}
//第三条线,直角三角形左侧的直角边。
PathLine{
x:16;
y:16;
}
//在大三角形内画一个小三角形
//设置起点。
PathMove{
x:100
y:100
}
//小三角形的斜边。
PathLine{
x:200
y:200
}
//小三角形下侧的直角边。
PathLine{
x:100
y:200
}
//小三角形左侧的直角边。
PathLine{
x:100
y:100
}
fillRule: ShapePath.OddEvenFill
}
}
}
使用奇偶填充规则得到的结果如下:
使用非零填充规则得到的结果如下:
构建圆形进度条
PathAngleArc
PathAngleArc是Path的一种,用来绘制圆形、椭圆形或扇形。
使用PathAngleArc需关注以下属性:
- real centerX:圆心的x坐标。
- real centerY:圆心的y坐标。
- bool moveToStart:标记该元素是否跟随上一个元素。
- real radiusX:x轴半径。
- real radiusY:y轴半径,x、y轴半径不相同可以画椭圆。
- real startAngle:开始绘制时的角度。
- real sweepAngle:圆弧扫过的角度。
抗锯齿
使用Shape画圆或其它带有弧度的形状时,会在图形边缘产生锯齿,如下图:
为了使线条更加平滑,需要使用抗锯齿功能:
全局设置
在main.c中包含:QSurfaceFormat头文件,在main函数中添加以下代码:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QSurfaceFormat>
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QSurfaceFormat format;
format.setSamples(4); //多重采样的次数
QSurfaceFormat::setDefaultFormat(format);
QQmlApplicationEngine engine;
QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed,
&app, []() { QCoreApplication::exit(-1); },
Qt::QueuedConnection);
engine.loadFromModule("untitled1", "Main");
return app.exec();
}
这会在全局开启多重采样。
局部设置
为了节省硬件设备资源,使用Shape时,可以开启局部多重采样,既只针对需要抗锯齿的图形进行多重采样。
import QtQuick
import QtQuick.Window
import QtQuick.Shapes
Window {
width: 640
height: 480
visible: true
title: qsTr("triangle")
Item {
anchors.fill: parent
layer.enabled: true //开启多重采样
layer.samples: 8 //多重采样的次数
Shape{
vendorExtensionsEnabled:true //使能openGL,设置为false图形会消失
......
}
}
}
开启多重采样后的圆形边缘会更加光滑,多重采样层数越多,边缘越光滑,最多只支持8重采样。
圆形进度条的具体实现
功能规划:
- 尽可能多的将属性暴露给用户,提高控件的可定制性。
- 控件的默认值要合理,使用户可以用最简单的方式创建并使用控件。
- 将整个圆形进度条分为三个主要部分:
- 进度条区域背景。
- 中心区域背景。
- 进度条自身。
- 每个主要区域提供颜色填充和渐变填充。
创建DsCycleProgressBar.qml,代码内容如下:
import QtQuick
import QtQuick.Shapes 1.5
Item{
id:root
width: 150
height: 150
//提供两种端点样式,平滑和圆形。
enum DsProgressCapStyle{
FlatCap = 30,
RoundCap
}
//提供两种工作模式,圆环和渐变圆环。
enum DsProgressWorkMode{
CycleMode,
GradientCycleMode
}
property real percent: 0 //进度条默认值。
onPercentChanged: {
if(percent < 0)
{
percent = 0
}
else if(percent > 100)
{
percent = 100
}
}
Behavior on percent{
NumberAnimation{
duration:root.animationTime
}
}
property int animationTime: 300 //进度条动画时间。
//进度条背景渐变,该属性被设置则cycleBackgroundFillColor属性无效,如需使cycleBackgroundFillColor属性生效,则此属性初始化为:Gradient。
property ShapeGradient cycleBackgroundShapeGradient: LinearGradient{
x1:0
y1:0
x2:progressBar.width
y2:progressBar.height
GradientStop{position: 0.0;color: "lightGray"}
GradientStop{position: 1.0;color: "lightYellow"}
}
property color cycleBackgroundFillColor:"#007330" //进度条部分背景颜色。
property color strokeColor: "orange" //进度条颜色。
property int capStyle: root.DsProgressCapStyle.RoundCap //进度条端点样式。
property real strokeWidth:width/10>20?20:width/10 //进度条笔画宽度。
property real startAngle:90 //进度条起始角度。
property real arcRadius:width/2 - strokeWidth/2 //圆半径,默认是父对象宽度的一半减去笔画宽度的一半,不建议修改。
property bool isAnticlockwise: false //进度条的方向,false顺时针,true逆时针,默认顺时针转动。
property int layerSamples:4 //多重采样层数。
onLayerSamplesChanged: {
if(value > 8)
{
value = 8
}
}
property int borderWidth: 0 //进度条内外边沿的宽度。
property color borderColor:"transparent" //进度条内外边沿的颜色。
property color textColor:root.strokeColor //进度条文字颜色,默认与进度条同色。
property int textFixedNumber:1 //非零进度显示到小数点后几位,默认显示一位。
property int textPixSize:root.width/10 //文字像素大小,默认大小为1/10图形宽度尺寸大小。
property string textFontFamily: "Ubuntu" //文字字体。
//中心圆背景渐变,该属性被设置则centerBackgroundFillColor属性无效,如需使centerBackgroundFillColor属性生效,则此属性初始化为:Gradient。
property ShapeGradient centerBackgroundShapeGradient: RadialGradient{
centerX: progressBar.width/2
centerY: progressBar.height/2
centerRadius: progressBar.width/2
focalX: centerX
focalY: centerY
GradientStop{position: 0.0;color:"lightGray"}
GradientStop{position: 1.0;color:"lightYellow"}
}
property color centerBackgroundFillColor:"#007330" //背景默认颜色。
//工作模式,标记工作在标准模式还是在渐变模式,渐变模式下,需要提供渐变首尾两种颜色。
property int workMode: root.DsProgressWorkMode.GradientCycleMode
property color gradientHeadColor:"orange" //渐变模式下头位置的颜色。
property color gradientMiddleColor:"#F9C603" //渐变模式下中间位置的颜色。
property color gradientEndColor:"orange" //渐变模式下尾位置的颜色。
layer.enabled: layerSamples > 0
layer.samples: layerSamples
Shape{
anchors.centerIn: parent
vendorExtensionsEnabled:true
width: parent.width
height: parent.height
//使用奇偶填充,实现进度条的背景与边框。
ShapePath{
id:backPath
strokeWidth: root.borderWidth
strokeColor: root.borderColor
fillGradient: root.cycleBackgroundShapeGradient
fillColor: root.cycleBackgroundFillColor
fillRule: ShapePath.OddEvenFill
//提供外圈的边框。
PathAngleArc{
id:backArc
radiusX: root.width/2 - root.borderWidth/2
radiusY: radiusX
centerX: root.width/2
centerY: root.height/2
startAngle: 0
sweepAngle: 360
}
//提供内圈的边框。
PathAngleArc{
radiusX: backArc.radiusX - movePath.strokeWidth - root.borderWidth
radiusY: radiusX
centerX: root.width/2
centerY: root.height/2
startAngle: 0
sweepAngle: 360
}
}
//提供CycleMode模式下的进度条。
ShapePath{
id:movePath
strokeColor: root.strokeColor
strokeWidth: root.strokeWidth
fillColor: "transparent"
capStyle: root.capStyle === root.DsProgressCapStyle.RoundCap?ShapePath.RoundCap:ShapePath.FlatCap
PathAngleArc{
id:moveArc
radiusX: root.arcRadius - backPath.strokeWidth
radiusY: radiusX
centerX: root.width/2
centerY: root.height/2
startAngle: root.workMode !== root.DsProgressWorkMode.CycleMode?0:root.startAngle
sweepAngle: root.percent <= 0?0:(root.workMode !== root.DsProgressWorkMode.CycleMode?0:root.isAnticlockwise?0 - root.percent * 3.6:root.percent * 3.6)
}
}
//提供进度条最内部的填充,可以是颜色,也可以是渐变色。
ShapePath{
strokeColor: backPath.strokeColor
strokeWidth: backPath.strokeWidth
fillColor: root.centerBackgroundFillColor
fillGradient: root.centerBackgroundShapeGradient
PathAngleArc{
centerX: root.width/2
centerY: root.height/2
radiusX: backArc.radiusX - movePath.strokeWidth - root.borderWidth
radiusY: radiusX
startAngle: 0
sweepAngle: 360
}
}
}
Shape{
anchors.centerIn: parent
vendorExtensionsEnabled: true
width: parent.width
height: parent.height
visible: root.workMode === root.DsProgressWorkMode.GradientCycleMode
transform:Matrix4x4 {
matrix: root.isAnticlockwise?Qt.matrix4x4(1, 0, 0,0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1):Qt.matrix4x4(-1, 0, 0, root.width,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1)
}
//提供GradientCycle模式下的进度条。
ShapePath{
strokeWidth: root.borderWidth
strokeColor: root.borderColor
fillGradient: ConicalGradient{
centerX: root.width/2
centerY: root.width/2
angle: -root.startAngle
GradientStop{position: 0.0;color:percent <= 0?"transparent":(percent >= 100?root.gradientHeadColor:root.gradientEndColor)}
GradientStop{position: (percent / 100)/2;color:percent <= 0?"transparent":root.gradientMiddleColor}
GradientStop{position: percent / 100;color:percent <= 0?"transparent":root.gradientHeadColor}
GradientStop{position: percent / 100 + 0.00001;color:percent >= 100?root.gradientHeadColor:"transparent"}
}
fillRule: ShapePath.OddEvenFill
PathAngleArc{
id:backArc_gradient
radiusX: root.width/2 - root.borderWidth/2
radiusY: radiusX
centerX: root.width/2
centerY: root.height/2
startAngle: 0
sweepAngle: 360
}
PathAngleArc{
radiusX: backArc_gradient.radiusX - movePath.strokeWidth - root.borderWidth
radiusY: radiusX
centerX: root.width/2
centerY: root.height/2
startAngle: 0
sweepAngle: 360
}
}
//用于在GradientCycleMode模式下,显示RoundCap不跟随移动的部分。
ShapePath{
strokeWidth: -1
fillColor: root.capStyle === root.DsProgressCapStyle.RoundCap?
(root.percent <= 0 || root.percent >= 100?"transparent":root.gradientEndColor):"transparent"
PathAngleArc{
radiusX: movePath.strokeWidth / 2
radiusY: radiusX
centerX: root.width / 2
centerY: root.height / 2 + moveArc.radiusX
startAngle: root.startAngle
sweepAngle: 180
}
}
}
//用于在GradientCycleMode模式下,显示RoundCap跟随移动的部分。
Shape{
anchors.centerIn: parent
vendorExtensionsEnabled: true
width: parent.width
height: parent.height
visible:root.capStyle === root.DsProgressCapStyle.RoundCap?(root.workMode === root.DsProgressWorkMode.GradientCycleMode?true:false):false
transform:Matrix4x4 {
matrix: root.isAnticlockwise == true?Qt.matrix4x4(-1, 0, 0, root.width,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1):Qt.matrix4x4(1, 0, 0,0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1)
}
rotation: percent * 3.6
ShapePath{
strokeWidth: -1
fillColor: root.percent <= 0?"transparent":root.gradientHeadColor
PathAngleArc{
radiusX: movePath.strokeWidth / 2
radiusY: radiusX
centerX: root.width / 2 + 1
centerY: root.height / 2 + moveArc.radiusX
startAngle: root.startAngle
sweepAngle: 180
}
}
}
//用于显示文字。
Text{
anchors.centerIn: parent
text:percent <= 0?0 + "%":(root.percent >= 100?100 + "%" : root.percent.toFixed(root.textFixedNumber).toString() + "%")
color: root.textColor
font.pixelSize: root.textPixSize
font.family: root.textFontFamily
}
}
进度条可分为有渐变色、无渐变色两种,具体使用参考上述代码中Item中的属性: