マテリアルな AppCompat テーマの作成のひな

AppCompat のテーマ を使ったカラーリングは,

正直,

どのSDKバージョンで,

どのアイテムが,

どこに反映されるか

謎ですね?

実際,

実機で,

各バージョンで,

それぞれ確認してますね?

こんなかんじだそうですが...

AndroidのAppCompat Themeの継承関係がよくわからないので図にしてみた - Qiita

今や継承すぎですね.

以下で分かりやすくまとめられています.

MoshDev/ThemesAndStyles: A sample application demonstrate Android themes and styles capabilities


<style name="MaterialTheme" parent="Theme.AppCompat.Light">

    <!-- 1 -->
    <item name="colorPrimaryDark">#5c142a</item>

    <!-- 2 -->
    <item name="colorPrimary">#8B1E3F</item>

    <item name="colorAccent">#3C153B</item>

    <!-- 3. CheckBox, RadioButton, (SeekBar, ProgressBar with alpha)  & Inactive EditText underline-->
    <item name="android:colorControlNormal">#89BD9E</item>

    <!-- 4. Used with Active -> SeekBars, ProgressBars, Switches, Checkboxes and RadioButtons & Active EditText underline-->
    <item name="android:colorControlActivated">#006665</item>

    <!-- 5. Used with Buttons-->
    <item name="android:colorButtonNormal">#EB7BC0</item>

    <!-- 6. Used with Buttons & EditText text & Toolbar title-->
    <item name="android:textColorPrimary">#04dc00</item>

    <!-- 7. TextAppearance Medium-->
    <item name="android:textColorSecondary">#00f</item>

    <!-- 8. CompoundButton text-->
    <item name="android:textColorPrimaryDisableOnly">#F00</item>

    <!-- 9. Normal and TextAppearance Small TextView-->
    <item name="android:textColorTertiary">#FF934F</item>

    <!-- 10. EditText Hint text color-->
    <item name="android:textColorHint">#6f00ff</item>

    <!-- 11 -->
    <item name="android:windowBackground">@color/windowBackground</item>

    <!--Activating/Enabling Ripple Effect-->
    <item name="android:colorControlHighlight">#F0C987</item>

    <!--Defaults-->
    <!--<item name="colorControlNormal">?android:attr/textColorSecondary</item>-->
    <!--<item name="colorControlActivated">?attr/colorAccent</item>-->
    <!--<item name="colorControlHighlight">@color/ripple_material_light</item>-->
    <!--<item name="colorButtonNormal">@color/button_material_light</item>-->
  </style>

ThemesAndStyles/styles.xml at master · MoshDev/ThemesAndStyles

こういう役に立つドキュメントが公式にない (のかあるのか分からない) のが一番の謎ですね.


AppCompat 25.1.0 では効かない Android 4.4.x の backgroundTint

最初の私のイメージとしては,

サポートライブラリが充実してきているので ボタンの色は, banckgroundTint で簡単に変更できる!

でしたが...

おさらい

まず, ボタンを設置します.


    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="@android:color/white"
        android:text="Button" />

灰色のボタンが表示されて押したら, それなりにエフェクトが効いて「押されました感」が見て分かります.

ボタンの色を変えます.


    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="@android:color/white"
        android:text="Button"
        android:background="@color/colorAccent" />

ボタンの色が変わりましたが, 押したときにエフェクトが効きません.

background 属性のかわりに backgroudTint を使います.


    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="@android:color/white"
        android:text="Button"
        android:backgroundTint="@color/colorAccent" />

これで, 背景色も変わりかつエフェクトも効きます.

background ではなく, backgroundTint で色を指定する

ということですね.

Button と AppCompatButton の記述

StackOverflow などを見ているとさまざまな記述が見えます.

android - Lollipop's backgroundTint has no effect on a Button - Stack Overflow

ただ「ボタンの色を変えたいだけ」なのですが, なんだか混乱しています.

backgroudTint を使った記述にも似たようなものいくつかあるようです.


    <android.support.v7.widget.AppCompatButton
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="@android:color/white"
        android:text="AppCompatButton"
        android:backgroundTint="@color/colorAccent" />


    <android.support.v7.widget.AppCompatButton
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="@android:color/white"
        android:text="AppCompatButton"
        app:backgroundTint="@color/colorAccent" />

Button のかわりに AppCompatButton を使っていたり, backgroundTint の prefix が android: となっていたり app: になっています.

とりあえず, すべてをレイアウトに記述して表示してみました.

以下3つの挙動は予想通りで同じです.


<Button
    android:backgroundTint="@color/colorAccent"
    ...


<AppCompatButton
    android:backgroundTint="@color/colorAccent"
    ...


<AppCompatButton
    app:backgroundTint="@color/colorAccent"
    ...

Button と記述した場合でも, 内部でいい感じに入れ替えてくれているようですね.

