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

представник: 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-клон поки дозволяє використовувати тільки звичайні.
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, потрібно доповнювати його попереду йде нулем. Для того, щоб можна було вибрати крайні елементи списку, необхідно додати порожні заглушки на початку і наприкінці моделі.

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
}

Исходники проекту.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.