Help us understand the problem. What is going on with this article?

Toolbar のメニューアイコンをアニメーションする

More than 3 years have passed since last update.

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();

update_portrait.gif

これで Toolbar に表示されているメニューにアニメーションが行われます。

2. アイコンだけアニメーションする

しかし、menuView は TextView クラスを継承した ActionMenuItemView で、テキストを表示することも可能です。このテキストが表示されていると、テキストも一緒に回転してしまう不具合がありました。

update_landscape_with_text.gif

これを回避するためにアイコンだけアニメーションするようにします。
menuView menuItem にアニメーションできる Drawable をセットした後、そのアイコンを取得してアニメーションを開始します。
アイコンは TextView.getCompoundDrawables() の left、つまり 0 番目で取得することができます。 MenuItem#getIcon() で取得できます。

アイコンだけのアニメーション
menuItem.setIcon(animatableDrawable);

Drawable leftDrawable = menuItem.getIcon();
// アニメーションを開始する
((AnimatableDrawable) leftDrawable).start();

// アニメーションを終了する
((AnimatableDrawable) leftDrawable).stop();

update_landscape.gif

AnimatableDrawable は Y.A.M の 雑記帳: カスタムDrawableで複雑なプログレスを作る が参考になります。

AnimatableDrawable.java
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);
        }
    }
}

まとめ

ユーザの操作に何も反応がないとユーザが動いているのか不安になることも考えられます。プログレスバーを出すほどでもないし、かといって何もないのは…というのであればアニメーションを実装してみてはいかがでしょうか?

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした