追記
最新の24.0.0では修正されています。
TL;DR
- Lollipopでは
ViewCompat#setBackgroundTintList
を実行しても即時反映されない - Lollipopで即時反映させるには
setBackgroundTintList
の前にsetBackgroundTintMode
する
環境
- Support Library v4 23.4.0/
24.0.0
現象
backgroundで使用するShapeを用意して、
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="8dp" />
<solid android:color="#000000" />
</shape>
Viewのbackgroundに指定し、
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
tools:context="com.chibatching.sandbox.MainActivity">
<TextView
android:id="@+id/version_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="50dp"
android:textColor="#000000"
android:textSize="32dp" />
<TextView
android:id="@+id/background_tint_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/test_background"
android:textColor="#ffffff"
android:textStyle="bold"
android:padding="30dp"
android:text="Background Tint" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="50dp"
android:text="Button" />
</LinearLayout>
ボタンクリックでTextViewのbackgroundにtintを適用して色を変えます。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final TextView versionText = (TextView) findViewById(R.id.version_text);
versionText.setText("Android SDK " + Build.VERSION.SDK_INT);
final TextView textView = (TextView) findViewById(R.id.background_tint_text);
final ColorStateList stateList = new ColorStateList(
new int[][]{{}}, new int[]{Color.parseColor("#aa0000")});
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ViewCompat.setBackgroundTintList(textView, stateList);
}
});
}
}
実行するとLollipopだけ色が反映されません。
ただし、バックグラウンドなどから復帰するなどすると反映されます。
原因
ViewCompat#setBackgroundTintList
の処理を追っていくと、 ViewCompatLollipop#setBackgroundTintList
に「tintを適用しても状態が更新されないLのバグを回避するワークアラウンドだよ」とコメントされているコードが埋め込まれています。
static void setBackgroundTintList(View view, ColorStateList tintList) {
view.setBackgroundTintList(tintList);
if (Build.VERSION.SDK_INT == 21) {
// Work around a bug in L that did not update the state of the background
// after applying the tint
Drawable background = view.getBackground();
boolean hasTint = (view.getBackgroundTintList() != null)
&& (view.getBackgroundTintMode() != null);
if ((background != null) && hasTint) {
if (background.isStateful()) {
background.setState(view.getDrawableState());
}
view.setBackground(background);
}
}
}
しかし、最初のsetBackgroundTintList
を実行しただけではview.getBackgroundTintMode()
がnullのままになるため、hasTint = false
となってしまい、結局何も処理が行われません。
対策
view.getBackgroundTintMode()
がnullにならなければ、その先の処理も実行されるはずです。
なので、ViewCompat#setBackgroundTintList
実行前にTint modeを設定します。
@Override
public void onClick(View v) {
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP) {
if (textView.getBackgroundTintMode() == null) {
textView.setBackgroundTintMode(PorterDuff.Mode.SRC_IN);
}
}
ViewCompat.setBackgroundTintList(textView, stateList);
}
これでLollipopでもbackgroundTintが即時反映されるようになりました。
おわりに
バージョン差異をいい感じに吸収してくれるはずのSupport Libraryでこういうのあるとはまりますね。。。
(追記)
雑にAOSPにissue上げました。スター増えると直るかも?
https://code.google.com/p/android/issues/detail?id=215422