スポンサーリンク

2016年9月5日月曜日

Android AppCompat系テーマでのカスタム View の落とし穴

現在の Android Studio で普通にプロジェクトを作成すると、テーマとして Theme.AppCompat.Light.DarkActionBar 等が使われています。いわゆる AppCompat 系のテーマです。

Android で普通にマテリアルデザインを使おうとすると Android5.0 以上が必要ですが、さすがに Android4 以下を切り捨ててしまうのは現時点ではあまり現実的ではありません。そこで AppCompat 系のテーマが使われる訳です。AppCompat 系テーマは appcompat-v7 ライブラリに含まれていることからも分かるように、APIレベル7(Android2.1) まで遡ってマテリアルデザインを使用することができます。

しかしこの AppCompat テーマには意外な落とし穴があることを発見しました。というか自分が落とし穴に嵌まっただけですが。
Android Studio でプロジェクトを作成して、以下の様な Button のサブクラスを作ります。
public class MyButton extends Button {
    public MyButton(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
}

単純に継承しただけで、何も動作は何も変えていません。これを標準の Button と並べて配置してみます。レイアウト XML はこんな感じです。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    android:id="@+id/main_layout"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    tools:context="com.mytest.android.myapplication.MainActivity">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"/>

    <com.mytest.android.myapplication.MyButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"/>

</LinearLayout>

Android 4.x と Android 5.x のデバイスでこれを実行すると以下の様になります。

Android 4.3
Android 5.1


Android 4.x ではボタンのデザインが異なっていて、サイズも微妙に違っています。オリジナルボタンの方はマテリアルデザイン特徴であるボタン文字が大文字になっていますが、カスタムボタンにはそれが適用されていません。更にカスタムボタンの方は押してみると青く反転するので、Holo 的なデザインになっているのが分かります(デバイスによって見え方は変わると思いますが)。
Android 5.x では両ボタンともまったく同じ、マテリアル的なデザインになっています。

この違いは何なのでしょう?

ここでもう一つテストしてみます。ここまでは Application に適用されているテーマ(res/values/styles.xml)はウィザードが生成したままですが、これに一行追加して以下のようにしてみます。テキストカラーを赤くしてみます。
<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>

        <item name="android:textColor">#ff0000</item>
    </style>

</resources>

これを Android4.x で実行すると以下のようになりました。


赤くなったのはオリジナルボタンの方だけです。つまりカスタムボタンの方にはテーマが適用されていないのです。

何が起こっているのかさっぱり分かりません。ということで Google グループの日本Androidの会で聞いてみたところ、速攻で返事が返ってきました。

結論から言うと、AppCompat 系のテーマを使う場合は Button クラスではなくて、AppCompat 用の専用クラス AppCompatButton クラスを使わなくてはならないのです。確かに以下の様に、MyButton の親クラスを AppCompatButton にしてみたところ、Android4.x でもマテリアルデザインになりました。
public class MyButton extends AppCompatButton {
    public MyButton(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
}

support-v7 ライブラリには、Button だけでなく、ありとあらゆるウィジェットの AppCompat 版が用意されています。



つまり、TextView をカスタム化する時は AppCompatTextView を、CheckBox をカスタム化する時は AppCompatCheckBox を、というようにそれぞれの AppCompat 版を使わなくてならないのです。

これ結構重要なことだと思うのですが、ちゃんと解説している記事はほんとんど見あたりません。それぞれの Javadoc の中で
You should only need to manually use this class when writing custom views. (カスタム View を作るときのみマニュアルでこのクラスを使わなくてはならない)
と軽く説明されているだけです。実際これを守らなかった時に何が起こるかとか、これだけではまったく分からないと思います。

Android Lint や Inspection で警告を出してくれてもいいと思うのですが、今のところそれも無いようです。

更にもう一点説明しておくと、レイアウトXML の中で Button を指定すると、(AppCompat 系テーマを使用した場合、) 自動的に AppCompoatButton に置き換えられています。つまり開発者の意図しないところで勝手にクラスが置き換えられているのです。かなり強引なことをやっているなという印象を受けます。

まとめ

AppCompat 系テーマを使う場合は、Button や TextView ではなく、AppCompatButton や AppCompatTextView 等の AppCompat 専用の View を使いましょう。これを怠ると Android4 以前のデバイスでマテリアルデザインにならなかったり、カスタム定義したテーマが適用されないといった問題が発生します。

それにしてもこの件についてあまりにも情報が無いことが不思議でなりません。AppCompat 系テーマってそれほど使われていないのでしょうか?それともあまりにも当たり前過ぎて、説明するまでもない事なのでしょうか?

0 件のコメント :

コメントを投稿