メインコンテンツまでスキップ

チュートリアルAndroidの画面サイズを管理する

📚 Source Page

さまざまな画面サイズに対応するアプリケーションを作成しましょう。このチュートリアルでは、Androidで利用可能な画面サイズは数多くありますが、これを管理するためのいくつかの戦略を検討します。

レベル:中級

プラットフォームアンドロイド, macOS, Windows

クラス: Desktop,AffineTransform,TabbedComponent

スタート

このチュートリアルでは、JUCEを使用してAndroidプラットフォーム上で異なる画面サイズを管理するためのいくつかの戦略を説明します。このチュートリアルには、いくつかのデモプロジェクトがあります。これらのプロジェクトのダウンロード・リンクは、チュートリアルの関連するセクションにあります。

各セクションのこのステップでヘルプが必要な場合は、以下を参照してください。Tutorial: Projucer Part 1: Getting started with the Projucer.

デモ・プロジェクト

このチュートリアルで提供されるデモ・プロジェクトは、JUCEを使用してAndroidプラットフォーム上で異なる画面サイズを管理するためのいくつかの異なる方法を示しています。大まかに、これらの方法は以下の通りです:

  • メインコンポーネント内の子コンポーネントのサイズ変更
  • トランスフォームを使用したメインコンポーネントのサイズ変更
  • 方向によって異なるコンポーネントのレイアウトを設計する
  • サイズごとに異なるコンポーネントレイアウトを設計

アンドロイドの画面サイズ

特にフルスクリーン操作を期待するデバイス(モバイルデバイスなど)では、すべての画面サイズとさまざまなデバイスの向きに効果的なユーザーインターフェイスをデザインすることが課題となります。これは、多くの画面サイズと解像度が存在するアンドロイド・プラットフォームでは特に難しいことです。ここでは主に3つの問題がある:

  • 物理的サイズ:画面の物理的な大きさを標準単位で表したもの(一般的な単位は画面の対角線上の距離で、単位はインチ)
  • 決議:ピクセル単位の画面解像度
  • オリエンテーション:デバイスの向き(横向きか縦向きか)

物理的なサイズと解像度の関係は重要である。特に、物理的なピクセルが標準的な解像度のスクリーンよりも小さく、高密度に配置されている高解像度スクリーンを考える際には重要です。特定の物理的な画面サイズと解像度の組み合わせは、画面のドット・パー・インチ(DPI)。これはスクリーンピクセル密度.これは、標準的な密度の画面上のピクセルとして、各次元で、「ソフトウェア」ピクセルのスペースを占める物理的なピクセルの数である。

アプリケーションによっては、物理的な大きさが最も重要な場合もある。例えば、繊細な指の動きからなる複雑なインタラクションを使用するアプリケーションなどです。この場合、画面サイズと典型的なユーザーの手の大きさが重要です。他のアプリケーションでは、画面のDPIがより重要です。例えば、DPIが高くてもフォントサイズが小さければテキストは読みやすくなります。しかし、スクリーン上の物理的なサイズで測定した場合、テキストの可読性には限界があります。アプリケーションを設計する際には、物理的なサイズと解像度(したがってDPI)の両方を考慮に入れる必要がある場合があります。

デフォルトでは、JUCEはその座標系をスクリーンのピクセル密度に基づいてスケーリングします。これは、高密度のスクリーンで描かれた図形やテキストは、標準的な密度のスクリーンで描かれたものとほぼ同じ物理的サイズに見えるはずであることを意味します。JUCEでは、特定のディスプレイに関する情報にDesktopクラスがあります。ここでは、利用可能なディスプレイと、どのディスプレイが「メインディスプレイ」としてマークされているかを確認することができます(特に複数のディスプレイがある場合)。

残念ながら、JUCEがディスプレイのDPIを取得するためにアクセスできる値は近似値に過ぎません(すべてのスクリーンデバイスがこの情報を適切に報告するわけではないため)。つまり、ユーザーの画面の物理的なサイズを正確に測定することはできません。しかしDesktopクラスは、アプリケーションのニーズに応じてユーザー・インターフェースを拡張するためのガイドとして十分なはずだ。

この後の各例では、子コンポーネントとしてResizingCompこれは、親コンポーネント(MainContentComponent).

注記

これらのプロジェクトをmacOSまたはWindowsでテストすると、メイン・ウィンドウの幅と高さを動的に変更することができます。これはある程度機能しますが、テスト目的以外ではプロジェクトの機能として意図されていません。プロジェクトは、サイズの頻繁な変更を想定して設計されています。例えば、アプリケーションの起動時や、ユーザーがAndroidデバイスを回転させた時などに、1回だけ設定します。

子コンポーネントのサイズ変更(単純なサイズ変更)

このセクションのデモ・プロジェクトのダウンロードはこちらから:PIP|ZIP.プロジェクトを解凍し、最初のヘッダーファイルをProjucerで開く。

この例では、スライダーとボタンのコレクションを含む単純なインターフェイスを使用します。これらの子コンポーネントには、それぞれ画面の高さ(端の小さな枠を除く)の割合が与えられている。水平方向でも同様のアプローチが可能です。簡単にするために、スライダーとボタンは、画面の幅全体を占めるだけです(ここでも、小さなボーダーを除きます)。画面が縦向きで、サイズが数百ピクセルの場合、以下のスクリーンショットのようになります:

子コンポーネントの簡単なサイズ変更:ポートレート
子コンポーネントの簡単なサイズ変更:ポートレート

横向きの場合は、以下のスクリーンショットのようになります:

子コンポーネントの簡単なサイズ変更ランドスケープ
子コンポーネントの簡単なサイズ変更ランドスケープ

コンポーネントの配列

ボタンとスライダーをResizingCompクラスではOwnedArrayテンプレート・クラスでは、これらの子コンポーネントは自動的に削除されます。ResizingCompデストラクタ)。まずResizingCompコンストラクタでColourオブジェクトの色を設定します。これらは、ボタン、スライダーの親指、スライダートラックの色を設定するために使用されます:

        ResizingComp()
{
juce::Array colours { juce::Colour (0xffb3c3Da), juce::Colour (0xff5973b8), juce::Colour (0xffd65667), juce::Colour (0xffd99154),
juce::Colour (0xffe5ad6c), juce::Colour (0xffecc664), juce::Colour (0xffefe369), juce::Colour (0xffdddB74) };
注記

これらは偶然にもJUCEのロゴの色である!

そしてfor()ループで複数のボタンを割り当て、設定する:

            for (auto i = 0; i < 6; ++i)
{
auto* button = buttons.add (new juce::TextButton (juce::String ("Button ") + juce::String (i + 1)));
addAndMakeVisible (button);

button->setColour (juce::TextButton::buttonColourId,
colours.getUnchecked (i % colours.size()));
}

スライダーも同じように設定されている(ただし、面白さを保つために、色の配列を使って色の選択を混ぜている)。

            for (auto i = 0; i < 6; ++i)
{
auto* slider = sliders.add (new juce::Slider());
addAndMakeVisible (slider);

slider->setColour (juce::Slider::thumbColourId,
colours.getUnchecked ((buttons.size() + i) % colours.size()));
slider->setColour (juce::Slider::backgroundColourId,
colours.getUnchecked ((buttons.size() + i + 2) % colours.size()).withAlpha (0.4f));
slider->setColour (juce::Slider::trackColourId,
colours.getUnchecked ((buttons.size() + i + 2) % colours.size()));
slider->setColour (juce::Slider::textBoxTextColourId, juce::Colours::black);
}

カスタムスライダーのサムサイズを使用する

タッチスクリーン・インターフェイスでより使いやすくするため、スライダーの親指は通常標準サイズより大きくなるようにカスタマイズされています。そのためにLookAndFeel_V4をオーバーライドした。LookAndFeel::getSliderThumbRadius()関数である。

        class CustomLookAndFeel : public juce::LookAndFeel_V4
{
public:
int getSliderThumbRadius (juce::Slider& slider) override
{
return juce::jmin (slider.getWidth(), slider.getHeight()) / 2;
}
};

このクラスのインスタンスをResizingCompクラスである:

        juce::OwnedArray buttons;
juce::OwnedArray sliders;
CustomLookAndFeel lf;
};

そして、最後にResizingCompコンストラクタで、このコンポーネントとそのすべての子コンポーネントのルック&フィールを設定します。

            setLookAndFeel (&lf);

そして、我々のResizingCompデストラクタでは、これをnullptrに設定する。

        ~ResizingComp() override
{
setLookAndFeel (nullptr);
}

ボタンとスライダーのサイズ変更

