LoginSignup
5
3

More than 5 years have passed since last update.

MaterialComponentsを導入したらThemeが適用されなくなった件について(ほらAppCompatの復習だ編)

Last updated at Posted at 2018-08-22

やっほーい

MaterialDesign2良いですねぇ、Chipとか最高じゃないすかぁ!と思って導入したらなんか見た目がおかしくなった。今回はそれについて書いていくんだよ。

問題

こういうとき、Buttonはどういう見た目になるでしょう?

build.gradle
android {
    compileSdkVersion 28
    ...
}
...
dependencies {
    ...
    implementation 'com.android.support:appcompat-v7:28.0.0-rc01'
    implementation 'com.android.support:design:28.0.0-rc01'
    ...
}
styles.xml
<resources>
    <style name="AppTheme" parent="Theme.MaterialComponents.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="buttonStyle">@style/MyButtonStyle</item>
    </style>

    <style name="MyButtonStyle" parent="Widget.AppCompat.Button">
        <item name="android:backgroundTint">@android:color/black</item>
        <item name="android:textColor">@android:color/white</item>
        <item name="android:stateListAnimator">@null</item>
    </style>

</resources>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="うぇーい"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>

</android.support.constraint.ConstraintLayout>

はい、正解は

こうです!

activity.png

なんでそうなるのか

とその前に前提として

AppCompat系のやつって導入するとButtonとかTextViewとかいろいろなコンポーネントがAppCompatXXXに置換される。例えば、ButtonはAppCompatButtonってな具合にね。
多分これに対応するやつに置き換わるんじゃないかなぁ

こっちのほうがわかりやすいかも

MainActivity.kt
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        findViewById<Button>(R.id.btn).setOnClickListener {
            if (it is AppCompatButton) {
                Toast.makeText(MainActivity@this, "実はAppCompatButtonなの", Toast.LENGTH_SHORT).show()
            }
        }
    }

AppCompatButton.png

ちなみにドキュメントでもこのように書かれている
https://developer.android.com/reference/android/support/v7/widget/AppCompatButton

This will automatically be used when you use Button in your layouts and the top-level activity / dialog is provided by appcompat. You should only need to manually use this class when writing custom views.

寄り道だよここは

ほーん、じゃあAppCompatActivityのソースを見てみようどうなってるのか見てみたいしね☆ってなってちょっと追ってみた
https://android.googlesource.com/platform/frameworks/support/+/oreo-cts-release/v7/appcompat/src/android/support/v7/app/AppCompatActivity.java
こいつの72行目付近にある

delegate.installViewFactory();

怪しいですねぇ…実に怪しい
じゃあほら、追っかけてみようか
https://android.googlesource.com/platform/frameworks/support/+/oreo-cts-release/v7/appcompat/src/android/support/v7/app/AppCompatDelegate.java#388
388行目付近のDocコメントにそれっぽいことが書かれている。どうやらここがゴールっぽい。

でだ同じソースの195行目付近にこんなものが書かれていますねぇ…
https://android.googlesource.com/platform/frameworks/support/+/oreo-cts-release/v7/appcompat/src/android/support/v7/app/AppCompatDelegate.java#195

    private static AppCompatDelegate create(Context context, Window window,
            AppCompatCallback callback) {
        if (Build.VERSION.SDK_INT >= 24) {
            return new AppCompatDelegateImplN(context, window, callback);
        } else if (Build.VERSION.SDK_INT >= 23) {
            return new AppCompatDelegateImplV23(context, window, callback);
        } else if (Build.VERSION.SDK_INT >= 14) {
            return new AppCompatDelegateImplV14(context, window, callback);
        } else if (Build.VERSION.SDK_INT >= 11) {
            return new AppCompatDelegateImplV11(context, window, callback);
        } else {
            return new AppCompatDelegateImplV9(context, window, callback);
        }
    }

それからそれから更に更に追ってみると(ゴールって言ったのは嘘だね。ゴメンねもうちょっとだけ続くんじゃ)
https://android.googlesource.com/platform/frameworks/support/+/oreo-cts-release/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV9.java#1056

