20
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

GoodpatchAdvent Calendar 2016

Day 10

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

Last updated at Posted at 2016-12-11

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

まとめ

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

20
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?