
QML Android Application: Picker

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

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

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.