Pull to refresh

Comments 57

Как разруливаете ситуацию с адоптацией UI под разные размеры экрана и разрешения?
Зависит от проекта. В основном, руководствуемся гайдом от Qt.
Но были проекты, например, где разработка сначала идёт под десктоп, а потом уже переносится на iOS/Android/WP. Из-за наличия большой кодовой базы просто масштабировали основной экран приложения, сохраняя пропорции (это применимо для планшетов в ландшафтной ориентации).
Ну и последнее, но не самое красивое решение — это при первоначальной загрузке высчитывать высоту базовой строки исходя из размеров экрана. А из этой базовой высоты уже строится высота всех остальных элементов. Тут нужно отметить, что если вы поддерживаете смарфтоны, то высоту лучше считать из портретной ориентации и не пересчитывать при повороте устройства, иначе в ландшафтной всё будет оч.мелко.
Так, в нескольких приложениях мы просто насовсем отключали клавиатуру и писали свою на базе Qt Virtual Keyboard

Это очень сильно пугает. У меня установлена кастомная клавиатура (нестандартная), и что, она работать не будет, и мне будут показывать обычную QWERTY?
Тут всё зависит от задачи, если приложение представляет из себя татарско-русский словарь, пользователю было бы удобнее не искать клавиатуру в плеймаркете/аппсторе/винсторе (тем более, если её там нет), либо добавлять через настройки нужный язык (тем более, если его в настройках нет) а пользоваться приложением сразу с интегрированной клавиатурой.
Мы на этот счёт проводили исследования на целевой группе, никто не смог найти и скачать кастомную клавиатуру из маркетов без подсказки. Жизнь приложения заканчивалась на фразе "блин, тут ещё чё-то ставить/настраивать надо" в случае с обучалкой, где рассказно, как поставить клавиатуру, либо "блин, а как тут вводить буквы-то" без обучалки.
Естественнно, если приложение не узко-специализированно, то нужно использовать стандартную клавиатуру.
Фух, а то реально напугали. Если это для настолько специализированных программ, то тогда понятно.
Самая сладость начинается тогда, когда нужно использовать разные версии NDK с учётом различных API, которые там представлены. Пытался использовать функции из posix для привязки треда к физическому ядру процессора с учётом cpu affinity — испытал непередаваемое удовольствие.
А приходилось ли сталкиваться с ситуацией, когда в приложении под Android, написанном на Qt, есть и foreground сервисы, и активити?
Да, естественнно, а также Broadcast Receiver'ы. С Content Provider'ами (самописными) не приходилось.
не выбирайте Qt/Qml!
Получается, вы используете QWidget и его потомков? Скажите, как он сейчас работает под android?
Я когда-то давно, когда порт под андроид еще назывался Necessitas, пытался с ними работать (точнее, просто откомпиллил виндово-убунтовое приложение) и столкнулся с тем что этот путь был совершенно непригоден т.к. виджеты были не адаптированы под тач, например, QTableView и, кажется, скролл имели крайне мелкие размеры скроллбаров, а диалог открытия файлов не ресайзился под экран и был раза этак в 2 больше телефона размером, при этом штатный путь кастомизации через QStyle попросту не срабатывал. В итоге плюнул и постепенно переписал приложение на QML, благо это домашний проект и чего сверхсложного там не было и нет.
Из сложностей — постоянно натыкаюсь на всяческие мелкие баги в контролах, то у ComboBox'а принципиально нельзя програмно выставить пустую строку (если присвоить comboBox1.currentIndex = индекс пустой строки текст попросту не меняется), то файловый диалог возвращает в винте пути в виде /C:/Users/myFile.txt которые едят не все компоненты qt работающие с путями (конкретно QFile), то еще что. В целом вроде и мелочи, но раздражает, хотя я бы все равно сказал что это классная библиотека.
Кстати, еще интересный вопрос — есть ли какой-то способ использовать разные элементы для разных платформ из qml? Вариант с подгрузкой через js я знаю, но мне он кажется несколько костыльным
Я не использую активно виджеты, поэтому написал именно про QML. Возможно, с QWidget есть способы это решить и потом внедрить решение в QML сцену.
По поводу второго вопроса не совсем понял, можно использовать Loader с указанием какие конкретно виджеты подгружать. В этом плане полезно смотреть на исходники Quick.Controls, как они там это решают.
А что же вы тогда имели в виду под "не выбирайте Qt/Qml"? Я как-то считал что когда речь идет о GUI на Qt, то можно использовать либо QWidget'ы, либо qml, либо же их комбинацию.
По поводу второго вопроса, да, про loader'ы я забыл, надо будет попробовать. Хотя у меня такой финт может быть и не пройдет — мне надо было загрузить разные меню для андроида и десктопа и хотелось написать что-то вида:
ApplicationWindow {

menuBar: Qt.platform.os == "android"? AndroidMenu{}:DesktopMenu{}

}
Когда я говорил про Не выбирайте Qt/Qml я хотел сказать, что не выбирайте его в качестве технологии для построения UI вашего мобильного приложения на Android если вам нужно очень много работать с текстом привычными для Android-пользователя методами.
Есть классная игра — VoltAir, сделанная ребятами из Google на базе QtQuick, в ней нет много работы с текстом — вот для этих целей, с моей точки зрения, Qml идеален (с учётом кроссплатформенности). Ради справедливости стоит отметить, если я не ошибаюсь, что ребята из Google правда подхачили немного исходники.
Спасибо за разъяснение. Я почему-то подумал что речь о том что вы отказались от qml вообще
В своем проекте использовал кастомные Qml компоненты, которые были реализованы в виде плагинов с общим интерфейсом. Правда проект был не нацелен на мобильные платформы, но думаю подобный подход использовать можно, назначив каждой платформе свой компонент.
То что можно понятно и я так и делаю, но самый простой способ который я смог найти был загрузка того или иного файла (не в виде плагина, а просто в виде отдельного qml которые отличаются для разных платформ) при помощи js через Qt.createComponent(), и я как раз хотел узнать есть ли более правильный способ.
Не знаю чем собираются Qt проекты под андроид но в CMake можно указать какую директорию в проект добавить в зависимости от ОС. По директориям распихать компоненты. CMake я правда взял исключительно ради возможности пользоваться CLion. И не уверен стоит ли такой подход затраченого времени в вашем случае.
qmake, и там тоже есть такая возможность. Другое дело, что придется подключать эти директории непосредственно в QML, а вот там препроцессора на импорты нет.
в QML нужно импортировать компонент, а не директорию. Если в соответствующих разным ОС директориях лежат компоненты с одинаковым названием, то в QML ничего не меняется. По крайней мере с плагинами это работает, не могу придумать причину почему не будет работать напрямую.
Даже если мы хотим импортировать компонент, а не папку целиком, нужно в импорте прописать путь до него, который будет отличаться.
Если правильно понял разговор, то в CMake добавляется что-то типа:
if(UNIX)
    install(FILES qml/unix/Foo.qml
            DESTINATION qml)
