ObjectAnimator を使って View の表示切り替えをかっこよくしてみる


## はじめに

ビューの切り取り位置を少しずつ変化させながら表示、非表示を切り替え行うアニメーションを紹介します。

GitHub のサンプルプロジェクトはこちら。
https://github.com/suzukihr/ClipAnimation

参考ドキュメント
http://developer.android.com/intl/ja/guide/topics/graphics/prop-animation.html

![clip_animation.gif](https://raw.githubusercontent.com/suzukihr/ClipAnimation/master/sample.gif "clip_animation.gif")


## アニメーションの定義

まず下記ようなインタフェースを用意します。createPath メソッドは progress (0-1) と View のサイズからどこの位置を切り取るかを返します。

```java:ClipAnimation.java
public interface ClipAnimation {
    Path createPath(float progress, int width, int height);
}
```

サンプルでは createPath を実装したクラスを４つほど作りました。

```java:CircleClipAnimation.java
public class CircleClipAnimation implements ClipAnimation {
    @Override
    public Path createPath(float progress, int width, int height) {
        float radius = (float) (Math.sqrt(width * width + height * height) / 2f);
        float centerX = width / 2f;
        float centerY = height / 2f;

        Path path = new Path();
        path.addCircle(centerX, centerY, radius * progress, Path.Direction.CCW);
        return path;
    }
}
```

```java:LeftToRightClipAnimation.java
public class LeftToRightClipAnimation implements ClipAnimation {
    @Override
    public Path createPath(float progress, int width, int height) {
        Path path = new Path();
        path.addRect(0, 0, width * progress, height, Path.Direction.CCW);
        return path;
    }
}
```

```java:TopToBottomClipAnimation.java
public class TopToBottomClipAnimation implements ClipAnimation {
    @Override
    public Path createPath(float progress, int width, int height) {
        Path path = new Path();
        path.addRect(0, 0, width, height * progress, Path.Direction.CCW);
        return path;
    }
}
```



```java:DroidClipAnimation.java
public class DroidClipAnimation implements ClipAnimation {
    @Override
    public Path createPath(float progress, int width, int height) {
        float radius = (float) (Math.sqrt(width * width + height * height) / 2f);
        float leftEyeX = width * 62.8f / 192;
        float leftEyeY = height * 73.5f / 192;
        float rightEyeX = width * 128.8f / 192;
        float rightEyeY = leftEyeY;

        Path path = new Path();
        path.addCircle(leftEyeX, leftEyeY, radius * progress, Path.Direction.CCW);
        path.addCircle(rightEyeX, rightEyeY, radius * progress, Path.Direction.CCW);
        return path;
    }
}
```

## 使い方

アニメーション用のクラスを用意します。ポイントは onDraw 内の Canvas#clipPath の部分です。

```java:ClipAnimationImageView.java
public class ClipAnimationImageView extends ImageView {

    // animation type
    private ClipAnimation clipAnimation;

    // progress for animation
    private float progress = 1f;

    public ClipAnimationImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (clipAnimation != null) {
            canvas.clipPath(clipAnimation.createPath(progress, getWidth(), getHeight()));
        }
        super.onDraw(canvas);
    }

    /**
     * getter for ObjectAnimator
     */
    public float getProgress() {
        return progress;
    }

    /**
     * setter for ObjectAnimator
     */
    public void setProgress(float progress) {
        this.progress = progress;
    }

    public void show(ClipAnimation clipAnimation) {

        this.clipAnimation = clipAnimation;

        ObjectAnimator animator = ObjectAnimator.ofFloat(this, "progress", 0, 1f);
        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animation) {
                setVisibility(View.VISIBLE);
            }
        });
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                invalidate();
            }
        });
        animator.start();
    }

    public void hide(ClipAnimation clipAnimation) {

        this.clipAnimation = clipAnimation;

        ObjectAnimator animator = ObjectAnimator.ofFloat(this, "progress", 1f, 0);
        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                setVisibility(View.INVISIBLE);
            }
        });
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                invalidate();
            }
        });
        animator.start();
    }
}
```

あとは下記のように show, hide メソッドを呼べばオッケーです。

```
clipAnimationImageView.show(new CircleClipAnimation());
clipAnimationImageView.hide(new DroidClipAnimation());
```