ここの1056行目にでこういうのにぶち当たる

    @Override
    public void installViewFactory() {
        LayoutInflater layoutInflater = LayoutInflater.from(mContext);
        if (layoutInflater.getFactory() == null) {
            LayoutInflaterCompat.setFactory2(layoutInflater, this);
        } else {
            if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImplV9)) {
                Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
                        + " so we can not install AppCompat's");
            }
        }
    }

で、今度はリファレンスだ
https://developer.android.com/reference/android/view/LayoutInflater#getFactory2()

Return the current LayoutInflater.Factory2. Returns null if no factory is set or the set factory does not implement the LayoutInflater.Factory2 interface. This is called on each element name. If the factory returns a View, add that to the hierarchy. If it returns null, proceed to call onCreateView(name).

あぁ!それっぽいことが書かれてる
よーし!ソース見てみるぞー!
https://android.googlesource.com/platform/frameworks/support/+/oreo-cts-release/compat/java/android/support/v4/view/LayoutInflaterCompat.java

…ブルっちゃったよ。行数は全然短いのに頭に入ってこねぇ。 結局、どこでButtonがAppCompatButtonに差し替わったのかさっぱりだった。が`createView`あたりでなんかしらあるのかしら?…心が回復したらもっと見てみよう。

でもAppCompatActivityを使うと onCreateLayoutInflaterの何かが差し替わってAppCompatXXXが提供されるってことは雰囲気でなんとなくわかった。

【追記】

あった、ありました!まさにこれが見たかった!しかもすごくわかりやすい!

最近のSupport Libraryで(Activity)view.getContext()するとクラッシュするので注意
https://qiita.com/takahirom/items/aa9b981a77d7c3ae0c64

なるほどAppCompatViewInflaterってやつが置き換えてるのか…
でもう一回追いかけて見たらそもそも
https://android.googlesource.com/platform/frameworks/support/+/oreo-cts-release/v7/appcompat/src/android/support/v7/app/AppCompatDelegate.java#94
これがFactory2を実装していて、LayoutInflaterCompat.setFactory2(layoutInflater, this);
thisって書かれてんのよね…

で、見てみたら
https://android.googlesource.com/platform/frameworks/support/+/oreo-cts-release/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV9.java#1009

件のAppCompatViewInflaterが使われていて、そいつが差し替えを行っていた。と。はぇ〜(感嘆)

で何なのよ?

MaterialComopnentsってやつも導入したら同じようになんかしらのなにかに置き換わりが発生しているのでは?という推測ができるわけだ。

早速見てみよう

MainActivity.kt
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        findViewById<Button>(R.id.btn).setOnClickListener {
            if (it is MaterialButton) {
                Toast.makeText(MainActivity@this, "実はMaterialButtonになったの", Toast.LENGTH_SHORT).show()
            }
        }
    }

やっぱりそうだな。そうなるよな。
MaterialButton.png

じゃあどうすんべ

ほい、冒頭で書いた
https://developer.android.com/reference/android/support/v7/widget/AppCompatButton

This will automatically be used when you use Button in your layouts and the top-level activity / dialog is provided by appcompat. You should only need to manually use this class when writing custom views.

こいつはButtonを書くと置き換わるって書いてあったよなぁ
じゃあButtonをこうしてやれば良いわけだ。

activity_main.xml
    <android.support.v7.widget.AppCompatButton
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="うぇーい"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>

Styleがちゃんと?あたってるね
NoMateralButton.png

で、ありました!あったのです!なんか置き換えをしてそうなやつが!
https://developer.android.com/reference/com/google/android/material/theme/MaterialComponentsViewInflater

まとめ

いずれ対応するときが来るんだからさ、ButtonのスタイルとかのparentをWidget.MaterialComponents.Buttonとかにして対応したほうが良いんでない?

あとリファレンスはちゃんと読まなきゃなぁと思いましたまる

ソースもちゃんと読まなきゃと思いました

5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3