の中でResizingComp::resized()関数では、ボタンとスライダーの配列を繰り返し処理し、その境界を設定する:

        void resized() override
{
auto space = 8;
auto widgetHeight = (getHeight() - space) / (buttons.size() + sliders.size()) - space;

for (auto* button : buttons)
button->setBounds (space, space + (widgetHeight + space) * buttons.indexOf (button),
getWidth() - space - space, widgetHeight);

for (auto* slider : sliders)
slider->setBounds (space, space + (widgetHeight + space) * (sliders.indexOf (slider) + buttons.size()),
getWidth() - space - space, widgetHeight);
}

ここでは、定数値(8)を使用してコンポーネントを分離します。そして、利用可能な高さと "ウィジェット"(ボタンとスライダー)の数に基づいて "ウィジェットの高さ "を計算します。

画面サイズが小さすぎると、以下のスクリーンショットのようにインターフェイスが使用できなくなり、読めなくなる:

画面が小さすぎる場合のシンプルなリサイズ
画面が小さすぎる場合のシンプルなリサイズ

とはいえ、ほとんどのアンドロイド端末ではそれなりに見えるはずだ。

エクササイズ

インターフェイスのスライダーやボタンの数を変える。

トランスフォームを使用したメインコンポーネントのサイズ変更

このセクションのデモ・プロジェクトのダウンロードはこちらから:PIP|ZIP.プロジェクトを解凍し、最初のヘッダーファイルをProjucerで開く。

この例では、子コンポーネントのサイズ変更に代わる方法を使用しています。代わりにResizingCompコンポーネントは公称サイズ(480×640ピクセル)に設定され、次にMainContentComponentオブジェクトはアフィン変換を適用し、これをスクリーンサイズに合わせて拡大縮小します。これは、同じアスペクト比を保ちながら行われます(スライダーやボタンの横や上下に空白を残します)。のコードはResizingCompクラスはsimple resizeの例だ。しかしMainContentComponent::resized()関数のサイズを設定する。ResizingCompコンポーネントが必要な変換を計算する:

    void resized() override
{
auto contentWidth = 480;
auto contentHeight = 640;

auto scaleX = (float) getWidth() / static_cast (contentWidth);
auto scaleY = (float) getHeight() / static_cast (contentHeight);
auto scale = juce::jmin (scaleX, scaleY);

resizingComp->setTransform (juce::AffineTransform::scale (scale, scale));
resizingComp->centreWithSize (contentWidth, contentHeight);
}

このコードは、公称サイズと実際の画面サイズの比率をソフトウェア・ピクセルで計算します。そして、アスペクト比を維持し、すべてのコンテンツを画面に表示するために、これらの比率のうち最小のものを選択します。次にAffineTransformクラスを使用しています。AffineTransform::scale()関数を使用して、スケール変換を画面の中央に配置します。このトランスフォームは、Component::setTransform()関数を使用してコンポーネントに適用されます。結果はsimple resizeメソッドを使用する。

縦向きと横向きの両方を表示するトランスフォームを使用して、UI全体を拡大縮小する。
縦向きと横向きの両方を表示するトランスフォームを使用して、UI全体を拡大縮小する。
注記

コンポーネントにトランスフォームを適用すると、ユーザーインターフェイスの描画が変換されるだけでなく、タッチ(およびマウス)アクティビティの位置も変換されます。

異なる方向性に対して異なるレイアウトをデザインする

このセクションのデモ・プロジェクトのダウンロードはこちらから:PIP|ZIP.プロジェクトを解凍し、最初のヘッダーファイルをProjucerで開く。

この例では、スクリーン(またはデバイス)の向きによって異なるレイアウトを表示する方法のひとつを紹介する。ここではDesktop::getCurrentOrientation()関数は、デバイスの向きにアクセスする手段を提供する。実際には、4つの可能な方向があります:

  • アップライト(ポートレート)
  • 逆さま(ポートレートを180度回転させたもの)
  • デバイスを時計回りに90度回転(横向きの1バージョン)
  • デバイスを反時計回りに90度回転(横向きの別バージョン)

簡単のため、このチュートリアルでは、縦長の画面を横長の画面として扱います(横長の画面を縦長の画面として扱います)。

