X

画面サイズを攻略して機種依存を吸収する

Android端末では一口に画面サイズ、といってもハードウェアの液晶パネルのサイズなのか、アプリケーションが利用できる範囲のみを指すのか不明確な場合が多く、気を付けなければいけません。今回は画面サイズを取得する方法をまとめると共に機種に依存することなくナビゲーションバーのサイズやステータスバーのサイズを取得する方法を紹介します。

Androidの画面を構成するパーツの名前

本題に入る前に用語を統一しておきます。さまざまな呼び方がありますが、本投稿のなかではAndroidの画面を構成する要素を以下のように呼ぶことにします。

コンポーネント名 説明
ステータスバー 通知バーとも呼ばれます。端末によってサイズが変わります。アプリケーションから表示/非表示を指定できます
タイトルバー アプリケーション名を表示します。横幅や高さは適用するStyleファイル(アプリのデザイン)に依存します(標準であれば48dp 最近のxhdpi端末では96px)
コンテンツ領域 contentRootとも。普段アプリを作る際にレイアウトファイルを(fill_parentなどを指定して)配置する領域です
ナビゲーションバー ソフトウェアキーの領域です。端末によって(または縦/横表示によって)サイズが変わります

取得できる画面サイズ

今回まとめた画面サイズは以下の3種類です。

取得できる画面サイズ

種類 説明
HardwareSize 液晶パネルそのもののサイズを取得できます。ただし、SDKが対応したのはAndroid 4.2以降からです。 今回、hideメソッドを利用して取得する方法も紹介しますが、利用可能範囲はAndroid 3.2以上です
DisplaySize アプリケーションが利用できる表示領域です
ViewSize コンテンツを表示できるサイズです


※上記の名前付けは一般的なものではなく、今回の解説用に定義した名前です。(Displayクラスのサイズといえばどれもディスプレイサイズということになるため、わかりやすさを優先して図示しました)

特にステータスバーとナビゲーションバーは端末ごとカスタマイズされている可能性もあるため、動的に取得するのが望ましい項目と言えるでしょう。しかしながら、直接この2つの高さ情報を取得するAPIは存在していません。踏み込んで解説するならば、これらはアプリケーションの領域外でありアプリが気にする必要はなく、気にしないでいられるデザインやレイアウトを検討すべきである、という設計思想がうかがえます。設計思想を尊重するならば、このあと解説するAPIをなるべく使わないでいいように工夫できると機種依存の苦悩から解放されるでしょう。

取得する方法は続きから

ナビゲーションバーを除いたディスプレイサイズを取得する

ディスプレイサイズを知るにはWindowManagerからDefaultDisplayを取得するのが最も手っ取り早いでしょう。
ここでは簡単化のため、タイトルバーを隠して、ディスプレイサイズ=タイトルバー+コンテンツ領域、という状態にして解説します。
■src/MainActivity.java

public class MainActivity extends Activity {

    @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // タイトルバーを隠す場合
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        // ステータスバーを隠す場合
        // getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);

        setContentView(R.layout.activity_main);

        TextView w = (TextView) findViewById(R.id.tv_displayWidth);
        TextView h = (TextView) findViewById(R.id.tv_displayHeight);

        // 画面サイズを取得する
        Display display = getWindowManager().getDefaultDisplay();
        Point p = new Point();
        display.getSize(p);
        w.setText("width: " + p.x);
        h.setText("height: " + p.y);
    }
}

9行目でタイトルバーを非表示に変更します。これはsetContentViewよりも前に行う必要があります。今回は非表示としていますが、表示していても取得できるディスプレイサイズに変化はありません(このあとステータスバーのサイズを計算するのにタイトルバーを非表示にしておくほうがわかりやすいので。)
19,21行目、Displayを取得したあと、getSizeメソッドをつかって画面サイズを取得できます。

ステータスバーもナビゲーションバーも両方含んだハードウェアサイズを取得する

ハードウェアサイズを取得する方法は公式にはAndroid 4.2のみサポートされています。非公式には(hideメソッドを使って)Android 3.2以降でも取得可能です。以下ではその両方をサポートするための方法を紹介します(Android 2.xも追記したいところですが気が向けば。)
少し長くなってしまいましたが、重要な部分はごく少数です。以下のサンプルではハードウェアサイズを取得するgetRealSizeメソッドを自作しています。
■src/MainActivity.java

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        …省略…
        // ステータスバー、ナビゲーションバーを含めた実画素数を取得する
        w = (TextView) findViewById(R.id.tv_hardwareWidth);
        h = (TextView) findViewById(R.id.tv_hardwareHeight);
        Point real = getRealSize();
        w.setText("width: " + real.x);
        h.setText("height: " + real.y);
    }

    @SuppressLint("NewApi")
    private Point getRealSize() {

        Display display = getWindowManager().getDefaultDisplay();
        Point real = new Point(0, 0);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            // Android 4.2以上
            display.getRealSize(real);
            return real;

        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
            // Android 3.2以上
            try {
                Method getRawWidth = Display.class.getMethod("getRawWidth");
                Method getRawHeight = Display.class.getMethod("getRawHeight");
                int width = (Integer) getRawWidth.invoke(display);
                int height = (Integer) getRawHeight.invoke(display);
                real.set(width, height);
                return real;

            } catch (Exception e) {
                // TODO 自動生成された catch ブロック
                e.printStackTrace();
            }
        }

        return real;
    }

