QMLで様々な解像度のディスプレイに対応する方法(提案)

だらだらアプリを書いている間に思いついた方法を書いてみます．

# QMLの不便だなと感じるところ
QMLにはAndroidのdpみたいなデバイス間でいい感じにUIのサイズを調整するための単位がありません．デスクトップ・モバイル両対応のアプリを作りたいとき，様々なモバイルデバイスに対応したアプリを作りたいときなどなどに，各々の開発者がなんらか工夫しなければなりません

# 解決策
## 1 画面サイズに対する比でUIのサイズを決める
ヘッダーバーの高さは画面の高さの1/10とか，ボタンの幅は画面の幅の2/3とか．しかし，これだと画面を回転させるとUIのサイズが変わることになってしまいます．

## 2 物理的な単位を用いる
main.qmlで`import QtQuick.Window 2.1`とし，

```qml:main.qml
property real mm: Screen.pixelDensity
```

と書いておくと，main.qmlから利用される全てのQMLファイル内でmm単位を利用できるので，`width: 5*mm`のようにして長さを指定できます．
しかし全てのデバイスでUIの物理的な大きさが同じというのはやはり違います．物理サイズの大きな画面ではUI部品を大きく，小さな画面では小さく表示したいものです

## 3 Androidのdpのような単位を作る
この方法は.qmlprojectを利用したアプリには利用できません．
先ず，プロジェクトファイル(.pro)に次の一文を追加します(Android対応しない場合は不要です)．

```mf:myApp.pro
android : QT += androidextras
```

次に，main.cppに`#include`文を追加し，main関数に次のような内容を追記します．

```cpp:main.cpp
#include <QScreen>
#ifdef Q_OS_ANDROID
#include <QtAndroidExtras/QAndroidJniObject>
#endif

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    QQmlApplicationEngine engine();

    //implement density-independent pixel
    int dp = 1; // 普通の解像度のPCでは1dp = 1pixel
    // PC向けコード 1inchあたりのpixel数を125で割る
    qreal dotsPerInch = app.screens()[0]->physicalDotsPerInch();
    qDebug() << "physicalDotsPerInch = " << dotsPerInch;
    dp = qRound(dotsPerInch) / 125;
#ifdef Q_OS_ANDROID
    // Androidにて1dpが何pixelであるか取得するためのコード
    qDebug() << "Android OS detected";
    QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative", "activity", "()Landroid/app/Activity;");
    QAndroidJniObject resource = activity.callObjectMethod("getResources","()Landroid/content/res/Resources;");
    QAndroidJniObject metrics = resource.callObjectMethod("getDisplayMetrics","()Landroid/util/DisplayMetrics;");
    dp = metrics.getField<float>("density");
#endif
#ifdef Q_OS_IOS
    // iOS用コード
    qDebug() <<	"iOS detected";
    dp = 3;
#endif
    qDebug() << "1dp = " << dp;
    // 定数dpを登録
    engine.rootContext()->setContextProperty(QLatin1String("dp"), QVariant::fromValue(dp));

    engine.load(QUrl("main.qml"));
    QObject *topLevel = engine.rootObjects().value(0);
    QQuickWindow *window = qobject_cast<QQuickWindow *>(topLevel);
    if ( !window ) {
    qWarning("Error: Your root item has to be a Window.");
        return -1;
    }
    window->show();
    return app.exec();
}
```

`QQmlApplicationEngine`ではなく`QQuickView`を使っている場合は，`engine.rootContext()`を`view.rootContext()`としてください．
`QtQuick2ApplicationViewer`を使っている場合も，`viewer.rootContext()`としてください．

注意するポイントとしては，`engine.rootContext()->setContextProperty(QLatin1String("dp"), QVariant::fromValue(dp));`の後に`engine.load(QUrl("main.qml"));`または`view.setSource(QUrl("main.qml"));`，または`viewer.setMainQmlFile(main.qml");`としてQMLファイルをロードしてください．

こうすることで，dpという定数が認識され，QMLファイル内で`width: 5*dp`のように使用できます．

# 動作確認
Android(GN)と普通の解像度のディスプレイ(13inch，1366x768)でのみ確認しているため，高解像度ディスプレイとiOSでは確認しておりません．不具合があればコメントにてご連絡ください．
また、もっといい方法があるよ！という方はぜひ記事にして教えて下さい。

# 参考
[RFC] Using DP instead of PX for Android http://qt-project.org/forums/viewthread/35287
implement dp unit(筆者のアプリ) https://github.com/yuntan/Aztter/commit/382e9b6508e8885b5dc817b5dd4708cf85035acc#diff-118fcbaaba162ba17933c7893247df3aR19