![](https://habrastorage.org/getpro/habr/post_images/853/747/bfd/853747bfdff28ff806ee041d8160fe69.png)
Qt я использую достаточно давно, начиная с версии 4.1. Не сказать, что я «профессионально» его использую, но опыт был разный — и работы с виджетами и эволюции до версии 5.6.
Некоторые примеры проектов:
- Пульт управления караоке-центром (Android/iOS)
- Русско-Татарский словарь с кастомной клавиатурой (Android/iOS/WP), тогда ещё даже API для iOS кастомных клавиатур не было
- Cоциальная сеть с разными примбамбахами под Android (гео, блютуз, чаты, фотки, профили и т.д.)
- Приложение для быстрого заказа цветов (Android/iOS/WP)
Кроме того, на Qt написано Android приложение 2gis, на котором вы и можете проверить большинство описанного здесь.
Заранее прошу меня поправить, если что-то из описанного здесь имеет ненайденные мною пути решения (буду благодарен, если вы их укажете). Всё нижесказанное относится по большей части к Android.
Проблема №1
Первое и самое главное на сегодняшний момент: если вам нужно много работать с текстом, вводимым пользователем — не выбирайте Qt/Qml!
Крайне не люблю восклицательные знаки, но тут этот знак на своём месте: вам будет крайне сложно реализовать привычное для пользователей целевой платформы работы с полями ввода, а именно:
- Выделение текста
- Copy & Paste
Суть проблемы: баг работы с элементом редактирования текста висит аж с 2014 года, отмечен как Important и зарегистрирован пользователем с Silver подпиской, но до сих пор не исправлен. В багтрекере описан обходной путь, но если вы хотите использовать не Quick.Controls а чистый Quick с TextEdit и TextInput — извините.
Возможно кто-то скажет, что я слишком многого хочу и TextEdit/TextInput это базовые компоненты, но, извините меня, отсутствие Copy & Paste в базовых компонентах и отсутствие его реализации в Controls не будут вам доставлять проблем до первого замечания Заказчика.
TextEdit не содержит сигналов работы с указателем, типичных для MouseArea, поэтому попытка реализовать показ контекстого меню через долгое нажатие (PressAndHold в терминах Qml) успехом не увенчается. Кроме того, попытка в лоб обернуть поле ввода в MouseArea подходит лишь для ограниченного числа сценариев, т.к. вам придётся долго и упорно реализовывать выставление курсора между буквами и словами.
Поэтому, остаётся либо лезть в исходники и кастомизировать поле ввода, либо смириться.
Проблема №2
Второе и самое любимое заказчиками приложений, содержащий социализацию: Emoji
Суть проблемы: отсутствует нативная обработка любимых всеми смайликов, что в полях ввода, что в тексте — развивайте фантазию и реализуйте сами.
Узнать, что такое на самом деле Emoji и какова нелёгкая судьба их реализации в различных ОС вам поможет статья в Википедии. По факту же, какие есть варианты:
- Использовать шрифт с поддержкой символов Emoji. Используйте FontForge для компиляции Roboto с Emoji!
- RichText с заменой символов Emoji на цветные png'шки
- Глубокая кастомизация поля ввода (можете посмотреть в исходниках телеграмма для десктопа)
Итого: либо оно выглядит некрасиво (вариант 1), либо глючит (вариант 2), либо требует отличных знаний внутренностей Qt (вариант 3 — а если они у вас есть, вам не стоит труда решить большинство проблем).
P.S. Забавная забавность — не выставляйте никаких inputMethodHints у поля ввода, иначе встроенная Android клавиатура с Emoji (iWinn IME) у вас просто не покажется.
Проблема №3
Третье и самое раздражающее: Мерцание и BlackScreen'ы — ваши лучшие друзья.
Это будет сопровождать вас при загрузке приложения, при попытке выставить windowSoftInputMode в AdjustResize, куски чёрного экрана будут тоже периодически появлятся. Поэтому тестируйте, тестируйте и ещё раз тестируйте на реальных девайсах.
Проблема №4
Четвёртое и самое трудноловимое: Шрифты
Суть проблемы: на разных девайсах каждый год всплывает одно и то же, симптомами похожее на закрытый баг. Вы не узнаете об этом, пока счастливый обладатель андроид-девайсана начнёт жаловаться на то, что шрифт плывёт, ломается, невидимый и т.д. В основном, это китайско-корейские девайсы.
Выход здесь только один — брать исходники Qt и патчить под конкретный GPU.
Проблема №5
Пятое и самое спорное: продвинутые контролы Qml — Camera и иже с ними.
Суть проблемы: частые краши, нехватка функциональности и прочие несоответствия стандартного пользовательского опыта нативных приложений. Лечится это всё очень просто — не стесняйтесь добавлять нативные компоненты (Activity в случае Android) в своё приложение. Да, от этого его кроссплатформенность снизится, а количество кода увеличится, но оно того стоит.
Бонус №1
Первое и самое важное: реальная кроссплатформенность.
Суть: после того, как ребята из SQLite портировали своё детище под WP, а скромный автор сего произведения указал на это ребятам из Qt, в порт Qt для WP был добавлен LocalStorage. Это счастье для всех любителей Qml.
Вы реально можете создавать приложения из одних исходников, реально под кучу платформ, при этом кастомизировать их в нужных местах исходя из возможностей и необходимостей платформы. Декларативный UI и js затягивают настолько сильно и позволяют писать настолько лаконичный код, что возвращаться после него на многословную Java + xml, либо спорный Swift + Storyboard'ы нет никакого желания.
Любые анимации, поддержка кастомных шрифтов, svg — это ли не счастье для мобильного разработчика?
Бонус №2
Второе и самое любимое заказчиками: нестандартность.
Суть: фраза от заказчика, приводящая к желанию убивать — «Сделайте как на айфоне». Теперь это не проблема, везде будет выглядеть примерно одинаково. Да, это нарушение гайдлайнов, да, это нехорошо и никогда так не делайте, но заказчик хочет так и у вас три пути — убедить его, уступить ему или отказаться от него, выбирайте сами.
Кроме того, при достаточном желании можно подхачить исходники платформы, так, как нужно именно вам. Так, в нескольких приложениях мы просто насовсем отключали клавиатуру и писали свою на базе Qt Virtual Keyboard, тогда как встроенные приложения таких возможностей не имели.
Бонус №3
Третье и самое любимое мной: скорость разработки.
Суть: в любом состоянии вы можете спроектировать UI практически любой сложности (исключая особенности взаимодействия с ОС, такие как поля ввода, обработка устройства ввода и т.д.). Если вы сам себе заказчик — то перед вами все дороги открыты.
Резюме
Начиная новый проект стоит прежде всего правильно для себя оценить границы развития этого проекта. Если в нём мало работы с нативными возможностями платформы и много нестандарта — используйте Qt. Если же наоборот — подумайте, сможете ли вы его доработать так, как вам нужно.
Спасибо за внимание! Поделитесь своим опытом использования Qt в разработке мобильных приложений в комментариях.
Upd 31.03.2016 16:53
Спасибо пользователю zsilas за наводку на интересную библиотеку QtOffscreenViews от команды 2gis, которая решает проблемы с вводом текста и показом emoji в полях ввода.
Комментарии (57)
Evengard
30.03.2016 13:54+1Так, в нескольких приложениях мы просто насовсем отключали клавиатуру и писали свою на базе Qt Virtual Keyboard
Это очень сильно пугает. У меня установлена кастомная клавиатура (нестандартная), и что, она работать не будет, и мне будут показывать обычную QWERTY?coursar
30.03.2016 14:04+2Тут всё зависит от задачи, если приложение представляет из себя татарско-русский словарь, пользователю было бы удобнее не искать клавиатуру в плеймаркете/аппсторе/винсторе (тем более, если её там нет), либо добавлять через настройки нужный язык (тем более, если его в настройках нет) а пользоваться приложением сразу с интегрированной клавиатурой.
Мы на этот счёт проводили исследования на целевой группе, никто не смог найти и скачать кастомную клавиатуру из маркетов без подсказки. Жизнь приложения заканчивалась на фразе "блин, тут ещё чё-то ставить/настраивать надо" в случае с обучалкой, где рассказно, как поставить клавиатуру, либо "блин, а как тут вводить буквы-то" без обучалки.
Естественнно, если приложение не узко-специализированно, то нужно использовать стандартную клавиатуру.Evengard
30.03.2016 16:22Фух, а то реально напугали. Если это для настолько специализированных программ, то тогда понятно.
RomanArzumanyan
30.03.2016 15:56Самая сладость начинается тогда, когда нужно использовать разные версии NDK с учётом различных API, которые там представлены. Пытался использовать функции из posix для привязки треда к физическому ядру процессора с учётом cpu affinity — испытал непередаваемое удовольствие.
aknew
30.03.2016 16:10+1не выбирайте Qt/Qml!
Получается, вы используете QWidget и его потомков? Скажите, как он сейчас работает под android?
Я когда-то давно, когда порт под андроид еще назывался Necessitas, пытался с ними работать (точнее, просто откомпиллил виндово-убунтовое приложение) и столкнулся с тем что этот путь был совершенно непригоден т.к. виджеты были не адаптированы под тач, например, QTableView и, кажется, скролл имели крайне мелкие размеры скроллбаров, а диалог открытия файлов не ресайзился под экран и был раза этак в 2 больше телефона размером, при этом штатный путь кастомизации через QStyle попросту не срабатывал. В итоге плюнул и постепенно переписал приложение на QML, благо это домашний проект и чего сверхсложного там не было и нет.
Из сложностей — постоянно натыкаюсь на всяческие мелкие баги в контролах, то у ComboBox'а принципиально нельзя програмно выставить пустую строку (если присвоить comboBox1.currentIndex = индекс пустой строки текст попросту не меняется), то файловый диалог возвращает в винте пути в виде /C:/Users/myFile.txt которые едят не все компоненты qt работающие с путями (конкретно QFile), то еще что. В целом вроде и мелочи, но раздражает, хотя я бы все равно сказал что это классная библиотека.
Кстати, еще интересный вопрос — есть ли какой-то способ использовать разные элементы для разных платформ из qml? Вариант с подгрузкой через js я знаю, но мне он кажется несколько костыльнымcoursar
30.03.2016 16:41Я не использую активно виджеты, поэтому написал именно про QML. Возможно, с QWidget есть способы это решить и потом внедрить решение в QML сцену.
По поводу второго вопроса не совсем понял, можно использовать Loader с указанием какие конкретно виджеты подгружать. В этом плане полезно смотреть на исходники Quick.Controls, как они там это решают.aknew
30.03.2016 16:54А что же вы тогда имели в виду под "не выбирайте Qt/Qml"? Я как-то считал что когда речь идет о GUI на Qt, то можно использовать либо QWidget'ы, либо qml, либо же их комбинацию.
По поводу второго вопроса, да, про loader'ы я забыл, надо будет попробовать. Хотя у меня такой финт может быть и не пройдет — мне надо было загрузить разные меню для андроида и десктопа и хотелось написать что-то вида:
ApplicationWindow {
…
menuBar: Qt.platform.os == "android"? AndroidMenu{}:DesktopMenu{}
…
}coursar
30.03.2016 17:42+1Когда я говорил про Не выбирайте Qt/Qml я хотел сказать, что не выбирайте его в качестве технологии для построения UI вашего мобильного приложения на Android если вам нужно очень много работать с текстом привычными для Android-пользователя методами.
Есть классная игра — VoltAir, сделанная ребятами из Google на базе QtQuick, в ней нет много работы с текстом — вот для этих целей, с моей точки зрения, Qml идеален (с учётом кроссплатформенности). Ради справедливости стоит отметить, если я не ошибаюсь, что ребята из Google правда подхачили немного исходники.aknew
30.03.2016 17:47Спасибо за разъяснение. Я почему-то подумал что речь о том что вы отказались от qml вообще
zmeykas
30.03.2016 17:07В своем проекте использовал кастомные Qml компоненты, которые были реализованы в виде плагинов с общим интерфейсом. Правда проект был не нацелен на мобильные платформы, но думаю подобный подход использовать можно, назначив каждой платформе свой компонент.
aknew
30.03.2016 17:15То что можно понятно и я так и делаю, но самый простой способ который я смог найти был загрузка того или иного файла (не в виде плагина, а просто в виде отдельного qml которые отличаются для разных платформ) при помощи js через Qt.createComponent(), и я как раз хотел узнать есть ли более правильный способ.
zmeykas
30.03.2016 17:23Не знаю чем собираются Qt проекты под андроид но в CMake можно указать какую директорию в проект добавить в зависимости от ОС. По директориям распихать компоненты. CMake я правда взял исключительно ради возможности пользоваться CLion. И не уверен стоит ли такой подход затраченого времени в вашем случае.
Zifix
30.03.2016 17:33qmake, и там тоже есть такая возможность. Другое дело, что придется подключать эти директории непосредственно в QML, а вот там препроцессора на импорты нет.
zmeykas
30.03.2016 17:39в QML нужно импортировать компонент, а не директорию. Если в соответствующих разным ОС директориях лежат компоненты с одинаковым названием, то в QML ничего не меняется. По крайней мере с плагинами это работает, не могу придумать причину почему не будет работать напрямую.
Zifix
30.03.2016 18:01Даже если мы хотим импортировать компонент, а не папку целиком, нужно в импорте прописать путь до него, который будет отличаться.
RPG18
30.03.2016 18:11Если правильно понял разговор, то в CMake добавляется что-то типа:
if(UNIX) install(FILES qml/unix/Foo.qml DESTINATION qml) elseif(WIN32) install(FILES qml/win/Foo.qml DESTINATION qml) endif()
Zifix
30.03.2016 18:26Не лучший вариант на самом деле, если нужно поправить платформоспецифичный компонент, обратно его после тестирования придется руками копировать. Это ладно, один, а если 3-5...
zmeykas
30.03.2016 18:12+1Я имел в виду что-то типа такого: в каждой папке реализован компонент.
В основной программе регистрируем кастомный тип:
const QUrl MyClass("qrc:/qml/MyClass.qml"); qmlRegisterType<MyClass>("com.mycompany.mycomponents", 1, 0, "MyClass");
в QML используем:
import com.mycompany.mycomponents 1.0 MyClass {}
файл ресурсов подключается в зависимости от ОСZifix
30.03.2016 18:20А в файле ресурсов прописываем алиасы, чтобы нивелировать различие путей, да. На самом деле хорошее решение, кода выйдет чуть меньше.
Я говорил именно про подключение напрямую внутри .qml файлов, без регистрации типов в плюсах, там альтернатив Qt.createComponent() нет, но оно не сильно напрягает.1KoT1
31.03.2016 08:47Посмотрите в сторону Loader http://doc.qt.io/qt-5/qml-qtquick-loader.html
Думаю будет удобнее чем Qt.createComponent().
Справедливости ради: Предложение zmeykas подменять файлы при сборке мне нравится больше.Zifix
31.03.2016 09:13И чем Loader будет удобнее?
1KoT1
31.03.2016 10:53Тем, что GUI будет описан декларативно.
Zifix
31.03.2016 11:10Он и так описан декларативно, просто подгружаем разные файлы:
Component.onCompleted: { var menuComponent; if (core.isIOS) { menuComponent = Qt.createComponent("IosNavigationTabBar.qml"); menu = menuComponent.createObject(panelApplication); } else { menuComponent = Qt.createComponent("AndroidNavigationTabBar.qml"); menu = menuComponent.createObject(mainActionBar); } }
Antervis
31.03.2016 11:44Аналог этого кода:
Loader { source = (core.isIOS) ? "IosNavigationTabBar.qml" : "AndroidNavigationTabBar.qml" }
Zifix
31.03.2016 11:51Вы можете заметить, что у компонентов разные родители, соответственно отображаются они в разных частях экрана.
Antervis
31.03.2016 12:10да, этот момент я действительно просмотрел. Можно "засунуть" лоадеры в те места, которым принадлежат загружаемые компоненты, например:
Loader { source: "IosNavigationTabBar.qml" active: core.isIOS }
Просто я считаю использование Qt.createComponent моветоном — оно нарушает декларативностьZifix
31.03.2016 12:13Можно, но зачем? Почему вы так считаете?
Antervis
31.03.2016 12:49+1- В иерархии QML-файла компоненты создаются там же, где они отображаются.
- Код лаконичнее
- Свойства объекта задаются прямо в лоадере. createObject же задает свойства создаваемого объекта а. списком, б. по значению
Например, вот здесь: http://doc.qt.io/qt-5/qml-qtquick-loader.html#sourceComponent-prop
достаточно красивый пример правильного, на мой взгляд (и взгляд разработчиков Qt), использования динамической загрузки компонентовZifix
31.03.2016 13:47- Да
- В простейшем случае — да. В сложном же — работа со свойствами и сигналами компонента в лоадере усложняется.
- В лоадере по сути тоже списком, просто он обычно представлен по одному выражению в строке ;)
Сложно представить, что на странице документации по Loader будет что-то другое. Вот страница по динамическому созданию от тех же разработчиков.
В целом я с вами согласен, что в простых случаях Loader предпочтительнее.
Antervis
31.03.2016 10:30+1в Qt 5 нет возможности вставлять QWidget'ы в QtQuick сцену: stackoverflow.com/questions/13014415/qt5-embed-qwidget-object-in-qml
Qt уходят от классических виджетов, и не просто так. QML проще в разработке, быстрее в рендеринге, да и кастомизируются проще.
sanja1989
30.03.2016 16:41Только SVG поддерживается не полностью. Есть очень хорошие векторные иконки Oxygen Icons (https://www.archlinux.org/packages/extra/any/oxygen-icons/). Но использовать их нормально не получается…
Вот цитата из документации (http://doc.qt.io/qt-5/svgrendering.html):
Qt supports the static features of SVG 1.2 Tiny.
Так что простая SVG графика отображается без проблем, а что-то сложнее — коверкается.coursar
30.03.2016 16:43+1Согласен, но мы использовали статичные, вместо картинок. Допустим, удобно для стилизованных флагов с градиентом, внедрённых в форму, придуманную дизайнером. Основным бонусом для нас — было избавление от "чудных мнгновений", потраченных на ресайз картинок (хотя imagemagick и скрипты в этом помогает, но потом всё равно вручную их надо отсматривать и находить и исправлять "поковерканные" при ресайзе).
К слову сказать, нынешние VectorDrawables для Android тоже не идеальны. В Qt с этим проще.
vitaly_KF
30.03.2016 17:45+2Спасибо за статью. Я в свое время столкнулся тоже с некоторыми проблемами, что привело к появлению собственных велосипедов. Может они будут полезны и Вам в чем-то:
- https://github.com/kafeg/qtrest — полноценный REST клиент, который умеет автоматически маппить JSON/XML в наследника QAbstractListModel, обрабатывать fetchMore и canFetchMore для пагинации, умеет передавать параметры сортировки и фильтрации и прочие нужные фишки. Имеет интерфейс из C++ и QML. Из коробки умеет работать с Yii2 REST API и Django REST Framework (ну как умеет, по их лекалам сделан =) ). Пока в полубете, много еще чего нужно сделать.
- https://github.com/kafeg/adctl — QtAdMob и Google Play Game Services для Qml.
Не радует конечно, что очень много приходится делать с нуля при разработке на Qt под мобильные платформы, но как правило весь новый код затем можно переиспользовать почти везде.coursar
30.03.2016 18:07Неплохо, а почему не строить работу с REST через WorkerScript и обычный Ajax? Если там возвращается JSON — то это очень просто и удобно. Ну и есть ListModel sync (если не нужны всякие фишки с сортировкой, fetchMore и т.д., которыми можно нагрузить разработчика серверной части).
vitaly_KF
30.03.2016 18:19Хотелось один раз сделать все полноценно. Почти в каждом проекте работающем с API есть стандартная потребность в получении данных и их сортировке/фильтрации/подгрузке, так и в отправке POST/PUT запросов. Плюс мне хотелось иметь доступ к моделям как из QML так и из C++ и располагать всеми средствами для предобработки данных из C++.
На JS можно было бы реализовать такую логику, но мне кажется код получился бы гораздо сложнее.
Сейчас, если Вы обратили внимание в репозитории описан сложный метод использования библиотеки — через создание производного класса. На самом же деле я планирую еще создать какой-нибудь простой QML-компонент для модели, чтобы ему модно было лишь передать параметр и показать API-метод из которого стоит брать данные. В этом случае программисту нужно будет лишь унаследовать и реализовать класс работы со своим API.
Harrix
31.03.2016 06:05А как вы решаете момент с черным экраном при запуске Qt Quick приложения на Android?
Zifix
31.03.2016 09:02Ну какой-то момент будет черным в любом случае, потом можно показывать картинку splash screen, задается стандартным образом в манифесте.
Harrix
31.03.2016 10:04Но как то же тот же 2Гис решило проблему… У них при запуске программы черного экрана нет.
zsilas
31.03.2016 14:25+2Можно использовать QSplashScreen и закрывать его по сигналу из QML, например когда нужные части UI прогрузились
coursar
31.03.2016 10:29+1Мы используем плавную анимацию opacity контейнера с контентом и фоновым цветом от 0 до 1. В итоге эффект следующий (он не совсем решает проблему, но немного улучшает поведение приложения): чёрный экран и плавное появление интерфейса вашего приложения.
Antervis
31.03.2016 10:58+2Самое важное не написали: модуль QtLocation очень скуден для реального использования. По факту, подходит для демо тулзы с тайловой картой osm/here/mapbox, и эта карта даже не поддерживает вращения (bearing). Можно написать геоплагин, предоставляющий тайлы, это в принципе несложно. Но если же вы хотите написать свою векторную карту на основе QML Map, будьте готовы, что от оригинального кода QtLocation у вас останутся лишь формулы пересчета пикселей сцены в координаты.
coursar
31.03.2016 11:43+1Спасибо за разъяснения. Опыта работы как такового под Android/iOS/WP с QtLocation не было. Когда только начинал интегрироваться с "железными фичами", такими как камера — понял, что оно далеко от идеала, в связи с чем работу с сенсорами, гео, блютуз, камерой и прочим строю исключительно на базе нативных компонентов и уже при необходимости передаю результат обратно в Qt.
Поделитесь, пожалуйста, опытом сравнения с нативными аналогами.
zsilas
31.03.2016 12:05+2По первой проблеме есть вполне себе готовое решение для андроида.
Можно использовать обертки над нативными полями ввода.
https://gitlab.com/2gisqtandroid/qtandroidextensions/tree/master/QtOffscreenViewscoursar
31.03.2016 16:48Попробовал, да, действительно, проблема с тестом и Emoji решается. Насколько большой опыт использования у вас этой компоненты? Есть ли какие-то камни и т.д.?
И не знаете ли, почему они не используют это решение в своём Android приложении?zsilas
31.03.2016 17:28+2Писалось под новое мобильное приложение в плэе идет под маркировкой "бэта", так что опыт от начала создания.
В своих кейсах почти все камни отловили и поправили.
Используем в новом мобильном приложении и в некоторых закрытых продуктах на базе текущей версии приложения.
RPG18
Как разруливаете ситуацию с адоптацией UI под разные размеры экрана и разрешения?
coursar
Зависит от проекта. В основном, руководствуемся гайдом от Qt.
Но были проекты, например, где разработка сначала идёт под десктоп, а потом уже переносится на iOS/Android/WP. Из-за наличия большой кодовой базы просто масштабировали основной экран приложения, сохраняя пропорции (это применимо для планшетов в ландшафтной ориентации).
Ну и последнее, но не самое красивое решение — это при первоначальной загрузке высчитывать высоту базовой строки исходя из размеров экрана. А из этой базовой высоты уже строится высота всех остальных элементов. Тут нужно отметить, что если вы поддерживаете смарфтоны, то высоту лучше считать из портретной ориентации и не пересчитывать при повороте устройства, иначе в ландшафтной всё будет оч.мелко.
Zifix
https://habrahabr.ru/post/268187/#comment_8600643