簡體   English   中英

如何在純 QML+JS 中創建圓形進度條?

[英]How to create a circular progress bar in pure QML+JS?

我的應用程序是使用 QML+JS 制作的,我希望創建一個圓形進度條小部件。 我可以使用 QML 矩形創建圓,並將其半徑設置為寬度/2,使其成為一個圓。 如何從中創建進度條?

我計划實現以下模型。

在此處輸入圖像描述

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

我在普通 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() + "%"
      }
   }
}

在此處輸入圖片說明

在 GitHub 上遇到了 Diego Dotta 的一個例子,它使用了兩個旋轉圓,似乎很適合這個用例。 它涉及設置 PropertyAnimation 的持續時間。 因此,雖然這對於您可以設置的計時器很有效,但對於您不知道需要多長時間的事情,它需要一種不同的方法。 這被稍微調整並移植到 QtQuick 2.0:

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

示例圖像

只需使用 EEIoT ( https://github.com/IndeemaSoftware/EEIoT ) 旋鈕組件。 更改參數 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
}

在此處輸入圖片說明

我嘗試了 Canvas 就像接受的答案所建議的那樣,但我發現它很慢。 在我的例子中,我需要指示器來顯示用戶在 state 轉換發生之前需要按住鼠標多長時間,如果指示器落后於它是有問題的,因為用戶認為他們有更多時間但實際上他們沒有。

我發現一個更快的解決方案是使用 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
        }
    }
}

它看起來如何在此處輸入圖像描述

最好的方法是使用 PNG 文件圖像。 因為它比純 qml 運行得更快,特別是如果你使用梯度。 如果您只想要純 qml,除非您向您的項目添加自定義 C++ 模塊,否則我沒有找到。 http://qt-project.org/doc/qt-4.8/qml-extending.html

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM