動的に生成されるViewの大きさを取得する

2014年9月12日金曜日 yukari

みなさまはじめまして、Androidアプリ開発担当をしていますyukariです。
アプリ開発に携わり始めて1年と少しになります。

今日はわたしがしばしば苦戦させられたActivityのライフサイクルメソッドについてかるく触れたいと思います。
特定のアイテムを表示領域の真ん中に表示するListViewを例にざっくり書いていきます。



↑基本的なActivityのライフサイクルメソッドの図(Android Developerより)

完成イメージはこんな感じ↓

とりあえず、画面いっぱいにListViewを表示します。
シンプルなViewですし、onCreate()でViewを生成します。
リストの真ん中に表示したいアイテムは分かりやすいようにドロイド君カラーにしました。
現状だとListViewはこうなっています。
ドロイド君カラーのアイテムが下の方にありますね。これをぐいっとスクロールしてListViewの真ん中に持ってきます。

指定のアイテムの表示位置を操作するにはListview#setSelectionFromTop()メソッドを使います。
/** リストの真ん中に持ってくるアイテムのindex */
final int SELECT_LIST_INDEX = 9;
ListView list = (ListView) findViewById(R.id.custom_listview);

LinearLayout child = (LinearLayout) list.getChildAt(0);
// 中心にするアイテムの縦方向の開始位置 = リスト全体の高さ / 2 - 各アイテムの高さ / 2
int y = list.getHeight()/2 - child.getHeight() / 2;
list.setSelectionFromTop(SELECT_LIST_INDEX, y);
これでスクロールするはず!……あれっ

スクロールされない……
ログを見るとListViewの高さとListView内のアイテムのViewがとれてきていないことが分かります。
というのも、ListViewは後から中身(アイテム)を入れるので、画面生成時(onCreate()の時点)はまだ領域を持っていません。
onStart()でもonResume()も同じくです。

画面が生成されて中身の入ったListViewの高さが取れるようになるライフサイクルメソッドは
onWindowFocusChanged() ←ココ
onWindowFocusChanged()は端末の向き(縦横とか上下反転とか)が変わった時に呼ばれるメソッドで、onResume()の後に走ります。
なのでスクロールの処理だけこのメソッドに引っ越します。
@Override
public void onWindowFocusChanged(final boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);
    ListView list = (ListView) findViewById(R.id.custom_listview);
    LinearLayout listItem = (LinearLayout) list.getChildAt(0);
    if (listItem != null) {
        int y = list.getHeight()/2 - listItem.getHeight() / 2;
        list.setSelectionFromTop(SELECT_LIST_INDEX, y);
    }
}
ListViewのView生成の処理はそのままonCreate()においておきます。
onWindowFocusChanged()内に置くと、端末の向きが変わった時に毎回Viewを作りなおすことになってしまいます。
そして実行。
無事ドロイド君カラーのアイテムをリストの真ん中に表示できました!

横向きにしてもちゃんと真ん中にいますね。これにて完成です!