Toolbar にあるメニューアイコンを押した時にアニメーションを実装したいと思ったことはありませんか? 例えば何らかのデータを更新するメニューだったとして、プログレスバーを別の場所に表示するより、メニューのアイコンが変化した方がユーザには分かりやすい場合もあると思います。そこで、Toolbar にあるメニューアイコンをアニメーションする方法をご紹介したいと思います。
1. View#startAnimation(Animation) でアニメーションする
一番最初に思いついたのは、単純に startAnimation でアニメーションを開始する方法です。メニューの View は Activity#findViewById(int) に該当のメニュー ID で検索すると取得できます。
View menuView = activity.findViewById(menuItem.getItemId());
// アニメーションを開始
menuView.startAnimation(animation);
// アニメーションを終了
animation.cancel();
animation.reset();
menuView.clearAnimation();
これで Toolbar に表示されているメニューにアニメーションが行われます。
2. アイコンだけアニメーションする
しかし、menuView は TextView クラスを継承した ActionMenuItemView で、テキストを表示することも可能です。このテキストが表示されていると、テキストも一緒に回転してしまう不具合がありました。
これを回避するためにアイコンだけアニメーションするようにします。
menuView menuItem にアニメーションできる Drawable をセットした後、そのアイコンを取得してアニメーションを開始します。
アイコンは TextView.getCompoundDrawables() の left、つまり 0 番目で取得することができます。 MenuItem#getIcon() で取得できます。
menuItem.setIcon(animatableDrawable);
Drawable leftDrawable = menuItem.getIcon();
// アニメーションを開始する
((AnimatableDrawable) leftDrawable).start();
// アニメーションを終了する
((AnimatableDrawable) leftDrawable).stop();
AnimatableDrawable は Y.A.M の 雑記帳: カスタムDrawableで複雑なプログレスを作る が参考になります。
public class AnimatableDrawable extends BitmapDrawable implements Animatable {
private ValueAnimator valueAnimator;
private boolean canAnimationFinish;
public AnimatableDrawable(Resources res, BitmapDrawable original) {
super(res, original.getBitmap());
init();
}
private void init() {
valueAnimator = ValueAnimator.ofFloat(0f, 1.0f);
valueAnimator.setDuration(540);
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
valueAnimator.setInterpolator(new RotateInterpolator());
valueAnimator.addListener(new UpdateAnimatorListener());
}
@Override
public void start() {
if (valueAnimator.isStarted()) {
return;
}
canAnimationFinish = false;
valueAnimator.start();
invalidateSelf();
}
@Override
public void stop() {
canAnimationFinish = true;
}
@Override
public boolean isRunning() {
return valueAnimator.isRunning();
}
@Override
public void draw(@NonNull Canvas canvas) {
Bitmap bitmap = getBitmap();
if (bitmap == null) {
return;
}
if (valueAnimator.isStarted()) {
int width = bitmap.getWidth();
int height = bitmap.getHeight();
float degrees = -360 * valueAnimator.getAnimatedFraction();
float px = width / 2;
float py = height / 2;
Matrix matrix = new Matrix();
matrix.postTranslate(-width / 2, -height / 2);
matrix.postRotate(degrees);
matrix.postTranslate(px, py);
canvas.drawBitmap(bitmap, matrix, null);
invalidateSelf();
} else {
super.draw(canvas);
}
}
@Override
public void setAlpha(int i) {
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
private class UpdateAnimatorListener implements Animator.AnimatorListener {
@Override
public void onAnimationStart(Animator animator) {
((RotateInterpolator) animator.getInterpolator()).stage = RotateInterpolator.Stage.start;
}
@Override
public void onAnimationEnd(Animator animator) {
}
@Override
public void onAnimationCancel(Animator animator) {
}
@Override
public void onAnimationRepeat(Animator animator) {
RotateInterpolator interpolator = (RotateInterpolator) animator.getInterpolator();
if (interpolator.stage == RotateInterpolator.Stage.start) {
interpolator.stage = RotateInterpolator.Stage.repeating;
} else if (canAnimationFinish) {
if (interpolator.stage == RotateInterpolator.Stage.repeating) {
interpolator.stage = RotateInterpolator.Stage.end;
} else {
animator.end();
}
}
}
}
private static class RotateInterpolator implements Interpolator {
private Stage stage = Stage.start;
@Override
public float getInterpolation(float input) {
return stage.getInterpolation(input);
}
enum Stage {
start {
@Override
public float getInterpolation(float input) {
return input * input;
}
},
repeating {
@Override
public float getInterpolation(float input) {
return input * 2;
}
},
end {
@Override
public float getInterpolation(float input) {
return 1.0f - (1.0f - input) * (1.0f - input);
}
},
;
public abstract float getInterpolation(float input);
}
}
}
まとめ
ユーザの操作に何も反応がないとユーザが動いているのか不安になることも考えられます。プログレスバーを出すほどでもないし、かといって何もないのは…というのであればアニメーションを実装してみてはいかがでしょうか?