Skip to content Skip to sidebar Skip to footer

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. I can create the circle using a QML Rectangle and settings its radius equal to its wi

Solution 1:

I've implemented a basic circular progress using a Canvas.

enter image description here

import QtQml 2.2import 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 colorif (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()
    }
}

Solution 2:

I found a kinda elegant solution in plain QML which can be also used for styling a regular QtQuick ProgressBar component. The idea behind this is to use a ConicalGradient on a border-only Rectangle.

Here is the code:

importQtQuick2.3importQtQuick.Controls.Styles1.2importQtGraphicalEffects1.0ProgressBarStyle
{
   panel : Rectangle
   {
      color: "transparent"implicitWidth: 80implicitHeight: implicitWidth

      Rectangle
      {
         id: outerRing
         z: 0
         anchors.fill: parent
         radius: Math.max(width, height) / 2color: "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

Solution 3:

I came across an example by Diego Dotta on GitHub using two rotating circles that seems to work nicely for this use case. It involves setting the duration of a 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:

main.qml:

importQtQuick2.0importUbuntu.Components0.1Rectangle {
    width:units.gu(50)height:units.gu(50)property int seconds :0LoadCircle {
        id:circleanchors.centerIn:parentloadtimer:10*1000//10secondsComponent.onCompleted:start();onFinishedChanged: {
            timer.stop();borderColor="green"
        }
    }

    Rectangle {
        id :theTimeranchors.centerIn:parentwidth :units.gu(10);height:units.gu(10)Label { 
            text:secondsfont.bold:truefontSize:"x-large"anchors.centerIn:parent
        }
    }

    Timer {
        id:timerinterval:1000;running:true;repeat:true;onTriggered:seconds++;
    }

}

LoadCircle.qml:

importQtQuick2.0importUbuntu.Components0.1Row{id:circleproperty int loadtimer:4000property color circleColor:"transparent"property color borderColor:"red"property int borderWidth:10property alias running:initCircle.runningproperty bool finished:false;width:units.gu(30)height:widthfunctionstart(){part1.rotation=180part2.rotation=180initCircle.start()}functionstop(){initCircle.stop()}Item{width:parent.width/2height:parent.heightclip:trueItem{id:part1width:parent.widthheight:parent.heightclip:truerotation:180transformOrigin:Item.RightRectangle{width:circle.width-(borderWidth*2)height:circle.height-(borderWidth*2)radius:width/2x:borderWidthy:borderWidthcolor:circleColorborder.color:borderColorborder.width:borderWidthsmooth:true}}}Item{width:parent.width/2height:parent.heightclip:trueItem{id:part2width:parent.widthheight:parent.heightclip:truerotation:180transformOrigin:Item.LeftRectangle{width:circle.width-(borderWidth*2)height:circle.height-(borderWidth*2)radius:width/2x:-width/2y:borderWidthcolor:circleColorborder.color:borderColorborder.width:borderWidthsmooth:true}}}SequentialAnimation{id:initCirclePropertyAnimation{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

Solution 4:

I know the solution using rotation property. See example

https://gitorious.org/apps-4-me/staq-me/source/fd20fe5b6fec053f364219842905e2afc5cfdc9d:ui.qml#L172

Solution 5:

Just use EEIoT (https://github.com/IndeemaSoftware/EEIoT) Knob component. Change parameters fromAngle: 0 and toAngle: Math.PI * 2. Also reverse: true if you need progress to be reversed

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

enter image description here

Post a Comment for "How To Create A Circular Progress Bar In Pure Qml+js?"