diff options
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.java | 329 |
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; + } + +} |
