Androidでボタンを横に並べて自動で折り返す

はい、タイトル分かりづらいですね。まぁ前回の続きなんですけど。

AndroidのLinearLayoutとかを使って横にViewを並べると、長すぎる奴はそのまま外に出しちゃったり変な風にクシャッとしてくれたりして自動で折り返してくれない。
でもボタンを横に並べて、画面からはみ出そうになったら折り返して表示したい!しろよ!って思いまはしたものの、よい案が浮かばなかったのでWebViewと連携してその描画はCSSでやればいいんじゃね?って思って作っていたんですが、どうもレスポンスがよろしくない。
気晴らしにGジェネやっていたら、相対レイアウト使えばまさか!?って思いついて早速やってみたら出来たのでメモ。

まずはlayout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">
  <ScrollView
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <RelativeLayout
      android:id="@+id/rl"
      android:layout_width="fill_parent"
      android:layout_height="wrap_content">

    </RelativeLayout>
  </ScrollView>
</LinearLayout>

ボタンは動的に出すので何も無いlayout。ScrollViewのなかにRelativeLayoutを入れているだけです。

Activityはこんな感じ。importとかは端折った。

public class TagsActivity extends Activity {
  private static String[] tags = { "hoge", "fuga", "bar", "foo", "もげ",
      "daadae", "aadfefaea", "12345678901234567890", "ちょっと長い", "1", "hoge",
      "fuga", "bar", "foo", "もげ", "daadae", "aadfefaea", "hoge", "fuga", "bar",
      "foo", "もげ", "daadae", "aadfefaea", "ちょっと長い", "1", "hoge", "fuga", "bar",
      "foo", "もげ", "daadae", "aadfefaea", "ちょっと長い", "1", "hoge", "fuga", "bar",
      "foo", "もげ", "daadae", "aadfefaea", "ちょっと長い", "1", "hoge", "fuga", "bar",
      "foo", "もげ", "daadae", "aadfefaea", "ちょっと長い", "1", "hoge", "fuga", "bar",
      "foo", "もげ", "daadae", "aadfefaea", "ちょっと長い", "1", "hoge", "fuga", "bar",
      "foo", "もげ", "daadae", "aadfefaea", "ちょっと長い", "1", "hoge", "fuga", "bar",
      "foo", "もげ", "daadae", "aadfefaea", "ちょっと長い", "1",

  };

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    // 1000個くらいいれると若干固まるから別スレッドで作るとかやったほうがいいかも?
    for (int i = 0; i < tags.length; i++) {
      String s = tags[i];
      Button btn = new Button(this);
      RelativeLayout.LayoutParams prm = new RelativeLayout.LayoutParams(
          LinearLayout.LayoutParams.WRAP_CONTENT,
          LinearLayout.LayoutParams.WRAP_CONTENT);
      // ボタン間のマージン 0にしても隙間が埋まらないんだけど!要調査
      prm.setMargins(0, 0, 0, 0);
      btn.setText(s);
      btn.setId(i + 1);
      RelativeLayout r = (RelativeLayout) findViewById(R.id.rl);
      r.addView(btn, prm);

    }
  }

  @Override
  public void onWindowFocusChanged(boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);
    RelativeLayout rl = (RelativeLayout) findViewById(R.id.rl);
    // 子供の数を取得
    int l = rl.getChildCount();
    // 無いなら何もしない
    if (l == 0) {
      return;
    }
    // ディスプレイの横幅を取得
    WindowManager wm = (WindowManager) this.getSystemService(Context.WINDOW_SERVICE);
    Display display = wm.getDefaultDisplay();
    int max = display.getWidth();

    int margin = 0;
    // 一番最初は基点となるので何もしない
    View pline = rl.getChildAt(0);
    // 一行全体の長さ
    int total = pline.getWidth() + margin;
    for (int i = 1; i < l; i++) {
      int w = rl.getChildAt(i).getWidth() + margin;
      RelativeLayout.LayoutParams prm = (RelativeLayout.LayoutParams) rl
          .getChildAt(i).getLayoutParams();
      // 横幅を超えないなら前のボタンの右に出す
      if (max > total + w) {
        total += w;
        prm.addRule(RelativeLayout.ALIGN_TOP, i);
        prm.addRule(RelativeLayout.RIGHT_OF, i);
      }
      // 超えたら下に出す
      else {
        prm.addRule(RelativeLayout.BELOW, pline.getId());
        // 基点を変更
        pline = rl.getChildAt(i);
        // 長さをリセット
        total = pline.getWidth() + margin;
      }
    }
  }
}

割と簡単。
onCreate時にButtonインスタンスを作って、RelativeLayoutにどんどん追加しています。
ボタンの数が多くなるようだったら別スレッドで処理したほうがいいのかな。ここも要検討。
Viewの長さはonCreateのときに取得しようとしてもまだ描画されてなく0になってしまうのでonWindowFocusChangedのタイミングで取得します。(タイミング的にここであってるよね?)
この辺のライフサイクルはほんとFlexと似てる。Flexの時もコンポーネントの長さを取得するのに頭を痛めた記憶があります。もう忘れたけど。

で、onWindowFocusChangedのところで、各ボタンの長さとディスプレイの長さを取得して、配置する場所を決めてるといった感じ。

今のところは当然ながらクリックしたときのレスポンスも良い感じ。これで先に進める…!?