2016年1月14日木曜日

一部の文字サイズを変えるだけの場合は RelativeSizeSpan が便利

相対文字サイズを変えるだけなら RelativeSizeSpan が便利です。 SpannableStringBuilder sb = new SpannableStringBuilder(); sb.append("Hello "); int start = sb.length(); sb.append("Android"); sb.setSpan(new RelativeSizeSpan(1.5f), start, sb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); TextView textView = (TextView) findViewById(R.id.text); textView.setText(sb);

2015年12月27日日曜日

Android 5.0未満では StateListDrawable に追加した Drawable の colorFilter は適用されない

5.0 未満では次のようなコードを実行しても赤色になりませんでしたが、5.0以降で実行すると赤色になります。 Drawable drawable = ContextCompat.getDrawable(context, R.drawable.ic_launcher); drawable.setColorFilter(Color.RED, PorterDuff.Mode.SRC_ATOP); StateListDrawable stateListDrawable = new StateListDrawable(); stateListDrawable.addState(new int[]{}, drawable); setBackground(stateListDrawable);
- 解説

原因は DrawableContainer の selectDrawable() での処理です。 5.0 から ColorFilter や tint を考慮するようになり、state に対応する Drawable に明示的に ColorFilter や tint をセットするようになりました。

4.4.0 の DrawableContainer.selectDrawable()
306 public boolean selectDrawable(int idx) { ... 336 if (d != null) { 337 d.mutate(); 338 if (mDrawableContainerState.mEnterFadeDuration > 0) { 339 mEnterAnimationEnd = now + mDrawableContainerState.mEnterFadeDuration; 340 } else { 341 d.setAlpha(mAlpha); 342 } 343 d.setVisible(isVisible(), true); 344 d.setDither(mDrawableContainerState.mDither); 345 d.setColorFilter(mColorFilter); 346 d.setState(getState()); 347 d.setLevel(getLevel()); 348 d.setBounds(getBounds()); 349 d.setLayoutDirection(getLayoutDirection()); 350 d.setAutoMirrored(mDrawableContainerState.mAutoMirrored); 351 } 5.0.1 の DrawableContainer.selectDrawable()
412 public boolean selectDrawable(int idx) { ... 442 if (d != null) { 443 d.mutate(); 444 if (mDrawableContainerState.mEnterFadeDuration > 0) { 445 mEnterAnimationEnd = now + mDrawableContainerState.mEnterFadeDuration; 446 } else if (mHasAlpha) { 447 d.setAlpha(mAlpha); 448 } 449 if (mDrawableContainerState.mHasColorFilter) { 450 // Color filter always overrides tint. 451 d.setColorFilter(mDrawableContainerState.mColorFilter); 452 } else { 453 if (mDrawableContainerState.mHasTintList) { 454 d.setTintList(mDrawableContainerState.mTintList); 455 } 456 if (mDrawableContainerState.mHasTintMode) { 457 d.setTintMode(mDrawableContainerState.mTintMode); 458 } 459 } 460 d.setVisible(isVisible(), true); 461 d.setDither(mDrawableContainerState.mDither); 462 d.setState(getState()); 463 d.setLevel(getLevel()); 464 d.setBounds(getBounds()); 465 d.setLayoutDirection(getLayoutDirection()); 466 d.setAutoMirrored(mDrawableContainerState.mAutoMirrored);

- 5.0 未満でどうするか

以下のコードでは、デフォルト用の Drawable からタップしたとき用の ColorFilter がかかった Drawable を用意し、StateListDrawable を構成して背景にセットしています。 このコードを 5.0 以降で実行すると思ったように動きますが、5.0 未満だとタップしたときに色が変わりません。 Drawable drawable = ContextCompat.getDrawable(context, R.drawable.ic_launcher); Drawable pressedDrawable = drawable.getConstantState().newDrawable().mutate(); pressedDrawable.setColorFilter(pressedColor, PorterDuff.Mode.SRC_ATOP); StateListDrawable stateListDrawable = new StateListDrawable(); stateListDrawable.addState(new int[]{android.R.attr.state_pressed}, pressedDrawable); stateListDrawable.addState(new int[]{}, drawable); setBackground(stateListDrawable); しょうがないので、http://stackoverflow.com/questions/7979440/android-cloning-a-drawable-in-order-to-make-a-statelistdrawable-with-filters にあるように次のような StateListDrawable の subclass を用意します。 private static class PressedStateListDrawable extends StateListDrawable { private final int pressedColor; public PressedStateListDrawable(Drawable drawable, int pressedColor) { super(); this.pressedColor = pressedColor; addState(new int[]{android.R.attr.state_pressed}, drawable); addState(new int[]{}, drawable); } @Override protected boolean onStateChange(int[] states) { boolean isPressed = false; for (int state : states) { if (state == android.R.attr.state_pressed) { isPressed = true; break; } } if (isPressed) { setColorFilter(pressedColor, PorterDuff.Mode.SRC_ATOP); } else { clearColorFilter(); } return super.onStateChange(states); } @Override public boolean isStateful() { return true; } } setBackground(new PressedStateListDrawable(drawable, pressedColor));


2015年11月3日火曜日

モジュール間の Product Flavor を同期させる

app モジュール('com.android.application')と api モジュール('com.android.library')があり、それぞれに staging と production flavor があるとする。

app モジュールの flavor が staging なら api モジュールの staging flavor を使い、
app モジュールの flavor が production なら api モジュールの production flavor を使いたい。

この場合、build.gradle に次のような設定を行う。

api モジュールの build.gradle apply plugin: 'com.android.library' configurations { stagingDebugCompile stagingReleaseCompile productionDebugCompile productionReleaseCompile } android { ... publishNonDefault true productFlavors { production { ... } staging { ... } } }

app モジュールの build.gradle apply plugin: 'com.android.application' configurations { stagingDebugCompile stagingReleaseCompile productionDebugCompile productionReleaseCompile } android { ... productFlavors { production { ... } staging { ... } } } dependencies { ... stagingDebugCompile project(path: ':api', configuration: 'stagingDebug') stagingReleaseCompile project(path: ':api', configuration: 'stagingRelease') productionDebugCompile project(path: ':api', configuration: 'productionDebug') productionReleaseCompile project(path: ':api', configuration: 'productionRelease') }