[Tool]-[Android]-[Layout Inspector]

AppCompat を意識すること無く, 昔からの Button を使って記述していけばサポートライブラリが対応してくれるということですね!

AppCompat は えらい!!

Android 4.4 KITKAT で確認する

結果 : 全てダメ.

え,

色とエフェクト両方きちんと反映されているものがない...

なんなんすかね これ...

AppCompat のバージョンを下げてみます.

//compile 'com.android.support:appcompat-v7:25.1.0'
compile 'com.android.support:appcompat-v7:25.0.1'

Android 4.4 では, backgroudTint が ひとつの記述だけしか意図通りに表示してくれません.

AppCompat はクソ!!

そら混乱もしますわ.

まとめ

backgroundTint についての
OSバージョンと appcompat-v7 バージョンの関係

現状では,

AppCompat-v7 を 25.0.1 で


   <android.support.v7.widget.AppCompatButton
	....
        app:backgroundTint="@color/colorAccent" />

と書いておくのが吉.


Espresso で アイドリング

espresso

Espresso は以下の非同期処理は考慮してくれるようですが.

- メッセージキューを利用したUIイベント
- デフォルト AsyncTask のスレッドプールを利用したタスク

Square Island: Espresso: Custom Idling Resource

これらに該当しない処理でも, テスト実行を「あるタイミング」まで待たせたいときがありますよね?

カスタムした「IdlingResource」を使ってみましょう.

Espresso Idling Resource

ここで書かれているアプローチとしては2つ.

Counting running jobs: When a job starts, increment a counter. When it finishes, decrement it. The app is idle if the counter is zero. This approach is very simple and accounts for most situations. CountingIdlingResource does exactly this.

Querying state: It might be more reliable to ask a work queue or an HTTP client (or whatever is doing the background work) if it is busy. If the state is exposed, implementing an Idling Resource is trivial.

これらのうち, 汎用的に使えるジョブカウンターを利用した方法を, Googleサンプルを眺めつつ手順整理しておきます.

Android Testing Codelab

1. IdlingResource ユーティリティの作成

アイドリング向けのユーテリティを作成します.
そのまま2つのクラスを利用してもいいかもしれません.

EspressoIdlingResource.java at master · googlecodelabs/android-testing

SimpleCountingIdlingResource.java at master · googlecodelabs/android-testing

2. テスト対象の Activity に記述

テスト対象となる Activity にテスト時に利用するメソッドを記述しておきます.
テスト時にはこのメソッドを利用して, 先ほどのユーティリティを利用登録します.


    @VisibleForTesting
    public IdlingResource getCountingIdlingResource() {
        return EspressoIdlingResource.getIdlingResource();
    }

AddNoteActivity.java at master · googlecodelabs/android-testing

NotesActivity.java at master · googlecodelabs/android-testing

3. 「待たせる」処理の記述

Presenter や View などのテスト対象クラス内にアイドリングの利用タイミングを記述しておきます.


EspressoIdlingResource.increment(); 


EspressoIdlingResource.decrement(); 

カウンターを増減しながらアイドリングを操作します.
カウンターが0になると処理が再開されます.


    @Override
    public void loadNotes(boolean forceUpdate) {
        mNotesView.setProgressIndicator(true);
        if (forceUpdate) {
            mNotesRepository.refreshData();
        }

        // The network request might be handled in a different thread so make sure Espresso knows
        // that the app is busy until the response is handled.
        EspressoIdlingResource.increment(); // App is busy until further notice

        mNotesRepository.getNotes(new NotesRepository.LoadNotesCallback() {
            @Override
            public void onNotesLoaded(List<Note> notes) {
                EspressoIdlingResource.decrement(); // Set app as idle.
                mNotesView.setProgressIndicator(false);
                mNotesView.showNotes(notes);
            }
        });
    }

AddNoteFragment.java at master · googlecodelabs/android-testing

NotesPresenter.java at master · googlecodelabs/android-testing

4. テスト内で登録する

テスト対象Activityに記述したメソッドを利用してテストコードへ登録します
アイドリング処理がテスト時に反映されます.


    @Before
    public void registerIdlingResource() {
        Espresso.registerIdlingResources(
                mAddNoteIntentsTestRule.getActivity().getCountingIdlingResource());
    }

    @After
    public void unregisterIdlingResource() {
        Espresso.unregisterIdlingResources(
                mAddNoteIntentsTestRule.getActivity().getCountingIdlingResource());
    }

AddNoteScreenTest.java at master · googlecodelabs/android-testing

まとめ

テストにかかるコストがただの自己満足で終わらないようにしたいです.

JakeWharton/okhttp-idling-resource: An Espresso IdlingResource for OkHttp.

PSA: Dont Use Espresso Idling Resources like Google does · Philosophical Hacker