Search code examples
qtqml

How can I animate a QML three way toggle


I'm trying to make a three-way-toggle in qml where the red rectangle animates/slides to the clicked position. The toggle contains only images and no text:

enter image description here

MyToggle.qml

Rectangle {
        color: "gray"
        width: 400
        height: 50
        Row {
            id: rowId
            MyRadioButton {
                id: button1
                onClicked: {
                    console.log("clicked on button1")
                    animationId.restart()
                }
            }
            MyRadioButton {
                id: button2
                onClicked: {
                    console.log("clicked on button2")
                }
            }
            MyRadioButton {
                id: button3
                onClicked: {
                    console.log("clicked on button3")
                }
            }
        }
    }

    PropertyAnimation {
        id: animationId
        target: button3
        property: "x"
        duration: 1000
        to: 0
        easing.type: Easing.InOutQuad
    }
}

MyRadioButton.qml

RadioButton {
   id: button1
   width: 90
   height: 100
   Image {
    source: "qrc:/image.png"
    sourceSize.width: 50
    sourceSize.height: 50
    anchors.horizontalCenter: parent.horizontalCenter
}
indicator: Rectangle {
    width: 100
    height: 50
    color: "red"
    opacity: 0.8
    visible: button1.checked
  }
}

The PropertyAnimation is wrong because it moves the whole button and not just the indicator. If I move the animation into MyRadioButton, I won't know how far to move "x". Does anybody have some ideas how to implement this?


Solution

  • You can have your indicator Rectangle floating with a Behavior defined on x.

    Thanks, @Greko for the improved indicator target binding. I tried both but elected to go with ButtonGroup as it seemed more qml.

    import QtQuick
    import QtQuick.Controls
    Page {
        Frame {
            background: Rectangle { color: "gray" }
            padding: 0
            Row {
                id: rowId
                MyRadioButton {
                    id: button1
                    onClicked: {
                        console.log("clicked on button1")
                        animationId.restart()
                    }
                }
                MyRadioButton {
                    id: button2
                    onClicked: {
                        console.log("clicked on button2")
                    }
                }
                MyRadioButton {
                    id: button3
                    onClicked: {
                        console.log("clicked on button3")
                    }
                }
            }
        }
        ButtonGroup {
            id: buttonGroup
            buttons: rowId.children
        }
        Rectangle {
            id: indicator
            property Item target: buttonGroup.checkedButton
            x: target ? target.x : 0
            y: target ? target.y : 0
            width: target ? target.width : 0
            height: target ? target.height : 0
            color: "red"
            opacity: 0.5
            Behavior on x { NumberAnimation { duration: 300 }}
        }
    }
    
    // MyRadioButton.qml
    import QtQuick
    import QtQuick.Controls
    RadioButton {
        id: button1
        width: 90
        height: 50
        Image {
            source: "cross.svg"
            sourceSize.width: 50
            sourceSize.height: 50
            anchors.horizontalCenter: parent.horizontalCenter
        }
        indicator: Item { }
    }
    
    // cross.svg
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
    <path stroke="black" stroke-width="2" fill="none" d="M 8 8 24 24"/>
    <path stroke="black" stroke-width="2" fill="none" d="M 8 24 24 8"/>
    </svg>
    

    You can Try it Online!

    The following edit describes how to limit the NumberAnimation to only when an onClick occurs:

    import QtQuick
    import QtQuick.Controls
    Page {
        Frame {
            background: Rectangle { color: "gray" }
            padding: 0
            Row {
                id: rowId
                MyRadioButton {
                    id: button1
                    onClicked: NumberAnimation {
                        target: indicator
                        property: "x"
                        to: button1.x
                    }
                }
                MyRadioButton {
                    id: button2
                    onClicked: NumberAnimation {
                        target: indicator
                        property: "x"
                        to: button2.x
                    }
                }
                MyRadioButton {
                    id: button3
                    onClicked: NumberAnimation {
                        target: indicator
                        property: "x"
                        to: button3.x
                    }
                }
            }
        }
        ButtonGroup {
            id: buttonGroup
            buttons: rowId.children
        }
        Rectangle {
            id: indicator
            property Item target: buttonGroup.checkedButton
            x: 0
            y: target ? target.y : 0
            width: target ? target.width : 0
            height: target ? target.height : 0
            color: "red"
            opacity: target ? 0.5 : 0
        }
    }
    
    // MyRadioButton.qml
    import QtQuick
    import QtQuick.Controls
    RadioButton {
        id: button1
        width: 90
        height: 50
        Image {
            source: "cross.svg"
            sourceSize.width: 50
            sourceSize.height: 50
            anchors.horizontalCenter: parent.horizontalCenter
        }
        indicator: Item { }
    }
    
    // cross.svg
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
    <path stroke="black" stroke-width="2" fill="none" d="M 8 8 24 24"/>
    <path stroke="black" stroke-width="2" fill="none" d="M 8 24 24 8"/>
    </svg>