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

やっほーい

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

# 問題

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

```gradle: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'
    ...
}
```

```xml: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>
```

```xml: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>
```

はい、正解は

こうです！

<img alt="activity.png" src="https://qiita-image-store.s3.amazonaws.com/0/82799/8061ff98-54c5-31ad-1a2e-d7254c28db05.png" width="320px" />


# なんでそうなるのか

### とその前に前提として
AppCompat系のやつって導入するとButtonとかTextViewとかいろいろなコンポーネントが`AppCompatXXX`に置換される。例えば、ButtonはAppCompatButtonってな具合にね。
[多分これに対応するやつに置き換わるんじゃないかなぁ](https://developer.android.com/reference/android/support/v7/widget/package-summary#classes)

[こっちのほうがわかりやすいかも](https://android.googlesource.com/platform/frameworks/support/+/64c32ba/v7/appcompat/src/android/support/v7/widget)

```kotlin: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()
            }
        }
    }
```

<img alt="AppCompatButton.png" src="https://qiita-image-store.s3.amazonaws.com/0/82799/49efcde0-78f4-aff2-75cd-6e5fd17de57b.png" width="320px" />


ちなみにドキュメントでもこのように書かれている
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行目付近にある

```java
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

```java
    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行目にでこういうのにぶち当たる

```java
    @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

<del>…ブルっちゃったよ。行数は全然短いのに頭に入ってこねぇ。</del>

<del>結局、どこでButtonがAppCompatButtonに差し替わったのかさっぱりだった。が`createView`あたりでなんかしらあるのかしら？…心が回復したらもっと見てみよう。</del>

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

### 【追記】
あった、ありました！まさにこれが見たかった！しかもすごくわかりやすい！
> 最近の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ってやつも導入したら同じように**なんかしらのなにかに置き換わりが発生しているのでは？**という推測ができるわけだ。

早速見てみよう

```kotlin: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()
            }
        }
    }
```

やっぱりそうだな。そうなるよな。
<img alt="MaterialButton.png" src="https://qiita-image-store.s3.amazonaws.com/0/82799/74a90065-7b45-a8f3-e5ef-4fe9ddef371c.png" width="320px" />

# じゃあどうすんべ

ほい、冒頭で書いた
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をこうしてやれば良いわけだ。

```xml: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がちゃんと？あたってるね
<img alt="NoMateralButton.png" src="https://qiita-image-store.s3.amazonaws.com/0/82799/7544551d-3ccc-fab5-16cd-83b0f134ee11.png" width="320px" />

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

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

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

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