この例では、ユーザー・インターフェースのスケーリングに同じテクニックを使っている。using a transform先ほど見たように。違いはResizingCompクラスは、向きによって異なるレイアウトを使用します。MainContentComponentクラスには2つの公称サイズ(横向き用と縦向き用)があります。向きはMainContentComponent::resized()関数である:

    void resized() override
{
auto isLandscape = getWidth() > getHeight();
auto contentWidth = isLandscape ? 640 : 480;
auto contentHeight = isLandscape ? 480 : 640;

そしてResizingComp::resized()関数では、向きに応じて2つのリサイズ関数から選択する:

        void resized() override
{
if (getHeight() > getWidth())
resizedPortrait();
else
resizedLandscape();
}

についてresizedPortrait()そしてresizedLandscape()関数は、ボタンとスライダーのレイアウトに異なる演算を使用する。

横向きの場合、ボタンとスライダーは1列ではなく2列で表示されます。これは以下のスクリーンショットに示されています:

横向きで別のレイアウトを使う
横向きで別のレイアウトを使う
エクササイズ

を使用するようにコードを変更する。Desktop::getCurrentOrientation()関数を使用して、画面の幅と高さを比較するのではなく、画面の向きを決定する。

画面サイズごとに異なるレイアウトをデザインする

このセクションのデモ・プロジェクトのダウンロードはこちらから:PIP|ZIP.プロジェクトを解凍し、最初のヘッダーファイルをProjucerで開く。

この最後の例では、画面の向きや画面サイズごとに異なるレイアウトを使用しています。このようなテクニックは、画面の向きによってまったく異なるレイアウトを使いたい場合や、Android携帯やタブレット用のユニバーサルアプリケーションを作りたい場合に特に有効です。ここで採用されている方法はTabbedComponentクラスを使用すると、コンポーネントのページが1ページに収まらない場合に、そのページを並べることができる。

の責任である。ResizingCompクラスは、先の3つのプロジェクトと比べて、このプロジェクトでは少し変更されています。特に、ボタンとスライダーを直接の子コンポーネントとして追加していません。次のコンストラクタのコードで、Component::addAndMakeVisible()関数を呼び出していないことに注目してください:

        ResizingComp()
{
juce::Array colours { juce::Colour (0xffb3c3Da), juce::Colour (0xff5973b8), juce::Colour (0xffd65667), juce::Colour (0xffd99154),
juce::Colour (0xffe5ad6c), juce::Colour (0xffecc664), juce::Colour (0xffefe369), juce::Colour (0xffdddB74) };

for (auto i = 0; i < 6; ++i)
{
auto* button = buttons.add (new juce::TextButton (juce::String ("Button ") + juce::String (i + 1)));

button->setColour (juce::TextButton::buttonColourId,
colours.getUnchecked (i % colours.size()));
}

for (auto i = 0; i < 6; ++i)
{
auto* slider = sliders.add (new juce::Slider());

slider->setColour (juce::Slider::thumbColourId,
colours.getUnchecked ((buttons.size() + i) % colours.size()));
slider->setColour (juce::Slider::backgroundColourId,
colours.getUnchecked ((buttons.size() + i + 2) % colours.size()).withAlpha (0.4f));
slider->setColour (juce::Slider::trackColourId,
colours.getUnchecked ((buttons.size() + i + 2) % colours.size()));
slider->setColour (juce::Slider::textBoxTextColourId, juce::Colours::black);
}

setLookAndFeel (&lf);
}

についてResizingCompクラスはボタンとスライダーのライフタイムを管理するが、コンポーネントの階層構造から見ると、それらはComponentHolder.

コンポーネントのレイアウト

についてComponentHolderクラスはComponentポインターを使い、向きによってコンポーネントを1列または2列に並べる。(このレイアウトを実現するテクニックは、以前、さまざまなscreen orientations.)一つの関数がある。ComponentHolder::addComp()-内部配列に追加し、Component::addAndMakeVisible()を呼び出します:

            void addComp (Component* comp)
{
comps.add (comp);
addAndMakeVisible (comp);
}

レイアウト機能ResizingComp::resizedPortrait()そしてResizingComp::resizedLandscape()-見覚えがあるはずだ。しかし、スライダーとボタンを別々に配列する必要がなくなったので、これらは少し異なるものにする必要がある:

            void resizedPortrait()
{
auto space = 8;
auto widgetHeight = (getHeight() - space) / comps.size() - space;

for (auto* comp : comps)
comp->setBounds (space, space + (widgetHeight + space) * comps.indexOf (comp),
getWidth() - space - space, widgetHeight);
}

void resizedLandscape()
{
auto space = 8;
auto halfComps = comps.size() / 2;
auto widgetHeight = (getHeight() - space) / halfComps - space;

for (auto* comp : comps)
{
auto index = comps.indexOf (comp);

if (index < halfComps)
{
comp->setBounds (space, space + (widgetHeight + space) * index,
getWidth() / 2 - space - space, widgetHeight);
}
else
{
comp->setBounds (getWidth() / 2 + space, space + (widgetHeight + space) * (index - halfComps),
getWidth() / 2 - space - space, widgetHeight);
}
}
}

単一ページまたは複数ページの選択

画面サイズが大きい場合は、以下のいずれか1つのみとなる。ComponentHolderコンポーネントが作成され、すべてのボタンとスライダーがそこに追加される。画面サイズが小さい場合はResizingCompクラスはTabbedComponentオブジェクトのインスタンスを2つ追加する。ComponentHolderクラスのタブを形成する。TabbedComponent.この単一ページか複数ページかの判断は、次のように管理される。ResizingCompクラスのResizingComp::resized()関数である。

        void resized() override
{
if (holder.get() != nullptr)
{
removeChildComponent (holder.get());
holder.reset();
}

auto minimumDimension = juce::jmin (getWidth(), getHeight());

if (minimumDimension >= 480)
layoutSinglePage();
else
layoutTabs();
}

ここでは、"大型 "スクリーンとは、その寸法の1つが480ソフトウエア・ピクセル以上であるものを指していることがわかります。もちろん、用途に応じて別の値を選択することもできます。このResizingComp::layoutSinglePage()関数は簡単だ:

        void layoutSinglePage()
{
holder.reset (new ComponentHolder());

for (auto* button : buttons)
dynamic_cast (holder.get())->addComp (button);

for (auto* slider : sliders)
dynamic_cast (holder.get())->addComp (slider);

addAndMakeVisible (holder.get());
holder->setBounds (getLocalBounds());
}

ここでは、すべてのボタンとスライダーをComponentHolderインスタンスの子コンポーネントとして追加します。ResizingCompオブジェクトになる。そのResizingComp::layoutTabs()関数はもう少し複雑だ:

        void layoutTabs()
{
auto orientation = getWidth() < getHeight() ? juce::TabbedButtonBar::TabsAtBottom
: juce::TabbedButtonBar::TabsAtLeft;

holder.reset (new juce::TabbedComponent (orientation)); // [1]
addAndMakeVisible (holder.get()); // [2]

auto* buttonTab = new ComponentHolder(); // [3]
auto* sliderTab = new ComponentHolder();

dynamic_cast (holder.get())->addTab ("Buttons", juce::Colours::white, buttonTab, true); // [4]
dynamic_cast (holder.get())->addTab ("Sliders", juce::Colours::white, sliderTab, true);

for (auto* button : buttons) // [5]
buttonTab->addComp (button);

for (auto* slider : sliders) // [6]
sliderTab->addComp (slider);

holder->setBounds (getLocalBounds()); // [7]
}
  • [1]を作成する。TabbedComponentオブジェクトを作成しholder画面の向きを利用してタブボタンを配置するメンバー。
  • [2]を追加する。TabbedComponentオブジェクトを子コンポーネントとする。
  • [3]を作成する。ComponentHolderオブジェクトがある。
  • [4]にタブとして追加する。TabbedComponentオブジェクト(最後のtrue引数はTabbedComponentオブジェクトを削除することができる。ComponentHolderオブジェクトが不要になったとき)。
  • [5]ボタンを "Buttons "タブに追加します。
  • [6]スライダーは "Sliders "タブへ。
  • [7]のサイズを設定する。TabbedComponentオブジェクトの境界を埋める。ResizingCompオブジェクトがある。

スクリーンのサイズが "small "と判断された場合TabbedComponentは以下のスクリーンショットのように使用される。

タブ付きインターフェイス
タブ付きインターフェイス
エクササイズ

インターフェイスのスライダーやボタンの数を増やし、必要であればこれらのコントロールを2つ以上のタブに分散させる工夫をする。

概要

このチュートリアルでは、Androidデバイスの画面サイズや向きに関する様々な問題を検証してきました。特に

  • 親コンポーネントの寸法に基づいてコンポーネントを拡大縮小する基本的な方法を示す。
  • の使い方を紹介する。AffineTransformクラスを使用してコンポーネントを拡大縮小します。
  • デバイスの向きによって異なるコンテンツを表示する方法を紹介。
  • の使い方を紹介する。TabbedComponentクラスを使用して、ユーザー・インターフェースを複数のページに分散させることができます。

こちらも参照