QML Android Application: Picker

Can I write an ordinary mobile application in Qt Quick? Not a game, but a traditional application? If six months ago I had serious doubts about the feasibility of this enterprise, now there is no doubt left - it is possible!

Of course, many problems awaited (and awaited) on this path, most of which are described here . At the moment, a decent amount of work has already accumulated, I hope this article will usher in a cycle to systematize and document experience. Let's start with something simple and necessary, namely a widget for selecting a digital value, in English called Picker. This is used in Android when you need to enter a date, time, or some specific value.


Under the hood


It is logical that in order to repeat, you must first dissect the original widget and understand what parts it consists of. So what do we have?

1) It is based on a scrollable list (marked in blue ), the selected element of which is in the center of the visible part. So, we will use the standard ListView as the basis. In order to realize the choice of the central element, we need:

  • Track the end of the movement;
  • Find an element falling into the geometric center vertically;
  • If necessary, twist it animatedly from a half position;
  • Make the central index current;
  • Generate a signal about an element change;

Resulting code
import QtQuick 2.0
Rectangle {
    id: rootRect
    property double itemHeight: 8*mm
    property alias model: listView.model
    signal indexChanged(int value)
    function setValue(value) {
        listView.currentIndex = value
        listView.positionViewAtIndex(value, ListView.Center);
    }
    ListView {
        id: listView
        clip: true
        anchors.fill: parent
        contentHeight: itemHeight*3
        delegate: Item {
            property var isCurrent: ListView.isCurrentItem
            id: item
            height: itemHeight
            width:  listView.width
            Rectangle {
                anchors.fill: parent
                Text {
                    text: model.text
                    font.pixelSize: 3*mm
                    anchors.centerIn: parent
                }
                MouseArea {
                    anchors.fill: parent
                    onClicked: {
                        rootRect.gotoIndex(model.index)
                    }
                }
            }
        }
        onMovementEnded: {
            var centralIndex = listView.indexAt(listView.contentX+1,listView.contentY+itemHeight+itemHeight/2)
            gotoIndex(centralIndex)
            indexChanged(currentIndex)
        }
    }
    function gotoIndex(inIndex) {
        var begPos = listView.contentY;
        var destPos;
        listView.positionViewAtIndex(inIndex, ListView.Center);
        destPos = listView.contentY;
        anim.from = begPos;
        anim.to = destPos;
        anim.running = true;
        listView.currentIndex = inIndex
    }
    NumberAnimation {
        id: anim;
        target: listView;
        property: "contentY";
        easing {
            type: Easing.OutInExpo;
            overshoot: 50
        }
    }
    function next() {
        gotoIndex(listView.currentIndex+1)
    }
    function prev() {
        gotoIndex(listView.currentIndex-1)
    }
}
It should be noted that in the original the lists are often cyclic, however, the resulting qml-clone so far allows you to use only ordinary ones.

2) Separators (marked in orange ) are on top of the list . Needed to visually highlight the selected item. They are implemented by trivial rectangles of the desired color, with a given offset (respectively, to the height of one and two elements).

3) To give the effect of illumination of the upper and lower elements (marked in green ), an image with a gradient from white to transparent is used. It is also superimposed on top, with positioning problems, too, no.

Code of the second and third elements
import QtQuick 2.0
import "../Global"
Rectangle {
    property alias model: pickerList.model
    signal indexSelected(int value)
    function setValue(value) {
        pickerList.setValue(value)
    }
    width: 10*mm
    height: 25*mm
    ACPickerList {
        id: pickerList
        width: parent.width
        height: parent.height
        onIndexChanged: {
            indexSelected(value)
        }
    }
    Image {
        id: upShadow
        sourceSize.height: 10*mm
        sourceSize.width: 10*mm
        source: "qrc:/img/images/icons/pickerShadowUp.svg"
        anchors {
            top: parent.top
        }
    }
    Image {
        id: downShadow
        sourceSize.height: 10*mm
        sourceSize.width: 10*mm
        source: "qrc:/img/images/icons/pickerShadowDown.svg"
        anchors {
            bottom: parent.bottom
        }
    }
    Rectangle {
        id: topSelector
        width: parent.width
        height: parseInt(0.3*mm)
        color: ACGlobal.style.holoLightBlue
        anchors {
            top: parent.top
            topMargin: pickerList.itemHeight
        }
    }
    Rectangle {
        id: bottomSelector
        width: parent.width
        height: parseInt(0.3*mm)
        color: ACGlobal.style.holoLightBlue
        anchors {
            top: parent.top
            topMargin: pickerList.itemHeight*2
        }
    }
}

Timing


So, we now have the widget itself, it remains to give an example of use. A full-fledged date picker is a topic for a separate article (but those who wish can well see it today right here ). Therefore, we will train on cats with something simpler, for example, create picers as a blank for the dialogue of timing. We need two of them, for hours and minutes, respectively. In the middle, between them, there should be a separator ":". To complete this task, you need to fill the model with the values ​​of hours and minutes, that is, generate values ​​from 0 to 23 and from 0 to 59. If the value is less than 10, you need to supplement it with a leading zero in front. In order to select the extreme elements of the list, it is necessary to add empty stubs at the beginning and end of the model.

    Rectangle {
        ACPicker {
            id: hoursPicker
            model:
                ListModel {
                id: hoursModel
                Component.onCompleted: {
                    append({ value: -1, text: " " })
                    for(var i = 0; i <= 23; i++){
                        var norm = i.toString();
                        if( i < 10 ) norm = "0" + i
                        append({ value: i, text: norm })
                    }
                    append({ value: -1, text: " " })
                }
            }
            anchors {
                right: center.left
                rightMargin: 1*mm
                verticalCenter: parent.verticalCenter
            }
        }
        Text {
            id: center
            text:":"
            font.pixelSize: 3*mm
            anchors.centerIn: parent
        }
        ACPicker {
            id: minutesPicker
            model:
                ListModel {
                id: minutesModel
                Component.onCompleted: {
                    append({ value: -1, text: " " })
                    for(var i = 0; i <= 59; i++){
                        var norm = i.toString();
                        if( i < 10 ) norm = "0" + i
                        append({ value: i, text: norm })
                    }
                    append({ value: -1, text: " " })
                }
            }
            anchors {
                left: center.right
                leftMargin: 1*mm
                verticalCenter: parent.verticalCenter
            }
        }
        anchors.fill: parent
    }

Sources of the project as a whole.

Also popular now: