2015年10月29日木曜日

Android ビルド時に git のブランチ名を埋め込む方法

ざきさんに教えてもらった。ありがとうざきさん。 defaultConfig { ... buildConfigField "String", "GIT_BRANCH", "\"${'git rev-parse --abbrev-ref HEAD'.execute([], project.rootDir).text.trim()}\"" } とすると、コードから BuildConfig.GIT_BRANCH でブランチ名を取得できる。


2015年9月15日火曜日

AppCompat でカラーの Raised Button を設定する正しい方法

http://www.google.com/design/spec/components/buttons.html#buttons-flat-raised-buttons

まとめ
  • ボタンの文字は android:textAppearanceButton(文字サイズは 14sp、文字色は textColorPrimary)
  • ボタンの色は colorButtonNormal
    • 4以下にも適用される
    • テーマ直下の属性なので、全部のボタンに適用されてしまうのが難点

1. 背景が Light でボタンのテキストが Dark <style name="AppTheme" parent="Theme.AppCompat.Light"> <item name="colorButtonNormal">#2196F3</item> </style>

2. 背景が Light でボタンのテキストが Dark(Disabled 時はグレー) <style name="AppTheme" parent="Theme.AppCompat.Light"> <item name="colorButtonNormal">@color/button_color</item> </style> res/color/button_color.xml <?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:color="#1E000000" android:state_enabled="false" /> <item android:color="#2196F3" /> </selector>

3. 背景が Dark でボタンのテキストが Light <style name="AppTheme" parent="Theme.AppCompat"> <item name="colorButtonNormal">#2196F3</item> </style>

4. 背景が Dark でボタンのテキストが Light(Disabled 時はグレー) <style name="AppTheme" parent="Theme.AppCompat"> <item name="colorButtonNormal">@color/button_color</item> </style> res/color/button_color.xml <?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:color="#1EFFFFFF" android:state_enabled="false" /> <item android:color="#2196F3" /> </selector>

5. 背景が Light でボタンのテキストが Light <style name="AppTheme" parent="Theme.AppCompat.Light"> <item name="colorButtonNormal">#2196F3</item> <item name="android:textAppearanceButton">@style/TextAppearance.Button</item> </style> <style name="TextAppearance.Button" parent="Base.TextAppearance.AppCompat.Button"> <item name="android:textColor">?android:attr/textColorPrimaryInverse</item> </style>

6. 背景が Light でボタンのテキストが Light(Disabled 時はグレー) <style name="AppTheme" parent="Theme.AppCompat.Light"> <item name="colorButtonNormal">@color/button_color</item> <item name="android:textAppearanceButton">@style/TextAppearance.Button</item> </style> <style name="TextAppearance.Button" parent="Base.TextAppearance.AppCompat.Button"> <item name="android:textColor">?android:attr/textColorPrimaryInverse</item> </style> res/color/button_color.xml <?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:color="#1E000000" android:state_enabled="false" /> <item android:color="#2196F3" /> </selector>

7. 背景が Light でボタンのテキストが Light(Disabled 時は文字もグレー) <style name="AppTheme" parent="Theme.AppCompat.Light"> <item name="colorButtonNormal">@color/button_color</item> <item name="android:textAppearanceButton">@style/TextAppearance.Button</item> </style> <style name="TextAppearance.Button" parent="Base.TextAppearance.AppCompat.Button"> <item name="android:textColor">@color/button_text_color</item> </style> res/color/button_color.xml <?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:color="#1E000000" android:state_enabled="false" /> <item android:color="#2196F3" /> </selector> res/color/button_text_color.xml <?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:color="@color/primary_text_disabled_material_light" android:state_enabled="false" /> <item android:color="@color/primary_text_default_material_dark" /> </selector>


解説

Widget.AppCompat.Button

values.xml <style name="Widget.AppCompat.Button" parent="Base.Widget.AppCompat.Button"/>

Base.Widget.AppCompat.Button

values.xml <style name="Base.Widget.AppCompat.Button" parent="android:Widget"> <item name="android:background">@drawable/abc_btn_default_mtrl_shape</item> <item name="android:textAppearance">?android:attr/textAppearanceButton</item> <item name="android:minHeight">48dip</item> <item name="android:minWidth">88dip</item> <item name="android:focusable">true</item> <item name="android:clickable">true</item> <item name="android:gravity">center_vertical|center_horizontal</item> </style> values-v21.xml <style name="Base.Widget.AppCompat.Button" parent="android:Widget.Material.Button"/> <style name="Widget.Material.Button"> <item name="background">@drawable/btn_default_material</item> <item name="textAppearance">?attr/textAppearanceButton</item> <item name="minHeight">48dip</item> <item name="minWidth">88dip</item> <item name="stateListAnimator">@anim/button_state_list_anim_material</item> <item name="focusable">true</item> <item name="clickable">true</item> <item name="gravity">center_vertical|center_horizontal</item> </style> これを見ると、ボタンの文字は ?attr/textAppearanceButton を参照していることがわかる


