简体   繁体   English

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

[英]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; }
    }
}

示例图像

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

how it looks它看起来如何在此处输入图像描述

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.htmlhttp://qt-project.org/doc/qt-4.8/qml-extending.html

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM