Форум программистов, компьютерный форум, киберфорум
Wyn
Войти
Регистрация
Восстановить пароль
Карта форума Блоги Сообщество Поиск Заказать работу  
Рейтинг: 5.00. Голосов: 1.

Рекомендации по QML

Запись от Wyn размещена 29.04.2016 в 15:04
Обновил(-а) Wyn 03.05.2016 в 23:18
Метки multiplatform, qml

[CENTER][SIZE="6"]Общий QML[/SIZE]

[SIZE="4"]Оглавление[/SIZE][/CENTER][LIST=1][*] [URL="https://www.cyberforum.ru/blogs/748276/blog4155.html"]Основы[/URL][LIST=1][*] Правила QML[*] Свойства и соединения[*] Динамическое создание объектов[*] JavaScript[*] Перевод приложения[/LIST][*] [URL="https://www.cyberforum.ru/blogs/748276/blog4225.html"]Взаимодействие между QML и C++[/URL][LIST=1][*] Основы[LIST=1][*] Нерегистрируемые в QML объекты[*] Регистрируемые в QML объекты[/LIST][*] Реализация специфичных для QML типов и аттрибутов[LIST=1][*] Прикреплённые объекты и аннотации[*] Модификаторы свойств[*] Определение свойства по-умолчанию для QML-объектов в С++[*] Получение уведомлений об инициализации объекта[/LIST][/LIST][*] [B]Рекомедации[/B][LIST=1][*] [B]Общие рекомендации[/B][*] [B]Оформление кода[/B][*] [B]Масштабируемость[/B][*] [B]Оптимизации[/B][/LIST][*] [URL="https://www.cyberforum.ru/blogs/748276/blog4226.html"]Полезности[/URL][LIST=1][*] Общие[LIST=1][*] Двунаправленное соединение[*] Скрытые свойства в подключаемом элементе[*] Неизменяемое снаружи свойство в подключаемом элементе, которое можно изменять внутри[*] Реализация класса асинхронной загрузки изображений[/LIST][*] Работа с компонентами[LIST=1][*] StackView(controls1)[/LIST][/LIST][*] Специфика платформ(пока нет)[LIST=1][*] Android[*] iOS[*] Linux[*] Windows[/LIST][*] [URL="https://www.cyberforum.ru/blogs/748276/blog4227.html"]Список ссылок и литературы[/URL][/LIST]
[CENTER][SIZE="5"][B]Рекомендации[/B][/SIZE]

[SIZE="4"]Общие рекомендации[/SIZE][/CENTER][LIST=1][*] Корневой элемент каждого документа лучше называть одинаково. К примеру - [INLINE]id: root[/INLINE][*] Следуйте QML Coding Conventions и базовым правилам оформления кода[*] Состояния(states) корневого элемента выносите в отдельный элемент(делайте их внутренними). В противном случае в случае при определении дополнительных внешних состояний они могут вызвать конфликт с теми, что определены внутри элемента
[SPOILER=Пример][JS]// пример многократно используемого компонента
Item {
id: rootComponent
// не определяйте состояния здесь
Item {
id: statesWrapper
states: .... // определяйте состояния здесь, это будет гарантировать, что они будут исключительно внутренними
}
}[/JS][/SPOILER][*] Не выстраивайте слишком сложную QML иерархию как по глубине, так и по структуре[*] Не используйте аббревиатуры для имён свойств или элементов, делайте ваш код легко читаемым и доступным для понимания[*] Опасайтесь конфликта имён свойств и элементов[/LIST]

[CENTER][SIZE="4"]Оформление кода QML[/SIZE][/CENTER]

Оформление кода является весьма важным элементов программы. С помощью него поддержка программы и ориентация в её коде становятся существенно легче.

Есть несколько базовых общепринятых правил по оформлению кода QML:[LIST=1][*] Каждый уровень вложения отделяется четырьмя пробелами[*] Открывающая блок кода скобка { отделяется пробелом на той же строке[*] В случае нескольких команд на одной строке пробел ставится после каждой точки с запятой, чтобы визуально отделить последующие команды[/LIST]
[URL="http://doc.qt.io/qt-5/qml-codingconventions.html"]QML Coding Conventions[/URL]:
Документы QML всегда структурированы в данном порядке[LIST=1][*] id (идентификатор элемента)[*] property declarations (объявление свойств)[*] signal declarations (объявление сигналов)[*] JavaScript functions (функции JavaScript)[*] object properties (унаследованные(встроенные) свойства объекта)[*] child objects (подэлементы)[*] states (состояния)[*] transitions (переходы)[/LIST]На основании этой структуры выработайте для себя собственную структуру документа и следуйте ей.
К примеру мои документы обладают подобной структурой:
[SPOILER="Element1.qml"][JS] // название для любого используемого qml элемента начинается с заглавной буквы (исключение составляет разве что main.qml)
import QtQuick 2.5 // подключение стандартных библиотек QML
...

import "my.components" 1.0 as Components // подключение пользовательских библиотек
...

import "code.js" as NeededCode // подключение кода JS
...

// оформление корневого элемента документа
Item { // в документе всегда только один корневой элемент
id: root // желательно всегда его называть одинаково

property ... // объявления свойств
...

signal ... // объявления сигналов
...

on...Changed: // обработчики сигналов объявленных выше свойств
...

height: implicitHeight // инициализация базовых свойств элемента
width: implicitWidth
...

on...Changed: // обработчики сигналов базовых(унаследованных) свойств(в данном случае - свойств Item) Можно их конечно
... // размещать в других местах, но лично мне гораздо удобнее(и нагляднее для меня) размещать их в конце,
// отделяя их от обработчиков написанных мною свойств

QtObject { id: d } // внутренние(private) свойства и функции, недоступные для внешнего вызова

// список элементов
Item1 {
id: item1 // название элемента (если есть)

property ... // объявление дополнительных свойств элемента
...

signal ... // объявление дополнительных сигналов элемента
...

on...Changed: // обработчики сигналов дополнительных свойств
...

height: parent.height // инициализация базовых свойств элемента
width: parent.width
...

on...Changed: // обработка базовых свойств элемента
....

Item11 { } // список подэлементов

Item12 { }

...

states: // состояния
...

transitions: // переходы
...

Component.onDestruction: // обработчики сигналов закрытия и прочих подобных.
...
}

Item2 { }

...


states: // состояния
...

transitions: // переходы
...

onClosing: // обработчики сигналов закрытия и прочих подобных. Можно их конечно размещать в других местах,
... // но лично мне гораздо удобнее(и нагляднее для меня) размещать их в конце
}
[/JS][/SPOILER]


[CENTER][SIZE="4"]Масштабируемость[/SIZE][/CENTER]
[url]http://doc.qt.io/qt-5/scalability.html[/url]

Когда программа пишется под разные устройства возникают следующие проблемы[LIST=1][*] разные платформы поддерживают устройства с экранами разных размеров, соотношений сторон, ориентаций и разрешений[*] разные платформы имеют различные требования к пользовательскому интерфейсу и ожидания пользователей[/LIST]Для того, чтобы решить подобные проблемы существуют следующие рекомендации:[LIST=1][*] проектируйте пользовательский интерфейс используя [I]Qt Quick Controls[/I][*] используйте [I]слои[/I]([I]layouts[/I]) предоставляемые [I]Qt Quick Layouts[/I]
[SPOILER="Более подробно"]Qt Quick Layouts предоставляют средства для расположения элементов в строку, колонку или в сетку, используя QML-типы RowLayout, ColumnLayout и GridLayout. Свойства этих QML-типов содержат ориентацию их слоя и расстояния между ячейками.

У элементов, расположенных в любом Layout, есть дополнительные свойства, прикреплённые к ним, в которых можно определить минимальные, максимальные и предпочтительные значения для высоты, ширины и размера этих элементов в Layout.

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

Список вещей, которые лучше не делать со слоями:[LIST=1][*] не имейте привязок к свойствам x, y, width или height у элементов слоя(Layout) тк это будет конфликтовать с целью существования слоя и также может вызвать бесконечный цикл привязок.[*] не используйте сложные функции JavaScript, которые постоянно вызываются. Это приведёт к низкой производительности, особенно при анимациях.[*] не стройте никаких предположений о размерах контейнера или о размерах подэлементов. Старайтесь сделать гибкую структуру слоёв, которая сможет поглотить изменения в доступном пространстве.[*] не используйте слои если хотите, чтобы дизайн был выверенным до пиксела. Элементы слоя всегда будут автоматически размерены и позиционированы в зависимости от доступного пространства.[/LIST][/SPOILER][*] используйте [I]привязки[/I]([I]property binding[/I]) в ситуациях, когда не применяются слои(layouts). [*] при использовании тестового устройства высчитывайте [I]коэффициент масштабирования[/I] для изображений, [I]размеры шрифта[/I] и [I]поля[/I] относительно актуального размера экрана, или используйте автоматическое масштабирование(доступно с Qt 5.6).[*] загружайте платформо-специфичные настройки используя файловые селекторы([I]file selectors[/I])[*] загружайте компоненты по требованию используя [I]Loader[/I] или другие варианты динамической загрузки.[/LIST]Рассматривайте следующие шаблоны при проектировании интерфейса:[LIST=1][*] содержимое вида может быть похожим на экранах разных размеров и иметь расширяемые области. При использовании ApplicationWindow размер экрана будет автоматически рассчитан основываясь на размерах содержимого. Если при этом используются Qt Quick Layouts для размещения элементов содержимого, то они автоматически подгонят размер их элементов.[*] содержимое всей страницы на меньших устройствах может формировать лишь часть вида на больших устройствах. Поэтому рассматривайте создание отдельных компонентов(размещённых в отдельных QML-файлах). В меньших устройствах таким образом вид может содержать один элемент, а на больших с помощью загрузчиков подгружать дополнительные элементы.[*] для игр обычно нужно создать одинаковую доску для всех видов, чтобы не давать преимущество игрокам на больших экранах. Одно из решений это определить безопасную зону, которая вмешается на экран с наименьшим поддерживаемым соотношением сторон(обычно 3:2), а на всех остальных экранах использовать декорации, которые будут скрывать пустые области.[/LIST]Учтите, что непрерывное изменение размеров и перерасчёт экрана серьёзно нагружает устройство. Мобильные и встраиваемые системы могут не иметь достаточной производительности для слишком частого пересчёта размеров и позиций анимированных объектов для каждого кадра. В таком случае рассматривайте использование других методов(к примеру, привязок).


[CENTER][SIZE="4"]Оптимизации[/SIZE][/CENTER]

[SIZE="3"]Основные рекомендации[/SIZE][LIST=1]
[*] С самого начала проектируйте вашу программу на быстрый старт. Подумайте что самое первое вы хотите показать пользователю[*] Создавайте QML UI с как можно меньшим использованием js кода. Каждый вызов js-кода влечёт инициализацию jvm, а это очень накладная операция.[LIST][*] js-код должен быть сведён к минимуму. Используйте C++ для реализации бизнес логики приложения[*] js особенно плохо влияет на производительность во время выполнения анимаций. Старайтесь выносить его за их пределы[*] для установки значений используйте привязки вместо js-кода. Они работают в разы быстрее[*] вызовы jvm для исполнения js-кода должны быть сведены к минимуму[/LIST][*] Выносите тяжёлую логику из QML в С++[*] Используйте Qt Quick Compiler для компиляции QML файлов в код С++[*] Разделяйте QML приложение на отдельные файлы, содержащие обособленные элементы интерфейса. Не используйте один большой QML файл[*] Создавайте плагины QML, которые подключаются только когда необходимо[*] Позвольте плагинам QML запускать некритичные сервисы и завершать их, когда они больше не требуются[*] Долгие операции должны быть реализованы как асинхронные вызовы или переведены в отдельный поток, чтобы UI мог обновлять себя во время их выполнения[LIST][*]в QML для этого используется элемент WorkerScript, в котором возможно выполнение какой-то js операции. Не используйте множество WorkerScript одновременно или у вас будут проблемы[*]в С++ можно реализовать плагин, использующий QThreads, QThreadPool или аналогичные механизмы для выполнения нужных вам тяжёлых вычислений асинхронно[/LIST][*] Используйте динамическую загрузку и подгрузку элементов, чтобы контролировать объём потребляемой памяти и убыстрять время загрузки приложения[LIST][*] используйте Loader, чтобы динамически загружать и выгружать элементы[*] загружайте только необходимый минимум элементов при старте, чтобы максимально ускорить загрузку программы. Далее показывайте Spinner или Splash Screen и продолжайте на их фоне необходимые операции инициализации программы[*] используйте цепочку загрузки. Старайтесь запускайте Loader'ов не больше, чем у вас есть ядер[/LIST][*] Используйте Qt Quick Controls 2.0. Они куда более оптимизированны и легки по сравнению с 1.0. Особенно это касается мобильных и встраиваемых систем[*] Используйте anchors вместо привязок к координатам[*] Используйте как можно меньшую область при анимации различных переходов(Transitions)[*] Используйте как можно более простые элементы. К примеру Item вместо Rectangle, где возможно. Их создание и поддержка отнимают меньше ресурсов[*] Используйте при работе со строками StringBuilder(пример - "%1%2".arg().arg()) вместо +[*] Избегайте лишних преобразований(QML проводит преобразование, когда тип не совпадает с назначенным для свойства)
[SPOILER="Пример"]Image или, к примеру, BorderImage имеют свойство source, которое имеет тип url. И если к нему привяжут string-свойство, то QML будет вынуждено провести операцию преобразования его в url, что затратит ресурсы[/SPOILER][*] Используйте как можно меньше clip, opacity и smooth[*] Объединяйте общие свойства. Это немного уменьшит потребление памяти
[SPOILER="Пример"][JS]Button {
id: button

anchors.top: parent.top
anchors.right: parent.right
anchors.left: parent.left
}[/JS]
Такой вариант займёт чуть меньше памяти:
[JS]Button {
id: button

anchors { top: parent.top; right: parent.right; left: parent.left }
}[/JS][/SPOILER][/LIST]
[SIZE="3"]Рекомендации по работе с изображениями[/SIZE][LIST=1]
[*] Оптимизируйте png / jpg изображения[*] Используйте асинхронные операции при загрузке изображений(asynchronous у QML Image или QQuickAsyncImageProvider), чтобы UI не блокировался. При загрузке множества мелких изображений предпочтительнее использовать QQuickAsyncImageProvider.
[SPOILER="Наблюдения за различными вариантами асинхронной загрузки множества мелких изображений"]
Асинхронная загрузка. Решил проверить различные настройки асинхронной загрузки и посмотреть, что будет быстрее:
1) загрузка через класс асинхронной загрузки изображений (Пример реализации - ниже, в "полезностях")
2) включение asynchronous у QML Image в true
И т.д. и т.п.
Проверял варианты с помощью тестовой программы, в которой было два ресурсных файла. old_images.qrc содержал изображения различных пропорций(191 изображений, 4.4 мб). new_images.qrc - оквадраченные(нужно было, в данном случае можете рассматривать их применение для того, чтобы более-менее сбрасывать кэш загруженных изображений) в большую сторону изображения(191 изображений, 4.5 мб), притом в разных папках итоговое разрешение было разным(в одной 100х100, в другой 200х200, в третьй 250х250).
Изображения были максимально сжаты командой [BASH]find -name '*.png' -exec pngcrush -ow -rem allb -reduce {} \;[/BASH]
[SPOILER="Тестовая программа"]Реализация асинхронного класса ниже, в полезностях
[CODE]TEMPLATE = app