elseif(WIN32)
    install(FILES qml/win/Foo.qml
            DESTINATION qml)
endif()
Не лучший вариант на самом деле, если нужно поправить платформоспецифичный компонент, обратно его после тестирования придется руками копировать. Это ладно, один, а если 3-5...
Все изменения должны фиксироваться в системе контроля версии, а для тестеров
$ make
$ make package

на выходе rpm/deb/windows installer
Погодите, вот скопировался платформоспецифичный компонент в папку qml, я его поправил, как он обратно в qml/unix попадет?
я думаю править нужно исходный компонент, а не тот что скопировался
Я не могу править отдельно, мне нужно чтобы он подгружался в программу. Да, можно придумать костыли с явным указанием папки для разработки, но тогда смысл в правилах выше теряется.
Я имел в виду что-то типа такого: в каждой папке реализован компонент.
В основной программе регистрируем кастомный тип:
const QUrl MyClass("qrc:/qml/MyClass.qml");
qmlRegisterType<MyClass>("com.mycompany.mycomponents", 1, 0, "MyClass");

в QML используем:
import com.mycompany.mycomponents 1.0

MyClass {}

файл ресурсов подключается в зависимости от ОС
А в файле ресурсов прописываем алиасы, чтобы нивелировать различие путей, да. На самом деле хорошее решение, кода выйдет чуть меньше.
Я говорил именно про подключение напрямую внутри .qml файлов, без регистрации типов в плюсах, там альтернатив Qt.createComponent() нет, но оно не сильно напрягает.
Посмотрите в сторону Loader http://doc.qt.io/qt-5/qml-qtquick-loader.html
Думаю будет удобнее чем Qt.createComponent().
Справедливости ради: Предложение zmeykas подменять файлы при сборке мне нравится больше.
И чем Loader будет удобнее?
Тем, что GUI будет описан декларативно.
Он и так описан декларативно, просто подгружаем разные файлы:

    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);
        }
    }
Аналог этого кода:
Loader { source = (core.isIOS) ? "IosNavigationTabBar.qml" : "AndroidNavigationTabBar.qml" }
Вы можете заметить, что у компонентов разные родители, соответственно отображаются они в разных частях экрана.
да, этот момент я действительно просмотрел. Можно "засунуть" лоадеры в те места, которым принадлежат загружаемые компоненты, например:
Loader {
  source: "IosNavigationTabBar.qml"
  active: core.isIOS
}

Просто я считаю использование Qt.createComponent моветоном — оно нарушает декларативность
Можно, но зачем? Почему вы так считаете?
  1. В иерархии QML-файла компоненты создаются там же, где они отображаются.
  2. Код лаконичнее
  3. Свойства объекта задаются прямо в лоадере. createObject же задает свойства создаваемого объекта а. списком, б. по значению