textAppearanceButton

values.xml <style name="Theme.AppCompat" parent="Base.Theme.AppCompat"/> <style name="Base.Theme.AppCompat" parent="Base.V7.Theme.AppCompat"> <style name="Base.V7.Theme.AppCompat" parent="Platform.AppCompat"> <item name="android:textAppearanceButton">@style/TextAppearance.AppCompat.Button</item> ... </style> <style name="Base.V7.Theme.AppCompat.Light" parent="Platform.AppCompat.Light"> <item name="android:textAppearanceButton">@style/TextAppearance.AppCompat.Button</item> </style> <style name="TextAppearance.AppCompat.Button" parent="Base.TextAppearance.AppCompat.Button"/> <style name="Base.TextAppearance.AppCompat.Button"> <item name="android:textSize">@dimen/abc_text_size_button_material</item> <item name="textAllCaps">true</item> <item name="android:textColor">?android:textColorPrimary</item> </style> values-v14.xml <style name="Base.TextAppearance.AppCompat.Button"> <item name="android:textSize">@dimen/abc_text_size_button_material</item> <item name="android:textAllCaps">true</item> <item name="android:textColor">?android:textColorPrimary</item> </style> 文字サイズは @dimen/abc_text_size_button_material (=14sp)、文字色は ?attr/textColorPrimary である。

values-v21.xml <style name="Base.Theme.AppCompat" parent="Base.V21.Theme.AppCompat"/> <style name="Base.V21.Theme.AppCompat" parent="Base.V7.Theme.AppCompat"> ... </style> <style name="Base.TextAppearance.AppCompat.Button" parent="android:TextAppearance.Material.Button"/> styles_material.xml <style name="TextAppearance.Material.Widget.Button" parent="TextAppearance.Material.Button" /> <style name="TextAppearance.Material.Button"> <item name="textSize">@dimen/text_size_button_material</item> <item name="fontFamily">@string/font_family_button_material</item> <item name="textAllCaps">true</item> <item name="textColor">?attr/textColorPrimary</item> </style> 文字サイズは @dimen/text_size_button_material (=14sp)、文字色は ?attr/textColorPrimary である。


btn_default_material <?xml version="1.0" encoding="utf-8"?> <!-- Copyright (C) 2014 The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> <ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="?attr/colorControlHighlight"> <item android:drawable="@drawable/btn_default_mtrl_shape" /> </ripple>

btn_default_mtrl_shape <?xml version="1.0" encoding="utf-8"?> <!-- Copyright (C) 2014 The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> <!-- Used as the canonical button shape. --> <inset xmlns:android="http://schemas.android.com/apk/res/android" android:insetLeft="@dimen/button_inset_horizontal_material" android:insetTop="@dimen/button_inset_vertical_material" android:insetRight="@dimen/button_inset_horizontal_material" android:insetBottom="@dimen/button_inset_vertical_material"> <shape android:shape="rectangle"> <corners android:radius="@dimen/control_corner_material" /> <solid android:color="?attr/colorButtonNormal" /> <padding android:left="@dimen/button_padding_horizontal_material" android:top="@dimen/button_padding_vertical_material" android:right="@dimen/button_padding_horizontal_material" android:bottom="@dimen/button_padding_vertical_material" /> </shape> </inset> これを見ると ?attr/colorButtonNormal が solid の color に指定されていることがわかる


AndroidJUnitRunner で Toast や PopupWindow を表示するには

Toast、Dialog、PopupWindow など別の Window を使う操作をテストメソッド内で行うと、Handler が作れないと言われてエラーになります (PopupWindow を内包したユーティリティクラスのテストで困りました)。

例えば以下のテストを実行すると、Toast 部分で

java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()

になります。 @RunWith(AndroidJUnit4.class) @LargeTest public class SimpleMenuPopupTest { @Test public void showToastTest() { Context context = InstrumentationRegistry.getTargetContext(); Toast.makeText(context, "Hello Android.", Toast.LENGTH_SHORT).show(); } }
このような Handler の生成が必要な操作では Instrumentation.runOnMainSync() を利用します。 @RunWith(AndroidJUnit4.class) @LargeTest public class SimpleMenuPopupTest { @Test public void showToastTest() { InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { @Override public void run() { Context context = InstrumentationRegistry.getTargetContext(); Toast.makeText(context, "Hello Android.", Toast.LENGTH_SHORT).show(); } }); } } これでエラーにならなくなりました。


Dialog の場合、InstrumentationRegistry.getTargetContext() では Window Token が無いと言われてエラーになるので、ActivityTestRule から取得した Activity とかを使います。

android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an application


Espresso のコードはテストメソッドの方に書きます。 @RunWith(AndroidJUnit4.class) @LargeTest public class SimpleMenuPopupTest { @Test public void showPopupTest() { InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { @Override public void run() { Context context = activityRule.getActivity(); new AlertDialog.Builder(context) .setMessage("Hello Android.") .show(); } }); onView(withText("Hello Android.")).check(matches(isDisplayed())); } }


参考