android.widget.Button の setMinWidth() と setMinimumWidth()

ちょっと前に android.widget.Button(以下、Button クラス)に最小幅を設定しようとして、ちょっとつまづいた。

問題

Button クラスに対して xml 側で最小幅を設定しているところがあった。 (指定が px 単位なのは分かりやすくするため)

<Button android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        android:minWidth="30px" />

これを、ちょっとした事情で .java 側にうつした。

Button button = (Button) view.findViewById(R.id.button);
button.setMinWidth(30);

.java 側にうつしたら、最小幅の指定が効かなくなった。

解決方法

Button クラスの setMinWidth() と setMinimumWidth() の両方で値を設定した。

Button button = (Button) view.findViewById(R.id.button);
button.setMinimumWidth(30);
button.setMinWidth(30);
return view;

これでめでたく最小幅指定が効くようになった。

詳細

TextView.setMinWidth() と View.setMinimumWidth()

Button クラスは android.widget.TextView(以下、TextView クラス) と android.widget.View(以下、View クラス) を継承している。

この二つのクラスは、最小幅レイアウトに関して以下のような特徴を持っている。

このように、実は Button クラスは内部的に最小幅に関するプロパティを二つ(TextView.mMinWidth、View.mMinWidth)持っている。

既存のコードに、この二つの値が正しく設定されていることを前提としたレイアウトコードがあった。 それが問題の原因で、ただしくこの二つの値を設定することで問題が解決した。 (そもそもその前提がいいのかは別の問題になるが)

同じ名前のプロパティがある

問題を特定しようとデバッグしているとき、デバッガで Button クラスに mMinWidth プロパティが二つ見えて、最初はデバッガのバグかと思った。

よくよく考えれば先祖クラスの private プロパティに同じ名前があるだけなので、特におかしいことではなかった。

xml で動いていたのはなぜ

xml で設定した時に動作していた理由が気になったので、LayoutInflater まわりのコードを追ってみた。

まず、Button クラスは継承関係で見ると、Button -> TextView -> View という関係になっている。

そのうえで、LayoutInflater は以下のようなことをしていた。

  1. xml をパースして、要素とその属性セットを生成
  2. 要素に基づいて各種 View を生成(今回は Button クラスの生成)
  3. 属性セットを引数として Button コンストラクタを呼び出す
  4. super 呼び出しで TextView のコンストラクタが呼ばれる
  5. 同じく super 呼び出しで View のコンストラクタが呼ばれる
  6. View コンストラクタで 属性セットに android:minWidth があれば setMinimumWidth() を介して値設定
  7. View コンストラクタが終了し、TextView コンストラクタに戻る
  8. TextView コンストラクタで、属性セットに android:minWidth があれば setMinWidth() を介して値設定
  9. TextView コンストラクタが終了し、Button コンストラクタに戻る
  10. Button コンストラクタ処理
  11. Button インスタンス生成完了

というわけで xml 側で android:minWidth を設定しておけば View と TextView クラスの両方の mMinWidth プロパティに値が設定されるという理屈だ。

実際、リファレンスでも setMinWidth() と setMinimumWidth() に対応する xml 属性は共に android:minWidth である。

なぜメソッド名が別なのか

単に次のようにすればいいんじゃね?と思ったけど、わざわざ別のメソッド名にしているのは何か問題があるからなのだろう。

// TextView クラスの中
public void setMinWidth(int minpixels) {
    super.setMinWidth(minpixels);
    mMinWidth = minpixels;

    // 実際には他にもいろいろやっているが省略
}

これ以上は調べる気力がないのでここまで。。。