Android приложение на QML: Picker

в 6:04, , рубрики: android, QML, qt, Разработка под android

Android приложение на QML: PickerМожно ли написать обыкновенное мобильное приложение на Qt Quick? Не игру, а именно традиционное приложение? Если полгода назад у меня были серьезные сомнения в осуществимости этого предприятия, то теперь сомнений не осталось — можно!

Конечно, на этом пути поджидало (и поджидает) множество проблем, большинство из которых описано тут. На данный момент накопилось уже приличное количество наработок, надеюсь эта статья положит начало циклу по систематизации и документированию опыта. Начнем с чего-нибудь простого и нужного, а именно с виджета выбора цифрового значения, по английски именуемого Picker. Такой используется в Android, когда нужно ввести дату, время, или какое-нибудь специфическое значение.

Под капотом

Логично, что для того, чтобы повторить, нужно сначала препарировать оригинальный виджет и понять из каких частей состоит. Итак, что мы имеем?

1) В основе лежит прокручиваемый список (отмечен синим), выбранный элемент которого находится в центре видимой части. А значит в качестве основы будем использовать стандартный ListView. Для того, чтобы реализовать выбор центрального элемента, нам необходимо:

  • Отслеживать окончание движения;
  • Находить элемент попадающий в геометрический центр по вертикали;
  • При необходимости анимировано докручивать его из половинчатой позиции;
  • Делать центральный индекс текущим;
  • Генерировать сигнал об изменении элемента;

Получившийся код

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

Следует отметить, что в оригинале списки часто циклические, однако получившийся qml-клон пока позволяет использовать только обычные.

Android приложение на QML: Picker

2) Поверх списка лежат разделители (отмечены оранжевым). Необходимы для визуального выделения выбранного элемента. Реализуются тривиальными прямоугольниками нужного цвета, с заданным смещением (соответственно на высоту одного и двух элементов).

3) Для придания эффекта засветки верхнего и нижнего элементов (отмечены зеленым) используется изображение с градиентом из белого в прозрачный. Так же накладывается сверху, с позиционированием проблем тоже никаких.

Код второго и третьего элементов

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

Выбор времени

Итак, сам виджет у нас теперь есть, осталось привести пример использования. Полноценный диалог выбора даты — тема для отдельной статьи (но желающие вполне могут посмотреть его уже сегодня вот тут). Потому потренируемся на кошках чем-нибудь более простом, например создадим пикеры как заготовку для диалога выбора времени. Их нам нужно целых два, для часов и минут соответственно. По середине, между ними, должен быть разделитель ":". Для выполнения этой задачи нужно заполнить модель значениями часов и минут, то есть сгенерировать значения от 0 до 23 и от 0 до 59. Если значение меньше 10, нужно дополнять его впереди идущим нулём. Для того, чтобы можно было выбрать крайние элементы списка, необходимо добавить пустые заглушки в начале и конце модели.
Android приложение на QML: Picker

    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
    }

Исходники проекта целиком.

Автор: Zifix

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js