summaryrefslogtreecommitdiff
path: root/samples/devbytes/animation/CardFlip/src/com/example/android/cardflip/CardView.java
diff options
context:
space:
mode:
Diffstat (limited to 'samples/devbytes/animation/CardFlip/src/com/example/android/cardflip/CardView.java')
-rw-r--r--samples/devbytes/animation/CardFlip/src/com/example/android/cardflip/CardView.java329
1 files changed, 329 insertions, 0 deletions
diff --git a/samples/devbytes/animation/CardFlip/src/com/example/android/cardflip/CardView.java b/samples/devbytes/animation/CardFlip/src/com/example/android/cardflip/CardView.java
new file mode 100644
index 000000000..9a3ab7126
--- /dev/null
+++ b/samples/devbytes/animation/CardFlip/src/com/example/android/cardflip/CardView.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.cardflip;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.Keyframe;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.drawable.BitmapDrawable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+
+/**
+ * This CardView object is a view which can flip horizontally about its edges,
+ * as well as rotate clockwise or counter-clockwise about any of its corners. In
+ * the middle of a flip animation, this view darkens to imitate a shadow-like effect.
+ *
+ * The key behind the design of this view is the fact that the layout parameters and
+ * the animation properties of this view are updated and reset respectively after
+ * every single animation. Therefore, every consecutive animation that this
+ * view experiences is completely independent of what its prior state was.
+ */
+public class CardView extends ImageView {
+
+ enum Corner {
+ TOP_LEFT,
+ TOP_RIGHT,
+ BOTTOM_LEFT,
+ BOTTOM_RIGHT
+ }
+
+ private final int CAMERA_DISTANCE = 8000;
+ private final int MIN_FLIP_DURATION = 300;
+ private final int VELOCITY_TO_DURATION_CONSTANT = 15;
+ private final int MAX_FLIP_DURATION = 700;
+ private final int ROTATION_PER_CARD = 2;
+ private final int ROTATION_DELAY_PER_CARD = 50;
+ private final int ROTATION_DURATION = 2000;
+ private final int ANTIALIAS_BORDER = 1;
+
+ private BitmapDrawable mFrontBitmapDrawable, mBackBitmapDrawable, mCurrentBitmapDrawable;
+
+ private boolean mIsFrontShowing = true;
+ private boolean mIsHorizontallyFlipped = false;
+
+ private Matrix mHorizontalFlipMatrix;
+
+ private CardFlipListener mCardFlipListener;
+
+ public CardView(Context context) {
+ super(context);
+ init(context);
+ }
+
+ public CardView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context);
+ }
+
+ /** Loads the bitmap drawables used for the front and back for this card.*/
+ public void init(Context context) {
+ mHorizontalFlipMatrix = new Matrix();
+
+ setCameraDistance(CAMERA_DISTANCE);
+
+ mFrontBitmapDrawable = bitmapWithBorder((BitmapDrawable)getResources()
+ .getDrawable(R.drawable.red));
+ mBackBitmapDrawable = bitmapWithBorder((BitmapDrawable) getResources()
+ .getDrawable(R.drawable.blue));
+
+ updateDrawableBitmap();
+ }
+
+ /**
+ * Adding a 1 pixel transparent border around the bitmap can be used to
+ * anti-alias the image as it rotates.
+ */
+ private BitmapDrawable bitmapWithBorder(BitmapDrawable bitmapDrawable) {
+ Bitmap bitmapWithBorder = Bitmap.createBitmap(bitmapDrawable.getIntrinsicWidth() +
+ ANTIALIAS_BORDER * 2, bitmapDrawable.getIntrinsicHeight() + ANTIALIAS_BORDER * 2,
+ Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmapWithBorder);
+ canvas.drawBitmap(bitmapDrawable.getBitmap(), ANTIALIAS_BORDER, ANTIALIAS_BORDER, null);
+ return new BitmapDrawable(getResources(), bitmapWithBorder);
+ }
+
+ /** Initiates a horizontal flip from right to left. */
+ public void flipRightToLeft(int numberInPile, int velocity) {
+ setPivotX(0);
+ flipHorizontally(numberInPile, false, velocity);
+ }
+
+ /** Initiates a horizontal flip from left to right. */
+ public void flipLeftToRight(int numberInPile, int velocity) {
+ setPivotX(getWidth());
+ flipHorizontally(numberInPile, true, velocity);
+ }
+
+ /**
+ * Animates a horizontal (about the y-axis) flip of this card.
+ * @param numberInPile Specifies how many cards are underneath this card in the new
+ * pile so as to properly adjust its position offset in the stack.
+ * @param clockwise Specifies whether the horizontal animation is 180 degrees
+ * clockwise or 180 degrees counter clockwise.
+ */
+ public void flipHorizontally (int numberInPile, boolean clockwise, int velocity) {
+ toggleFrontShowing();
+
+ PropertyValuesHolder rotation = PropertyValuesHolder.ofFloat(View.ROTATION_Y,
+ clockwise ? 180 : -180);
+
+ PropertyValuesHolder xOffset = PropertyValuesHolder.ofFloat(View.TRANSLATION_X,
+ numberInPile * CardFlip.CARD_PILE_OFFSET);
+ PropertyValuesHolder yOffset = PropertyValuesHolder.ofFloat(View.TRANSLATION_Y,
+ numberInPile * CardFlip.CARD_PILE_OFFSET);
+
+ ObjectAnimator cardAnimator = ObjectAnimator.ofPropertyValuesHolder(this, rotation,
+ xOffset, yOffset);
+ cardAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ if (valueAnimator.getAnimatedFraction() >= 0.5) {
+ updateDrawableBitmap();
+ }
+ }
+ });
+
+ Keyframe shadowKeyFrameStart = Keyframe.ofFloat(0, 0);
+ Keyframe shadowKeyFrameMid = Keyframe.ofFloat(0.5f, 1);
+ Keyframe shadowKeyFrameEnd = Keyframe.ofFloat(1, 0);
+ PropertyValuesHolder shadowPropertyValuesHolder = PropertyValuesHolder.ofKeyframe
+ ("shadow", shadowKeyFrameStart, shadowKeyFrameMid, shadowKeyFrameEnd);
+ ObjectAnimator colorizer = ObjectAnimator.ofPropertyValuesHolder(this,
+ shadowPropertyValuesHolder);
+
+ mCardFlipListener.onCardFlipStart();
+ AnimatorSet set = new AnimatorSet();
+ int duration = MAX_FLIP_DURATION - Math.abs(velocity) / VELOCITY_TO_DURATION_CONSTANT;
+ duration = duration < MIN_FLIP_DURATION ? MIN_FLIP_DURATION : duration;
+ set.setDuration(duration);
+ set.playTogether(cardAnimator, colorizer);
+ set.setInterpolator(new AccelerateDecelerateInterpolator());
+ set.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ toggleIsHorizontallyFlipped();
+ updateDrawableBitmap();
+ updateLayoutParams();
+ mCardFlipListener.onCardFlipEnd();
+ }
+ });
+ set.start();
+ }
+
+ /** Darkens this ImageView's image by applying a shadow color filter over it. */
+ public void setShadow(float value) {
+ int colorValue = (int)(255 - 200 * value);
+ setColorFilter(Color.rgb(colorValue, colorValue, colorValue),
+ android.graphics.PorterDuff.Mode.MULTIPLY);
+ }
+
+ public void toggleFrontShowing() {
+ mIsFrontShowing = !mIsFrontShowing;
+ }
+
+ public void toggleIsHorizontallyFlipped() {
+ mIsHorizontallyFlipped = !mIsHorizontallyFlipped;
+ invalidate();
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ mHorizontalFlipMatrix.setScale(-1, 1, w / 2, h / 2);
+ }
+
+ /**
+ * Scale the canvas horizontally about its midpoint in the case that the card
+ * is in a horizontally flipped state.
+ */
+ @Override
+ protected void onDraw(Canvas canvas) {
+ if (mIsHorizontallyFlipped) {
+ canvas.concat(mHorizontalFlipMatrix);
+ }
+ super.onDraw(canvas);
+ }
+
+ /**
+ * Updates the layout parameters of this view so as to reset the rotationX and
+ * rotationY parameters, and remain independent of its previous position, while
+ * also maintaining its current position in the layout.
+ */
+ public void updateLayoutParams () {
+ RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) getLayoutParams();
+
+ params.leftMargin = (int)(params.leftMargin + ((Math.abs(getRotationY()) % 360) / 180) *
+ (2 * getPivotX () - getWidth()));
+
+ setRotationX(0);
+ setRotationY(0);
+
+ setLayoutParams(params);
+ }
+
+ /**
+ * Toggles the visible bitmap of this view between its front and back drawables
+ * respectively.
+ */
+ public void updateDrawableBitmap () {
+ mCurrentBitmapDrawable = mIsFrontShowing ? mFrontBitmapDrawable : mBackBitmapDrawable;
+ setImageDrawable(mCurrentBitmapDrawable);
+ }
+
+ /**
+ * Sets the appropriate translation of this card depending on how many cards
+ * are in the pile underneath it.
+ */
+ public void updateTranslation (int numInPile) {
+ setTranslationX(CardFlip.CARD_PILE_OFFSET * numInPile);
+ setTranslationY(CardFlip.CARD_PILE_OFFSET * numInPile);
+ }
+
+ /**
+ * Returns a rotation animation which rotates this card by some degree about
+ * one of its corners either in the clockwise or counter-clockwise direction.
+ * Depending on how many cards lie below this one in the stack, this card will
+ * be rotated by a different amount so all the cards are visible when rotated out.
+ */
+ public ObjectAnimator getRotationAnimator (int cardFromTop, Corner corner,
+ boolean isRotatingOut, boolean isClockwise) {
+ rotateCardAroundCorner(corner);
+ int rotation = cardFromTop * ROTATION_PER_CARD;
+
+ if (!isClockwise) {
+ rotation = -rotation;
+ }
+
+ if (!isRotatingOut) {
+ rotation = 0;
+ }
+
+ return ObjectAnimator.ofFloat(this, View.ROTATION, rotation);
+ }
+
+ /**
+ * Returns a full rotation animator which rotates this card by 360 degrees
+ * about one of its corners either in the clockwise or counter-clockwise direction.
+ * Depending on how many cards lie below this one in the stack, a different start
+ * delay is applied to the animation so the cards don't all animate at once.
+ */
+ public ObjectAnimator getFullRotationAnimator (int cardFromTop, Corner corner,
+ boolean isClockwise) {
+ final int currentRotation = (int)getRotation();
+
+ rotateCardAroundCorner(corner);
+ int rotation = 360 - currentRotation;
+ rotation = isClockwise ? rotation : -rotation;
+
+ ObjectAnimator animator = ObjectAnimator.ofFloat(this, View.ROTATION, rotation);
+
+ animator.setStartDelay(ROTATION_DELAY_PER_CARD * cardFromTop);
+ animator.setDuration(ROTATION_DURATION);
+
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ setRotation(currentRotation);
+ }
+ });
+
+ return animator;
+ }
+
+ /**
+ * Sets the appropriate pivot of this card so that it can be rotated about
+ * any one of its four corners.
+ */
+ public void rotateCardAroundCorner(Corner corner) {
+ switch(corner) {
+ case TOP_LEFT:
+ setPivotX(0);
+ setPivotY(0);
+ break;
+ case TOP_RIGHT:
+ setPivotX(getWidth());
+ setPivotY(0);
+ break;
+ case BOTTOM_LEFT:
+ setPivotX(0);
+ setPivotY(getHeight());
+ break;
+ case BOTTOM_RIGHT:
+ setPivotX(getWidth());
+ setPivotY(getHeight());
+ break;
+ }
+ }
+
+ public void setCardFlipListener(CardFlipListener cardFlipListener) {
+ mCardFlipListener = cardFlipListener;
+ }
+
+}