fresh digitable

めんどくさかったなってことを振り返ったり振り返らなかったりするための記録

カスタムビューに独自のスタイル属性を定義する

メディアのサムネイルを同じ大きさで横一列に並べるコンテナクラスを作ったので、中にいれるサムネイルの数をレイアウトリソースで定義できるようにした。

res/values/attr.xmldeclare-styleableを追加する

<resources>
    <declare-styleable name="MyCustomView">
        <attr name="thumbCount" format="integer" />
    </declare-styleable>
</resources>

layoutリソースファイルで次のように使う

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    >
    <com.freshdigitable.MyCustomView
        android:id="@+id/custom_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:thumbCount="4"
        />
</LinearLayout>

独自のネームスペースを定義しようとしたら、「gradleプロジェクトではapp:.../res-autoを使え」的なことを言われた気がする。

カスタムビューのコンストラクタで設定した値を受け取る

(色々と省略している)

public class MyCustomView extends View {
  private final int thumbCount;

  public MyCustomView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);

    final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyCustomView, defStyle, 0);
    try {
      this.thumbCount = a.getInt(R.styleable.MyCustomView_thumbCount, 0);
    } finally {
      a.recycle();
    }
  }
}

TextViewをextendsする

Viewを継承して自分のカスタムビューを作ろうとする時、3種類のコンストラクタをオーバーライドしなければならない。今までは何も考えずに

public HogeView(Context context) {
  this(context, null);
}

public HogeView(Context context, AttributeSet attrs) {
  this(context, attrs, 0);
}

public HogeView(Context context, AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);
}

などとしていたが、TextViewなどのandroid.R.attr.xxxStyleがあるものについては、引数が2このコンストラクタの中を

public MyCustomTextView(Context context, AttributeSet attrs) {
  this(context, attrs, android.R.attr.textViewStyle);
}

のようにしなければならない。

TextViewはsupport-libの中にAppCompatTextViewがあるのでこれを継承する。というか、他のTextViewと同じ色にならなくてなんでだろうな〜スタイルとかいじってないつもりなのにな〜とAppCompatTextViewのソースを読んでいたところこの事実に気づいた。引数3つのやつでsuperを呼ぶより、それぞれのコンストラクタの中でsuperを呼んで、さらにinit(Context, AttributeSet, int)みたいな初期化メソッドを用意して呼んでやるのがこれからは良いのだろうか。

FloatingActionButtonのBackgroundTintにselectorを使うには

FloatingActionButtonsetEnabled(false)とかいう邪道なことをしようとしていて、状態に合わせて色を変えたかったのでcolorリソースのselectorを次のような感じで指定しようとした。

ColorStateList colorList = ContextCompat.getColorStateList(getContext(), R.color.selector);
fab.setBackgroundTintList(colorList);

ところが、これでいざenabledをtrueとかfalseに変えてもずっとenabled=falseの色のまま変わらなかった。ボタン自体の状態は変わっているようで、enabled=falseの時はリスナに登録したアクションは起きないし、trueの時はちゃんと起きる。

先にsetBackgroundTintMode(Mode)を呼ぶといいという情報を見て試したり、ViewCompat.setBackgroundTintList()を試してみたが効果はなかった。

調べているといろんなバグレポートが見つかるので、いまちゃんと動かないのかなと半ば諦めていたが、layoutリソースにapp:backgroundTint=@color/selectorで指定することで思った通りの動きになった。最初にandroid:backgroundTintを使おうとしてAPI Level 21以上でしか有効でないという警告が出たのでjavaソースのほうで解決しようとしたのが敗因だった。

appパッケージの方もサジェストされてほしいものだ。

(support-lib: 24.0.0で確認)