如何在纯QML+JS中创建一个圆形进度条?

22

我的应用程序使用QML+JS制作,我想创建一个圆形进度条小部件。我可以使用QML矩形来创建圆形,并将其半径设置为其宽度的1/2以使其成为圆形。如何基于此创建进度条?

我计划实现以下模型。

输入图像描述


画布可以吗?当我为Ubuntu股票行情应用程序的一些用户界面工作时,我稍微摆弄了一下它,我认为它可能适合你想要的东西。 - iBelieve
@iBelieve 是的,我正在意识到这一点。希望有人已经准备好了一个带有圆形进度条的代码,因为我以前使用过Canvas,所以对我来说实现起来更加困难。 - Nik
7个回答

23

我使用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()
    }
}

嘿,谢谢。几天前我在网上找到了一个教程,然后就实现了它。你介意在你的代码中添加一些注释,特别是在onPaint()函数中,以便让每个人都能理解吗? 然后我就可以接受你的答案了。提前感谢您。 - Nik
更改是否适合您? - Charles
四边稍微被截断了一点.. 如何使它成为一个完美的圆形。 - George Thomas
@GeorgeThomas 这是因为半径大小太大了。如果你以 Math.min(canvas.width, canvas.height)/4 为例,它会完美地适配(但比填充整个父组件的大小要小)。只需根据其父级的大小进行调整并使其变小(还要考虑线条的宽度)。 - rbaleksandar

18

我在纯QML中找到了一种相当优雅的解决方案,也可以用于样式化常规的QtQuick ProgressBar组件。这背后的想法是在仅具边框的Rectangle上使用ConicalGradient

以下是代码:

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() + "%"
      }
   }
}

enter image description here


不错的想法和更漂亮的整体外观! - BaCaRoZzo
它正在为我绘制一个正方形! - George Thomas
@George: 如果你看到的是一个正方形,那么可能与这行代码有关:"radius: Math.max(width, height) / 2"。如果你在那里使用绝对的半径值,它是否有效? - warranty_void
不错的想法,只是提一下可以通过用默认颜色的圆形图像替换内部和外部圆来进一步简化。您还可以将“source”设置为“ConicalGradient”的图像。 - peco
outerRing 中应该使用 border.width 而不是 order.width - applesoup

4
我发现Diego Dotta在GitHub上的一个示例使用两个旋转圆圈,似乎非常适合这种用例。它涉及设置PropertyAnimation的持续时间。所以虽然对于可以设置的计时器来说这很有效,但对于你不知道需要多长时间的东西,就需要采用不同的方法。这被稍微调整并移植到了QtQuick 2.0:

main.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:

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; }
    }
}

example image


谢谢你的解决方案。我发现使用QML画布可以更简短、更简单地实现这个功能。但是,如果我无法在画布方法中实现某些功能,我仍然可以记住这个解决方案。 - Nik
我该如何导入Ubuntu.Components 0.1? - George Thomas

1

我尝试了Canvas,就像被接受的答案建议的那样,但我发现它很慢。在我的情况下,我需要指示器显示用户需要按住鼠标多长时间才能发生状态转换,如果指示器落后于实际时间,这会引起问题,因为用户认为他们有更多的时间,但实际上并没有。

我发现一个更快的解决方案是使用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
        }
    }
}

它的外观如何 输入图像描述


0

嗯,代码作者使用60个矩形(呈圆形)来模拟进度。我不确定这看起来会有多流畅。我会尝试一下并在这里回复评论。 - Nik

0

只需使用EEIoT(https://github.com/IndeemaSoftware/EEIoT)旋钮组件。将参数从角度:0和toAngle:Math.PI * 2更改。如果需要反向进度,请将reverse:true也设置为true。

Knob {
    id: knob
    x: 0
    y: 83
    width: 100
    height: 100
    from:0
    to: 100
    fromAngle: 0
    toAngle: Math.PI*2
    reverse: false
}

enter image description here


-4

最好的方法是使用PNG文件图像。因为它比纯QML运行得更快,特别是如果您使用渐变。如果您只想使用纯QML,则我没有找到任何方法,除非您向项目添加自定义C++模块。请参见http://qt-project.org/doc/qt-4.8/qml-extending.html


如何使用图像实现此操作 - George Thomas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接