Нативный код Android и iOS в Qt на примере status bar

в 15:24, , рубрики: android, iOS, java, objective-c, qt, мобильная разработка, разработка мобильных приложений

Здравствуйте! Уверен, многие слышали о том, что Qt очень хорош для кросплатформенной разработки мобильных приложений. Однако, для решения некоторых задач приходится иметь дело с нативным кодом (Java, Objective-C), к примеру, вызов камеры, галереи, вызов стороннего api.

В этой статье на простом примере задания прозрачности для status bar я покажу, как осуществляется вызов нативного кода Java и Objective-C.

вжух

Andoid

Возможность использования прозрачного status bar появилась в Android 4.4 KitKat. Для того, чтобы status bar стал прозрачным, необходимо в Activity нашего проекта указать флаг прозрачности для Window (не путать с QQuickWindow, который используется для отображения QML).

Если кто не знает, как переопределить свою Activity от QtActivity

Открываем вкладку Проекты → Добавить сборку под Andoid → “Сборка” → Нажимаем “Подробнее” в “Собрать Android APK” → “Создать шаблоны”.

Тем самым мы создали AndroidManifest, папку с ресурсами и файлы gradle, которые будут находится в папке android. Для размещения нашего java класса создадим папку src в папке android.

Создадим файл MyActivity.java. Важно, чтобы путь до файла совпадал с именем пакета, т.е. используя пакет с именем com.example.myPackage, путь должен быть android/src/com/example/myPackage/MyActivity.java

package com.example.myPackage;

import org.qtproject.qt5.android.bindings.QtActivity;
import android.app.Activity;
import android.os.Bundle;

public class MyActivity extends QtActivity
{
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
}

Теперь, нам надо задать имя Activity в AndroidManifest.xml. Ищем

android:name="com.example.myPackage.MyActivity"

и меняем на

android:name="org.qtproject.qt5.android.bindings.QtActivity"

Этими нехитрыми манипуляциями мы переопределили стандартную QtActivity.

Функция выставляющая флаг прозрачности status bar и пример её применения:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setTranparentStatusBar();
}

void function setTranparentStatusBar() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
            getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
            getWindow().setStatusBarColor(Color.TRANSPARENT);
        } else {
            getWindow().setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,                         WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
        }
}

Функция, возвращающая высоту status bar:

public int statusBarHeight() {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
        return 0;
    }

    int result = 0;
    int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
    if (resourceId > 0) {
        result = getResources().getDimensionPixelSize(resourceId);
    }

    return result;
}

Для того, чтобы вызвать метод Java класса из QML потребуется написать С++ класс, который будет использовать JNI. Для работы с JNI добавим в наш *.pro файл модуль Android:

QT += androidextras. 

Создадим singleton класс DeviceInfo.

DeviceInfo.h:

#pragma once

#include <QObject>

class DeviceInfo : public QObject
{
    Q_OBJECT

    Q_PROPERTY(int statusBarHeight READ statusBarHeight)

    public:
        DeviceInfo(QObject *parent = NULL);
        static DeviceInfo &instance(QObject *parent = 0);

        Q_INVOKABLE int statusBarHeight();

    private:
        static DeviceInfo _instance;
};

DeviceInfo.cpp:

#include "DeviceInfo.h"

#if defined(Q_OS_ANDROID)
    #include <QAndroidJniObject>
    #include <QtAndroidExtras>
    #include <QtAndroid>
#endif

DeviceInfo::DeviceInfo(QObject *parent)
    : QObject(parent)
{}

DeviceInfo &DeviceInfo::instance(QObject *parent)
{
    static DeviceInfo instance(parent);
    return instance;
}

int DeviceInfo::statusBarHeight()
{
#if defined (Q_OS_ANDROID)
    QAndroidJniObject activity = QtAndroid::androidActivity();
    jint height = activity.callMethod<jint>("statusBarHeight");

    return (int) height;
#endif
    return 0;
}

Далее, определим наш класс в QML:

view.rootContext()->setContextProperty("DeviceInfo", &DeviceInfo::instance());

Всё готово, осталось лишь вызвать метод statusBarHeight в QML:

Rectangle {
    width: parent.width
    height: DeviceInfo.statusBarHeight()
}

Итог на экране:

android screenshot

iOS

Возможность задания различного стиля у status bar в iOS появилась в iOS 7.0. Для того, чтобы status bar в нашем приложение был прозрачен, нам нужно сделать 3 вещи:

  1. Изменить info.plist, а именно, изменить ключ UIViewControllerBasedStatusBarAppearance:

<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>

  1. Для отображения QQuickView или QQuickWindow использовать метод showFullScreen() вместо show().

  1. Выставить у status bar стиль UIStatusBarStyleLightContent

Если с первыми двумя пунктами всё понятно, разберём третий более подробно. Изменить стиль у status bar'a можно следующим методом на Objective-C:

[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];

Функция, возвращающая высоту status bar’a:

[UIApplication sharedApplication].statusBarFrame.size.height

Для того, чтобы код на Objective-C работал в классе DeviceInfo, нам потребуется поменять разрешение исходника с .cpp на .mm. Поэтому в .pro файле сделаем следующее:

HEADERS += 
    Include/DeviceInfo.h

!ios {
    SOURCES += 
        Source/DeviceInfo.cpp
}

ios {
    OBJECTIVE_SOURCES += 
        Source/DeviceInfo.mm
}

DeviceInfo.mm:

#include "DeviceInfo.h"
#import <UIKit/UIKit.h>

DeviceInfo::DeviceInfo(QObject *parent)
    : QObject(parent)
{
    [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];
}

DeviceInfo &DeviceInfo::instance(QObject *parent)
{
    static DeviceInfo instance(parent);
    return instance;
}

int DeviceInfo::statusBarHeight()
{
    return [UIApplication sharedApplication].statusBarFrame.size.height;
}

Итог на экране:

ios screenshot

Заключение

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

Автор: Vonabirg

Источник


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


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