package example.easy.animations.view; import android.content.Context; import android.graphics.Camera; import android.graphics.Matrix; import android.util.AttributeSet; import android.view.View; import android.view.animation.Animation; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import android.view.animation.Transformation; import android.widget.FrameLayout; public class FlipLayout extends FrameLayout implements Animation.AnimationListener, View.OnClickListener, OnSwipeListener { public static final int ANIM_DURATION_MILLIS = 500; private static final Interpolator fDefaultInterpolator = new DecelerateInterpolator(); private OnFlipListener listener; private FlipAnimator animator; private boolean isFlipped; private Direction direction; private OnSwipeTouchListener touchListener; private View frontView, backView; public FlipLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context); } public FlipLayout(Context context, AttributeSet attrs) { super(context, attrs); init(context); } public FlipLayout(Context context) { super(context); init(context); } private void init(Context context) { animator = new FlipAnimator(); animator.setAnimationListener(this); animator.setInterpolator(fDefaultInterpolator); animator.setDuration(ANIM_DURATION_MILLIS); direction = Direction.DOWN; setSoundEffectsEnabled(true); touchListener = new OnSwipeTouchListener(context); } @Override protected void onFinishInflate() { super.onFinishInflate(); if (getChildCount() > 2) { throw new IllegalStateException("FlipLayout can host only two direct children"); } frontView = getChildAt(0); frontView.setOnTouchListener(touchListener); frontView.setOnClickListener(this); backView = getChildAt(1); backView.setOnTouchListener(touchListener); backView.setOnClickListener(this); touchListener.addSwipeListener(this); reset(); } private void toggleView() { if (frontView == null || backView == null) { return; } if (isFlipped) { frontView.setVisibility(View.VISIBLE); backView.setVisibility(View.GONE); } else { frontView.setVisibility(View.GONE); backView.setVisibility(View.VISIBLE); } isFlipped = !isFlipped; } public void setOnFlipListener(OnFlipListener listener) { this.listener = listener; } public void reset() { isFlipped = false; direction = Direction.DOWN; frontView.setVisibility(View.VISIBLE); backView.setVisibility(View.GONE); } public void toggleUp() { direction = Direction.UP; startAnimation(); } public void toggleDown() { direction = Direction.DOWN; startAnimation(); } public void toggleLeft() { direction = Direction.LEFT; startAnimation(); } public void toggleRight() { direction = Direction.RIGHT; startAnimation(); } public void startAnimation() { animator.setVisibilitySwapped(); startAnimation(animator); } @Override public void onAnimationStart(Animation animation) { if (listener != null) { listener.onFlipStart(this); } } @Override public void onAnimationEnd(Animation animation) { if (listener != null) { listener.onFlipEnd(this); } if (direction == Direction.UP) direction = Direction.DOWN; if (direction == Direction.DOWN) direction = Direction.UP; if (direction == Direction.LEFT) direction = Direction.RIGHT; if (direction == Direction.RIGHT) direction = Direction.LEFT; } @Override public void onAnimationRepeat(Animation animation) { } public void setAnimationListener(Animation.AnimationListener listener) { animator.setAnimationListener(listener); } @Override public void onClick(View view) { toggleDown(); } @Override public void onSwipeLeft() { toggleLeft(); } @Override public void onSwipeRight() { toggleRight(); } @Override public void onSwipeUp() { toggleUp(); } @Override public void onSwipeDown() { toggleDown(); } private enum Direction { UP, DOWN, LEFT, RIGHT } public interface OnFlipListener { void onFlipStart(FlipLayout view); void onFlipEnd(FlipLayout view); } public class FlipAnimator extends Animation { private static final float EXPERIMENTAL_VALUE = 50.f; private Camera camera; private float centerX; private float centerY; private boolean visibilitySwapped; public FlipAnimator() { setFillAfter(true); } public void setVisibilitySwapped() { visibilitySwapped = false; } @Override public void initialize(int width, int height, int parentWidth, int parentHeight) { super.initialize(width, height, parentWidth, parentHeight); camera = new Camera(); this.centerX = width / 2; this.centerY = height / 2; } @Override protected void applyTransformation(float interpolatedTime, Transformation t) { // Angle around the y-axis of the rotation at the given time. It is // calculated both in radians and in the equivalent degrees. final double radians = Math.PI * interpolatedTime; float degrees = (float) (180.0 * radians / Math.PI); if (direction == Direction.UP || direction == Direction.LEFT) { degrees = -degrees; } // Once we reach the midpoint in the animation, we need to hide the // source view and show the destination view. We also need to change // the angle by 180 degrees so that the destination does not come in // flipped around. This is the main problem with SDK sample, it does // not // do this. if (interpolatedTime >= 0.5f) { switch (direction) { case LEFT: case UP: degrees += 180.f; break; case RIGHT: case DOWN: degrees -= 180.f; break; } if (!visibilitySwapped) { toggleView(); visibilitySwapped = true; } } final Matrix matrix = t.getMatrix(); camera.save(); //you can delete this line, it move camera a little far from view and get back camera.translate(0.0f, 0.0f, (float) (EXPERIMENTAL_VALUE * Math.sin(radians))); switch (direction) { case DOWN: case UP: camera.rotateX(degrees); camera.rotateY(0); break; case LEFT: case RIGHT: camera.rotateY(degrees); camera.rotateX(0); break; } camera.rotateZ(0); camera.getMatrix(matrix); camera.restore(); matrix.preTranslate(-centerX, -centerY); matrix.postTranslate(centerX, centerY); } } }