QT += qml quick
CONFIG += c++11

SOURCES += main.cpp \
asyncimageprovider.cpp

RESOURCES += qml.qrc \
new_images.qrc \
old_images.qrc
# new_images - квадратные

# Default rules for deployment.
include(deployment.pri)

HEADERS += \
asyncimageprovider.h
[/CODE]
[CPPQT] // main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>

#include <QDir>
#include <QDebug>
#include <QtQml>

#include "asyncimageprovider.h"

int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);

QQmlApplicationEngine engine;
AsyncImageProvider *asyncImageProvider = new AsyncImageProvider();
engine.addImageProvider("asyncImage", asyncImageProvider);

QStringList newDirsNames({ /* список ресурсных директорий с квадратными изображениями */ });
QStringList newFullFilesNames;
foreach (QString dirName, newDirsNames) {
QStringList filesNames = QDir(dirName).entryList(QDir::Files);
foreach (QString fileName, filesNames) {
newFullFilesNames.append(QDir(dirName).filePath(fileName));
}
}
qDebug() << QString("new first file: '%1'").arg(newFullFilesNames.value(0));
qDebug() << QString("new filesCount: %1").arg(newFullFilesNames.count());
engine.rootContext()->setContextProperty("newfilesList", newFullFilesNames);

QStringList oldDirsNames({ /* список ресурсных директорий с изображениями различных пропорций */ });
QStringList oldFullFilesNames;
foreach (QString dirName, oldDirsNames) {
QStringList filesNames = QDir(dirName).entryList(QDir::Files);
foreach (QString fileName, filesNames) {
oldFullFilesNames.append(QDir(dirName).filePath(fileName));
}
}
qDebug() << QString("old first file: '%1'").arg(oldFullFilesNames.value(0));
qDebug() << QString("old filesCount: %1").arg(oldFullFilesNames.count());
engine.rootContext()->setContextProperty("oldfilesList", oldFullFilesNames);

engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

return app.exec();
}[/CPPQT]
[JS] // main.qml
import QtQuick 2.6
import QtQuick.Window 2.2
import Qt.labs.controls 1.0

Window {
id: root

visible: true

height: 800
width: 1200

ListModel {
id: newModel

function load() {
newfilesList.forEach(function(file, i, filesList) {
append({'fileName' : "image://asyncImage/"+file }) // использование асинхронного класса
// append({'fileName' : file.substr(2) }) // использование обычной ресурсной загрузки
})
}
Component.onCompleted: { load(); console.log("new model completed, count: %1".arg(count)) }
}

ListModel {
id: oldModel

function load() {
oldfilesList.forEach(function(file, i, filesList) {
append({'fileName' : file.substr(2) })
})
}
Component.onCompleted: { load(); console.log("old model completed, count: %1".arg(count)) }
}

property bool loadImages: false
property int componentCount: 0

Column {
Row {
id: row
Button {
width: root.width / 2
text: "Old"
onClicked: {
repeater.model = []
loadImages = false
componentCount = 0
repeater.model = oldModel
console.time('old time')
loadImages = true
}
}
Button {
width: root.width / 2
text: "New"
onClicked: {
repeater.model = []
loadImages = false
componentCount = 0
repeater.model = newModel
console.time('new time')
loadImages = true
}
}
}

Flickable {
ScrollBar.vertical: ScrollBar { }

width: root.width
height: root.height - row.height

contentHeight: view.height
contentWidth: view.width

Grid {
id: view

property int delegateWidth: metrics.height * 2 // размер отображаемых изображений
columns: root.width / delegateWidth

Repeater {
id: repeater

delegate: Rectangle {
id: rect_deleg

height: view.delegateWidth
width: view.delegateWidth

border { width: 2; color: "red" }
radius: 5
clip: true

Image {
anchors.fill: parent
asynchronous: true
source: (loadImages) ? fileName : ""
fillMode: Image.PreserveAspectFit
onStatusChanged: {
if (status === Image.Ready) {
++componentCount
if (componentCount === newModel.count) {
switch (repeater.model) {
case newModel:
console.timeEnd('new time')
break
case oldModel:
console.timeEnd('old time')
break
default:
console.log("Error!")
}
}
}
}
}
}
}
}
}


}

FontMetrics { id: metrics }
}[/JS]
[/SPOILER]
Выводы по итогам наблюдений:
1) Использование асинхронного класса уменьшает время загрузки в 2-3 раза.
2) Использование asynchronous у QML Image увеличивает время загрузки на 10%
[/SPOILER][*] Операция масштабирования(scaling) очень трудоёмкая. Постарайтесь обойтись без неё. Для этого используйте картинки в их реальном размере.[*] Используйте sourceSize при работе с большими изображений чтобы уменьшить потребление памяти. Однако учтите, что при любом изменении параметров sourceSize изображение будет загружено заново. Поэтому старайтесь на привязывать их к другим, часто изменяющимся свойствам(к примеру, таким как height и width).[*] Не забывайте, что уже загруженные изображения заносятся в кэш. Их повторный вызов даст почти мгновенный результат.[*] Используйте smooth с оглядкой. Это весьма тредоёмкая операция. Если же вам нужно её использовать, то не забывайте выключать её на время выполнения анимаций.[*] Избегайте, если возможно, композиции изображения из множества элементов. Используйте вместо этого уже готовые изображения.[/LIST]
[SIZE="3"]Рекомендации по работе с 3д[/SIZE][LIST=1][*] Оптимизируйте 3д модели путём уменьшения количества вершин и удаления невидимых частей.[*] Оптимизируйте загрузку 3ж моделей путём [URL="http://blog.qt.io/blog/2016/01/08/qt3d-asset-conditioning/"]использования glTF[/URL].[/LIST]
[SIZE="3"]Рекомендации по работе с моделями и их представлений(ListView и т.д.)[/SIZE][LIST=1]
[*] Модель должна быть максимально быстрой. Делегаты представлений должны создаваться очень быстро, чтобы пролистывание видов было как можно более плавным. Поэтому функция data() модели должна быть способна выполняться более 1000 раз в секунду.[*] Дополнительный функционал в делегате(который к примеру запрашивается по клику на делегат) лучше создавать динамически(к примеру, Loader) по запросу.[*] Используйте минимум QML-элементов в делегате.[*] Используйте как можно меньше вычислений в делегате(особенно - js). Если возможно, используйте предрасчёты.[*] Используйте cacheBuffer (он определяет пространство за пределами видимости, в котором делегаты начинают загружаться) для моделей с малым количеством записей, чтобы полностью загружать их.[/LIST]
Размещено в Без категории
Показов 9313 Комментарии 0
Всего комментариев 0
Комментарии
 
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2024, CyberForum.ru