はじめに
ビューの切り取り位置を少しずつ変化させながら表示、非表示を切り替え行うアニメーションを紹介します。
GitHub のサンプルプロジェクトはこちら。
https://github.com/suzukihr/ClipAnimation
参考ドキュメント
http://developer.android.com/intl/ja/guide/topics/graphics/prop-animation.html
アニメーションの定義
まず下記ようなインタフェースを用意します。createPath メソッドは progress (0-1) と View のサイズからどこの位置を切り取るかを返します。
ClipAnimation.java
public interface ClipAnimation {
Path createPath(float progress, int width, int height);
}
サンプルでは createPath を実装したクラスを4つほど作りました。
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;
}
}
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;
}
}
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;
}
}
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 の部分です。
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());