Например, вот здесь: http://doc.qt.io/qt-5/qml-qtquick-loader.html#sourceComponent-prop
достаточно красивый пример правильного, на мой взгляд (и взгляд разработчиков Qt), использования динамической загрузки компонентов
  1. Да
  2. В простейшем случае — да. В сложном же — работа со свойствами и сигналами компонента в лоадере усложняется.
  3. В лоадере по сути тоже списком, просто он обычно представлен по одному выражению в строке ;)

Сложно представить, что на странице документации по Loader будет что-то другое. Вот страница по динамическому созданию от тех же разработчиков.

В целом я с вами согласен, что в простых случаях Loader предпочтительнее.
Только 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 графика отображается без проблем, а что-то сложнее — коверкается.
Согласен, но мы использовали статичные, вместо картинок. Допустим, удобно для стилизованных флагов с градиентом, внедрённых в форму, придуманную дизайнером. Основным бонусом для нас — было избавление от "чудных мнгновений", потраченных на ресайз картинок (хотя imagemagick и скрипты в этом помогает, но потом всё равно вручную их надо отсматривать и находить и исправлять "поковерканные" при ресайзе).
К слову сказать, нынешние VectorDrawables для Android тоже не идеальны. В Qt с этим проще.
Спасибо за статью. Я в свое время столкнулся тоже с некоторыми проблемами, что привело к появлению собственных велосипедов. Может они будут полезны и Вам в чем-то:
  • 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 под мобильные платформы, но как правило весь новый код затем можно переиспользовать почти везде.
Неплохо, а почему не строить работу с REST через WorkerScript и обычный Ajax? Если там возвращается JSON — то это очень просто и удобно. Ну и есть ListModel sync (если не нужны всякие фишки с сортировкой, fetchMore и т.д., которыми можно нагрузить разработчика серверной части).
Хотелось один раз сделать все полноценно. Почти в каждом проекте работающем с API есть стандартная потребность в получении данных и их сортировке/фильтрации/подгрузке, так и в отправке POST/PUT запросов. Плюс мне хотелось иметь доступ к моделям как из QML так и из C++ и располагать всеми средствами для предобработки данных из C++.
На JS можно было бы реализовать такую логику, но мне кажется код получился бы гораздо сложнее.
Сейчас, если Вы обратили внимание в репозитории описан сложный метод использования библиотеки — через создание производного класса. На самом же деле я планирую еще создать какой-нибудь простой QML-компонент для модели, чтобы ему модно было лишь передать параметр и показать API-метод из которого стоит брать данные. В этом случае программисту нужно будет лишь унаследовать и реализовать класс работы со своим API.
А как вы решаете момент с черным экраном при запуске Qt Quick приложения на Android?
Ну какой-то момент будет черным в любом случае, потом можно показывать картинку splash screen, задается стандартным образом в манифесте.
Но как то же тот же 2Гис решило проблему… У них при запуске программы черного экрана нет.
У них Qt патченное.
Можно использовать QSplashScreen и закрывать его по сигналу из QML, например когда нужные части UI прогрузились
Мы используем плавную анимацию opacity контейнера с контентом и фоновым цветом от 0 до 1. В итоге эффект следующий (он не совсем решает проблему, но немного улучшает поведение приложения): чёрный экран и плавное появление интерфейса вашего приложения.
Самое важное не написали: модуль QtLocation очень скуден для реального использования. По факту, подходит для демо тулзы с тайловой картой osm/here/mapbox, и эта карта даже не поддерживает вращения (bearing). Можно написать геоплагин, предоставляющий тайлы, это в принципе несложно. Но если же вы хотите написать свою векторную карту на основе QML Map, будьте готовы, что от оригинального кода QtLocation у вас останутся лишь формулы пересчета пикселей сцены в координаты.
Спасибо за разъяснения. Опыта работы как такового под Android/iOS/WP с QtLocation не было. Когда только начинал интегрироваться с "железными фичами", такими как камера — понял, что оно далеко от идеала, в связи с чем работу с сенсорами, гео, блютуз, камерой и прочим строю исключительно на базе нативных компонентов и уже при необходимости передаю результат обратно в Qt.


Поделитесь, пожалуйста, опытом сравнения с нативными аналогами.
Попробовал, да, действительно, проблема с тестом и Emoji решается. Насколько большой опыт использования у вас этой компоненты? Есть ли какие-то камни и т.д.?

И не знаете ли, почему они не используют это решение в своём Android приложении?
Посмотрите в профиль
Писалось под новое мобильное приложение в плэе идет под маркировкой "бэта", так что опыт от начала создания.
В своих кейсах почти все камни отловили и поправили.
Используем в новом мобильном приложении и в некоторых закрытых продуктах на базе текущей версии приложения.
Sign up to leave a comment.

Articles