Posted at

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

More than 5 years have passed since last update.

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


QMLの不便だなと感じるところ

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


解決策


1 画面サイズに対する比でUIのサイズを決める

ヘッダーバーの高さは画面の高さの1/10とか,ボタンの幅は画面の幅の2/3とか.しかし,これだと画面を回転させるとUIのサイズが変わることになってしまいます.


2 物理的な単位を用いる

main.qmlでimport QtQuick.Window 2.1とし,


main.qml

property real mm: Screen.pixelDensity


と書いておくと,main.qmlから利用される全てのQMLファイル内でmm単位を利用できるので,width: 5*mmのようにして長さを指定できます.

しかし全てのデバイスでUIの物理的な大きさが同じというのはやはり違います.物理サイズの大きな画面ではUI部品を大きく,小さな画面では小さく表示したいものです


3 Androidのdpのような単位を作る

この方法は.qmlprojectを利用したアプリには利用できません.

先ず,プロジェクトファイル(.pro)に次の一文を追加します(Android対応しない場合は不要です).


myApp.pro

android : QT += androidextras


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


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