AndroidでViewの幅と高さを取得するのには一工夫が必要です。

例えばレイアウトファイルで

1
2
3
4
<View
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>

のように相対的に決めるパラメータを組まれるとonCreate時にview.getWidth()などではすぐに取得できません。

なので、Viewのレンダリング(描画)が終了した時点で正確な値を取得したいと考えるかと思います。

そんな時の手段を紹介します。

使うクラスはViewTreeObserverViewTreeObserver.OnBlogalLayoutListenerです。

ViewTreeObserver

A view tree observer is used to register listeners that can be notified of global changes in the view tree. A ViewTreeObserver should never be instantiated by applications as it is provided by the views hierarchy. Refer to getViewTreeObserver() for more information.

意訳すると

「ViewTreeObserverはViewTreeの全体的な変化を知る事が出来るリスナーを登録するために使われます。ViewTreeObserverはアプリケーションによってインスタンス化されるべきではありません。詳細についてはgetViewTreeObserver()を参照してください。」

と書かれています。

このクラスにはネストされたインターフェースがいくつかあります。

まずはそのインターフェースを紹介(簡易的に)してから、その後参照方法を紹介したいと思います。

Nested Class

ViewTreeObserverには以下のインターフェースがネストされています。

1
2
3
4
5
6
7
8
interface OnDrawListener;
interface OnGloblFocusChangeListener;
interface OnGlobalLayoutListener;
interface OnPreDrawListener;
interface OnScrollChangedListener;
interface OnTouchModeChangeListener;
interface OnWindowAttachListener;
interface OnWindowFoucusChangeListener;

一つ一つ紹介はしませんが、今回使うクラスはこのネストされたインターフェースの中のOnGlobalLayoutListenerになります。

ViewTreeObserverには各Listeneradd~remove~するメソッドが実装されています。

正確な情報が知りたい場合はViewTreeObserver | Android Developersを参照ください。

ViewTreeObserverの参照方法

WidgetなりLayoutクラスが継承しているandroid.view.ViewクラスにはgetViewTreeObserver()というメソッドがあります。

このメソッドを使って、ViewTreeObserverクラスを参照する必要があります。

引用にあげたようにアプリケーションによって生成されるのではなく、各イベントを知りたいandroid.view.Viewクラスを継承したクラスのgetViewTreeObserver()を使って取得しなければなりません。

ViewTreeObserver.OnGlobalLayoutListener

Interface definition for a callback to be invoked when the global layout state or the visibility of views within the view tree changes.

「グローバルレイアウトの状態や、ViewTreeの変更内のViewの可視性が変化した時に呼び出されるコールバックインターフェースの定義」

とでも意訳できますかね。

つまり状態変化した時に呼び出されるインターフェースということだと思います…。

このインターフェースを実装して、Viewが見えるようになった時(つまり描画が終了した時)に処理を追加して、幅や高さを取得します。

処理の紹介

おおざっぱな使い方(処理の順番)で言うと、

  1. 描画された後の幅と高さが知りたいandroid.view.Viewを継承しているクラス(ここではRelativeLayoutとします)のgetViewTreeObserver()を呼ぶ
  2. 取得したViewTreeObserverクラスのaddOnGlobalLayoutListenerを使ってViewTreeObserver.OnGlobalLayoutListenerを実装したクラスを追加する
  3. 描画が終了したらViewTreeObserver.OnGlobalLayoutListenerを実装したクラスのonGlobalLayout()が呼び出される
  4. onGlobalLayout()の中で、view.getWidth()view.getHeight()を使って値を取得する

という流れになります。

ソースコードを紹介します。

注意点

もし幅や高さの取得のためだけに使うのであれば、一度呼び出された時点でremoveOnGlobalLayoutListenerをすることをおすすめします。

引用でも挙げましたが、こちらはViewTreeにある変化のすべてに反応するため、もしかしたら望んでいないところでも呼び出される可能性があります。

そしてremoveOnGlobalLayoutListenerなのですが、API Levelが16以上となっています。

バージョンコードで言うと4.0以上ですね。

アプリの対応バージョンが4.0以上であるならばremoveOnGlobalLayoutListenerを使ってもなにも問題ありませんが、2.3以上とかに対応されている場合は気をつけてください。

上記のソースコードですと、以下の部分のことを指しています。

1
2
3
4
5
6
7
8
9
10
private static void removeOnGlobalLayoutListener(ViewTreeObserver observer, ViewTreeObserver.OnGlobalLayoutListener listener) {
if (observer == null) {
return ;
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
observer.removeGlobalOnLayoutListener(listener);
} else {
observer.removeOnGlobalLayoutListener(listener);
}
}

非推奨ってひどいですよね…。

終わりに

AdMaxSDKのAndroid版ではこれで画面幅を取っています。

ここにたどり着くのに結構時間がかかりました。

以上になります。

参考

ViewTreeObserver | Android Developers

ViewTreeObserver.OnGlobalLayoutListener | Android Developers