[英]How to create a circular progress bar in pure QML+JS?
My application is made using QML+JS and I am looking to create a circular progress bar widget.我的应用程序是使用 QML+JS 制作的,我希望创建一个圆形进度条小部件。 I can create the circle using a QML Rectangle and settings its radius equal to its width/2 to make it into a circle.
我可以使用 QML 矩形创建圆,并将其半径设置为宽度/2,使其成为一个圆。 How do I create a progress bar out of it?
如何从中创建进度条?
I am planning to implement the following mockup.我计划实现以下模型。
I've implemented a basic circular progress using a Canvas.我已经使用 Canvas 实现了一个基本的循环进度。
import QtQml 2.2
import QtQuick 2.0
// draws two arcs (portion of a circle)
// fills the circle with a lighter secondary color
// when pressed
Canvas {
id: canvas
width: 240
height: 240
antialiasing: true
property color primaryColor: "orange"
property color secondaryColor: "lightblue"
property real centerWidth: width / 2
property real centerHeight: height / 2
property real radius: Math.min(canvas.width, canvas.height) / 2
property real minimumValue: 0
property real maximumValue: 100
property real currentValue: 33
// this is the angle that splits the circle in two arcs
// first arc is drawn from 0 radians to angle radians
// second arc is angle radians to 2*PI radians
property real angle: (currentValue - minimumValue) / (maximumValue - minimumValue) * 2 * Math.PI
// we want both circle to start / end at 12 o'clock
// without this offset we would start / end at 9 o'clock
property real angleOffset: -Math.PI / 2
property string text: "Text"
signal clicked()
onPrimaryColorChanged: requestPaint()
onSecondaryColorChanged: requestPaint()
onMinimumValueChanged: requestPaint()
onMaximumValueChanged: requestPaint()
onCurrentValueChanged: requestPaint()
onPaint: {
var ctx = getContext("2d");
ctx.save();
ctx.clearRect(0, 0, canvas.width, canvas.height);
// fills the mouse area when pressed
// the fill color is a lighter version of the
// secondary color
if (mouseArea.pressed) {
ctx.beginPath();
ctx.lineWidth = 1;
ctx.fillStyle = Qt.lighter(canvas.secondaryColor, 1.25);
ctx.arc(canvas.centerWidth,
canvas.centerHeight,
canvas.radius,
0,
2*Math.PI);
ctx.fill();
}
// First, thinner arc
// From angle to 2*PI
ctx.beginPath();
ctx.lineWidth = 1;
ctx.strokeStyle = primaryColor;
ctx.arc(canvas.centerWidth,
canvas.centerHeight,
canvas.radius,
angleOffset + canvas.angle,
angleOffset + 2*Math.PI);
ctx.stroke();
// Second, thicker arc
// From 0 to angle
ctx.beginPath();
ctx.lineWidth = 3;
ctx.strokeStyle = canvas.secondaryColor;
ctx.arc(canvas.centerWidth,
canvas.centerHeight,
canvas.radius,
canvas.angleOffset,
canvas.angleOffset + canvas.angle);
ctx.stroke();
ctx.restore();
}
Text {
anchors.centerIn: parent
text: canvas.text
color: canvas.primaryColor
}
MouseArea {
id: mouseArea
anchors.fill: parent
onClicked: canvas.clicked()
onPressedChanged: canvas.requestPaint()
}
}
I found a kinda elegant solution in plain QML which can be also used for styling a regular QtQuick ProgressBar component.我在普通 QML 中找到了一种优雅的解决方案,它也可用于为常规 QtQuick ProgressBar 组件设置样式。 The idea behind this is to use a
ConicalGradient
on a border-only Rectangle
.这背后的想法是在仅边框的
Rectangle
上使用ConicalGradient
。
Here is the code:这是代码:
import QtQuick 2.3
import QtQuick.Controls.Styles 1.2
import QtGraphicalEffects 1.0
ProgressBarStyle
{
panel : Rectangle
{
color: "transparent"
implicitWidth: 80
implicitHeight: implicitWidth
Rectangle
{
id: outerRing
z: 0
anchors.fill: parent
radius: Math.max(width, height) / 2
color: "transparent"
border.color: "gray"
order.width: 8
}
Rectangle
{
id: innerRing
z: 1
anchors.fill: parent
anchors.margins: (outerRing.border.width - border.width) / 2
radius: outerRing.radius
color: "transparent"
border.color: "darkgray"
border.width: 4
ConicalGradient
{
source: innerRing
anchors.fill: parent
gradient: Gradient
{
GradientStop { position: 0.00; color: "white" }
GradientStop { position: control.value; color: "white" }
GradientStop { position: control.value + 0.01; color: "transparent" }
GradientStop { position: 1.00; color: "transparent" }
}
}
}
Text
{
id: progressLabel
anchors.centerIn: parent
color: "black"
text: (control.value * 100).toFixed() + "%"
}
}
}
I came across an example by Diego Dotta on GitHub using two rotating circles that seems to work nicely for this use case.我在 GitHub 上遇到了 Diego Dotta 的一个例子,它使用了两个旋转圆,似乎很适合这个用例。 It involves setting the duration of a PropertyAnimation.
它涉及设置 PropertyAnimation 的持续时间。 So while this works well for a timer that you can set, it would need a different approach for something you didn't know how long it would take.
因此,虽然这对于您可以设置的计时器很有效,但对于您不知道需要多长时间的事情,它需要一种不同的方法。 This is tweaked a bit and ported to QtQuick 2.0:
这被稍微调整并移植到 QtQuick 2.0:
main.qml :主.qml :
import QtQuick 2.0
import Ubuntu.Components 0.1
Rectangle {
width: units.gu(50)
height: units.gu(50)
property int seconds : 0
LoadCircle {
id: circle
anchors.centerIn: parent
loadtimer: 10*1000 // 10 seconds
Component.onCompleted: start();
onFinishedChanged: {
timer.stop();
borderColor = "green"
}
}
Rectangle {
id : theTimer
anchors.centerIn: parent
width : units.gu(10) ; height: units.gu(10)
Label {
text: seconds
font.bold: true
fontSize: "x-large"
anchors.centerIn: parent
}
}
Timer {
id: timer
interval: 1000; running: true; repeat: true;
onTriggered: seconds++;
}
}
LoadCircle.qml : LoadCircle.qml :
import QtQuick 2.0
import Ubuntu.Components 0.1
Row{
id: circle
property int loadtimer: 4000
property color circleColor: "transparent"
property color borderColor: "red"
property int borderWidth: 10
property alias running: initCircle.running
property bool finished: false;
width: units.gu(30)
height: width
function start(){
part1.rotation = 180
part2.rotation = 180
initCircle.start()
}
function stop(){
initCircle.stop()
}
Item{
width: parent.width/2
height: parent.height
clip: true
Item{
id: part1
width: parent.width
height: parent.height
clip: true
rotation: 180
transformOrigin: Item.Right
Rectangle{
width: circle.width-(borderWidth*2)
height: circle.height-(borderWidth*2)
radius: width/2
x:borderWidth
y:borderWidth
color: circleColor
border.color: borderColor
border.width: borderWidth
smooth: true
}
}
}
Item{
width: parent.width/2
height: parent.height
clip: true
Item{
id: part2
width: parent.width
height: parent.height
clip: true
rotation: 180
transformOrigin: Item.Left
Rectangle{
width: circle.width-(borderWidth*2)
height: circle.height-(borderWidth*2)
radius: width/2
x: -width/2
y: borderWidth
color: circleColor
border.color: borderColor
border.width: borderWidth
smooth: true
}
}
}
SequentialAnimation{
id: initCircle
PropertyAnimation{ target: part2; property: "rotation"; to:360; duration:loadtimer/2 }
PropertyAnimation{ target: part1; property: "rotation"; to:360; duration:loadtimer/2 }
ScriptAction { script: finished = true; }
}
}
I know the solution using rotation
property.我知道使用
rotation
属性的解决方案。 See example查看示例
https://gitorious.org/apps-4-me/staq-me/source/fd20fe5b6fec053f364219842905e2afc5cfdc9d:ui.qml#L172 https://gitorious.org/apps-4-me/staq-me/source/fd20fe5b6fec053f364219842905e2afc5cfdc9d:ui.qml#L172
Just use EEIoT ( https://github.com/IndeemaSoftware/EEIoT ) Knob component.只需使用 EEIoT ( https://github.com/IndeemaSoftware/EEIoT ) 旋钮组件。 Change parameters fromAngle: 0 and toAngle: Math.PI * 2. Also reverse: true if you need progress to be reversed
更改参数 fromAngle: 0 和 toAngle: Math.PI * 2。如果需要反转进度,也可以反转:true
Knob {
id: knob
x: 0
y: 83
width: 100
height: 100
from:0
to: 100
fromAngle: 0
toAngle: Math.PI*2
reverse: false
}
I tried Canvas like the accepted answer suggested, but I found it was slow.我尝试了 Canvas 就像接受的答案所建议的那样,但我发现它很慢。 In my case, I needed the indicator to show how long the user needs to hold the mouse before a state transition will happen, and if the indicator lags behind it is problematic because the user thinks they have more time but really they don't.
在我的例子中,我需要指示器来显示用户在 state 转换发生之前需要按住鼠标多长时间,如果指示器落后于它是有问题的,因为用户认为他们有更多时间但实际上他们没有。
I found a faster solution was to use Shape.我发现一个更快的解决方案是使用 Shape。
import QtQuick 2.15
import QtQuick.Shapes 1.15
Shape {
id: root
property real radius: 18
property alias strokeWidth: path.strokeWidth
// value between 0 and 1
property real progress: .75
// don't set these externally. Set radius instead
width: radius * 2
height: width
// antialiasing
layer.enabled: true
layer.samples: 8
ShapePath {
id: path
fillColor: "transparent"
strokeColor: "#77999999"
strokeWidth: 3
startX: radius
startY: strokeWidth/2
PathArc {
x: radiusX * Math.sin(Math.PI * 2 * progress) + radius
y: -radiusY * Math.cos(Math.PI * 2 * progress) + radius
radiusX: radius - strokeWidth/2
radiusY: radius - strokeWidth/2
useLargeArc: x < radius
}
}
}
The best way would be to use PNG file image.最好的方法是使用 PNG 文件图像。 Because it runs faster than pure qml, particulary if you use gradient.
因为它比纯 qml 运行得更快,特别是如果你使用梯度。 If you want pure qml only, I didn't find anyway except if you add a custom C++ module to youproject.
如果您只想要纯 qml,除非您向您的项目添加自定义 C++ 模块,否则我没有找到。 See http://qt-project.org/doc/qt-4.8/qml-extending.html
见http://qt-project.org/doc/qt-4.8/qml-extending.html
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.