13行目以降getRealSizeメソッド内で実際の画面サイズを取得しています。
Android 4.2の場合はより簡単です。15行目でWindowManagerからDisplayを取得したあと、20行目でgetRealSizeメソッドを呼び出すだけで実画面サイズが得られます(Display#getRealSize(Point)はSDK Level.17以降に新規メソッドとして追加されました)
やや複雑なのがAndroid 3.2(ハニカム)以降での実画面サイズの取得です。
26行目から29行目にかけて@hideメソッドを使って取得しています。@hide属性のメソッドはAndroid SDKのサポート対象外のため、うまく取得できる保証はありません(端末に依存してしまいますが、方法の一つとしてご紹介します)。

コンテンツ領域のサイズを取得する

コンテンツ領域のサイズはViewをinflateした段階で即座に決まるとは限りません。fill_parentやmatch_contentなど指定方法によってはほかの要素の影響を受けるためです。今回はActivity#onWindowFocusChangedメソッドをつかってWindowにアタッチ/フォーカスが切り替わったタイミングで取得してみましょう。
■src/MainActivity.java

    …省略…
    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        TextView w = (TextView) findViewById(R.id.tv_viewWidth);
        TextView h = (TextView) findViewById(R.id.tv_viewHeight);

        // Viewサイズを取得する
        RelativeLayout rl = (RelativeLayout) findViewById(R.id.relativeLayout1);
        w.setText("width: " + rl.getWidth());
        h.setText("height: " + rl.getHeight());
    }
    …省略…

9行目、findViewById(R.id.relativeLayout1)として取得しているレイアウトはxmlファイルで定義している最も上位のレイアウトです。

■res/layout/activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/relativeLayout1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >
    …省略…

実際には上記のようになっていると思います(Android SDKのWizardに則した作りにしていますので大体一緒になるはず)。
今回、コンテンツ領域の上位レイアウトであるRelativeLayoutのサイズを調べることでサイズを取得しています

ナビゲーションバーのサイズを端末ごと計算する

ここまででディスプレイサイズ、実画面サイズであるハードウェアサイズ、コンテンツ領域であるビューサイズを求めました。
結果は実行したAndroid端末ごと異なりますが、おおむね以下のように表示されていると思います

ここからは非常に簡単です。動的にナビゲーションバーのサイズを知るにはハードウェアサイズ(HardwareSize)からディスプレイサイズ(DisplaySize)を引けば良いわけです。

1280px – 1184px = 96px

ステータスバーのサイズを端末ごと計算する

ステータスバーもナビゲーションバー同様です。
ディスプレイサイズ(DisplaySize)とコンテンツ領域(ViewSize)の引き算です。

1184px – 1134px = 50px

タイトルバーを表示している場合でも同様の手順で求めることができます。(タイトルバーを表示しているのはアプリなので)高さを取得してディスプレイサイズより引いてください。

ナビゲーションバーとステータスバーのサイズ(Androidの標準実装)

最後にAndroidの標準実装の紹介です。
標準的にはナビゲーションバーとステータスバーは以下の値で実装されています
■frameworks/base/core/res/res/values/dimens.xml

     <!-- Height of the status bar -->
     <dimen name="status_bar_height">25dip</dimen>
     <!-- Height of the bottom navigation / system bar. -->
     <dimen name="navigation_bar_height">48dp</dimen>
     <!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height -->
     <dimen name="navigation_bar_height_landscape">48dp</dimen>
     <!-- Width of the navigation bar when it is placed vertically on the screen -->
     <dimen name="navigation_bar_width">42dp</dimen>

http://tools.oesf.biz/android-4.2.0_r1.0/xref/frameworks/base/core/res/res/values/dimens.xml

以上、おつかれさまでした。

mhidaka: Software Engineerだよ。DroidKaigi Organizer / Androidと組込とRe:VIEW。techbooster主宰。mhidaka's writings http://booklog.jp/users/mhidaka 技術書典! http://techbookfest.org