package com.adamnickle.deck; import android.content.Context; import android.graphics.Bitmap; import android.os.Handler; import android.os.Looper; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.animation.Animation; import android.view.animation.AnimationSet; import android.view.animation.AnimationUtils; import android.widget.ImageView; import com.adamnickle.deck.Game.Card; import com.adamnickle.deck.Game.CardCollection; import com.adamnickle.deck.Game.DeckSettings; import com.squareup.picasso.Picasso; import java.io.IOException; import java.util.Comparator; import java.util.HashMap; import ru.noties.debug.Debug; public class PlayingCardView extends ImageView { private static final float MINIMUM_VELOCITY = 50.0f; private static final HashMap< Float, Bitmap > sScaledBackBitmaps = new HashMap< Float, Bitmap >(); private static int sCardBackResource = 0; private Bitmap mCardBitmap; private final Card mCard; private String mOwnerID; private boolean mFaceUp; private boolean mResetCard; private float mVelocityX; private float mVelocityY; private long mLastUpdate; private float mScale; private boolean mAttachedToWindow; private boolean mBitmapLoaded; private boolean mIsSpreading; protected final AnimationSet mToMiddle; protected final AnimationSet mFromMiddle; public PlayingCardView( final Context context, String ownerID, Card card, float scale ) { super( context ); mCard = card; mOwnerID = ownerID; mFaceUp = false; mResetCard = true; mVelocityX = 0.0f; mVelocityY = 0.0f; mScale = Math.round( scale * 100.0f ) / 100.0f; mAttachedToWindow = false; mBitmapLoaded = false; mIsSpreading = false; this.setLayoutParams( new CardDisplayLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT ) ); this.setScaleType( ScaleType.CENTER_CROP ); boolean hasBackground = false; if( mOwnerID.startsWith( TableFragment.DISCARD_PILE_ID_PREFIX ) ) { this.setBackgroundResource( R.drawable.discard_pile_outline ); hasBackground = true; } else if( mOwnerID.startsWith( TableFragment.DRAW_PILE_ID_PREFIX ) ) { this.setBackgroundResource( R.drawable.draw_pile_outline ); hasBackground = true; } if( hasBackground ) { final int cardOutlineWidth = getResources().getDimensionPixelSize( R.dimen.card_outline_width ); final int cardOutlineSpace = getResources().getDimensionPixelSize( R.dimen.card_outline_space ); final int padding = cardOutlineWidth + cardOutlineSpace; this.setPadding( padding, padding, padding, padding ); } mToMiddle = (AnimationSet) AnimationUtils.loadAnimation( getContext(), R.anim.flip_first_half ); mFromMiddle = (AnimationSet) AnimationUtils.loadAnimation( getContext(), R.anim.flip_last_half ); mToMiddle.getAnimations().get( 0 ).setAnimationListener( new Animation.AnimationListener() { @Override public void onAnimationEnd( Animation animation ) { mFaceUp = !mFaceUp; PlayingCardView.this.setCardBitmap(); PlayingCardView.this.clearAnimation(); PlayingCardView.this.startAnimation( mFromMiddle ); } @Override public void onAnimationStart( Animation animation ) { } @Override public void onAnimationRepeat( Animation animation ) { } } ); new Thread() { @Override public void run() { try { final int cardWidth = (int) ( getResources().getDimensionPixelSize( R.dimen.card_width ) * mScale ); final int cardHeight = (int) ( getResources().getDimensionPixelSize( R.dimen.card_height ) * mScale ); synchronized( PlayingCardView.class ) { final int cardBackResource = DeckSettings.getCardBackResource( getContext() ); if( cardBackResource != sCardBackResource ) { sCardBackResource = cardBackResource; for( Bitmap bitmap : sScaledBackBitmaps.values() ) { bitmap.recycle(); } sScaledBackBitmaps.clear(); } if( !sScaledBackBitmaps.containsKey( mScale ) ) { final Bitmap bitmap = Picasso .with( getContext() ) .load( sCardBackResource ) .resize( cardWidth, cardHeight ) .get(); sScaledBackBitmaps.put( mScale, bitmap ); } } mCardBitmap = Picasso .with( getContext() ) .load( mCard.getResource() ) .resize( cardWidth, cardHeight ) .get(); mBitmapLoaded = true; new Handler( Looper.getMainLooper() ).post( new Runnable() { @Override public void run() { setCardBitmap(); } } ); } catch( IOException e ) { e.printStackTrace(); } } }.start(); } public PlayingCardView( Context context, String ownerID, Card card, float x, float y ) { this( context, ownerID, card ); this.setX( x ); this.setY( y ); } public PlayingCardView( Context context, String ownerID, Card card ) { this( context, ownerID, card, 1.0f ); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); mAttachedToWindow = true; } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); mAttachedToWindow = false; } private Runnable mFlingUpdater = new Runnable() { @Override public void run() { synchronized( PlayingCardView.this ) { final long now = System.currentTimeMillis(); final float elapsedTime = now - mLastUpdate; final int dx = (int) ( ( mVelocityX * elapsedTime ) / 1000.0f ); final int dy = (int) ( ( mVelocityY * elapsedTime ) / 1000.0f ); PlayingCardView.this.offsetLeftAndRight( dx ); PlayingCardView.this.offsetTopAndBottom( dy ); CardDisplayLayout.LayoutParams lp = (CardDisplayLayout.LayoutParams) PlayingCardView.this.getLayoutParams(); lp.Left += dx; lp.Top += dy; mVelocityX *= 0.99f; mVelocityY *= 0.99f; final CardDisplayLayout parent = (CardDisplayLayout) PlayingCardView.this.getParent(); if( parent == null ) { PlayingCardView.this.stop(); } final CardDisplayLayout.Side side = PlayingCardView.this.hasHitWall(); if( side != CardDisplayLayout.Side.NONE && parent.childShouldBounce( PlayingCardView.this, side ) ) { switch( side ) { case LEFT: mVelocityX = -mVelocityX; PlayingCardView.this.setX( 0 ); break; case RIGHT: mVelocityX = -mVelocityX; PlayingCardView.this.offsetLeftAndRight( parent.getRight() - getRight() ); break; case TOP: mVelocityY = -mVelocityY; PlayingCardView.this.setY( 0 ); break; case BOTTOM: mVelocityY = -mVelocityY; PlayingCardView.this.offsetTopAndBottom( parent.getBottom() - getBottom() ); break; } } final CardDisplayLayout.Side offSide = PlayingCardView.this.getWallSlidPast(); if( offSide != CardDisplayLayout.Side.NONE ) { Debug.d( offSide.toString() ); PlayingCardView.this.stop(); parent.childViewOffScreen( PlayingCardView.this, offSide ); } if( Math.abs( mVelocityX ) < MINIMUM_VELOCITY ) { mVelocityX = 0.0f; } if( Math.abs( mVelocityY ) < MINIMUM_VELOCITY ) { mVelocityY = 0.0f; } if( mVelocityX != 0.0f && mVelocityY != 0.0f ) { mLastUpdate = now; PlayingCardView.this.postDelayed( this, 10 ); } else if( mVelocityX == 0.0f && mVelocityY == 0.0f ) { PlayingCardView.this.stop(); } } } }; public synchronized void onTouched() { this.stop(); } public synchronized void fling( float xVelocity, float yVelocity ) { mVelocityX = Math.min( xVelocity / 2.0f, ViewConfiguration.get( getContext() ).getScaledMaximumFlingVelocity() ); mVelocityY = Math.min( yVelocity / 2.0f, ViewConfiguration.get( getContext() ).getScaledMaximumFlingVelocity() ); mLastUpdate = System.currentTimeMillis(); this.post( mFlingUpdater ); } public void spreadCard() { setIsSpreading( true ); this.fling( (float) Math.random() * 40000.0f - 20000.0f, (float) Math.random() * 40000.0f - 20000.0f ); } public void setIsSpreading( boolean isSpreading ) { mIsSpreading = isSpreading; } public boolean isSpreading() { return mIsSpreading; } public synchronized void stop() { mVelocityX = 0.0f; mVelocityY = 0.0f; mIsSpreading = false; } @Override public void setX( float x ) { mResetCard = false; this.offsetLeftAndRight( (int) ( x - this.getLeft() ) ); CardDisplayLayout.LayoutParams lp = (CardDisplayLayout.LayoutParams) this.getLayoutParams(); lp.Left = (int) x; } @Override public void setY( float y ) { mResetCard = false; this.offsetTopAndBottom( (int) ( y - this.getTop() ) ); CardDisplayLayout.LayoutParams lp = (CardDisplayLayout.LayoutParams) this.getLayoutParams(); lp.Top = (int) y; } @Override protected void onLayout( boolean changed, int left, int top, int right, int bottom ) { super.onLayout( changed, left, top, right, bottom ); if( mResetCard && mBitmapLoaded ) { mResetCard = false; reset(); } } private CardDisplayLayout.Side getWallSlidPast() { final ViewGroup parent = (ViewGroup) this.getParent(); if( parent == null ) { return CardDisplayLayout.Side.NONE; } else if( this.getRight() < 0 ) { return CardDisplayLayout.Side.LEFT; } else if( this.getBottom() < 0 ) { return CardDisplayLayout.Side.TOP; } else if( this.getLeft() > parent.getWidth() ) { return CardDisplayLayout.Side.RIGHT; } else if( this.getTop() > parent.getHeight() ) { return CardDisplayLayout.Side.BOTTOM; } else { return CardDisplayLayout.Side.NONE; } } private CardDisplayLayout.Side hasHitWall() { final ViewGroup parent = (ViewGroup) this.getParent(); if( parent == null ) { return CardDisplayLayout.Side.NONE; } else if( this.getLeft() <= 0 ) { return CardDisplayLayout.Side.LEFT; } else if( this.getTop() <= 0 ) { return CardDisplayLayout.Side.TOP; } else if( this.getRight() >= parent.getWidth() ) { return CardDisplayLayout.Side.RIGHT; } else if( this.getBottom() >= parent.getHeight() ) { return CardDisplayLayout.Side.BOTTOM; } else { return CardDisplayLayout.Side.NONE; } } public String getOwnerID() { return mOwnerID; } public Card getCard() { return mCard; } public void flip() { this.flip( !mFaceUp ); } public void flip( boolean faceUp ) { this.flip( faceUp, true ); } public void flip( boolean faceUp, boolean animate ) { if( faceUp != mFaceUp ) { if( mAttachedToWindow && animate ) { this.startAnimation( mToMiddle ); } else { mFaceUp = !mFaceUp; setCardBitmap(); } } } private void setCardBitmap() { if( mFaceUp ) { if( mCardBitmap != null ) { this.setImageBitmap( mCardBitmap ); } } else { if( sScaledBackBitmaps.containsKey( mScale ) ) { this.setImageBitmap( sScaledBackBitmaps.get( mScale ) ); } } } public synchronized void reset() { this.stop(); final ViewGroup parent = (ViewGroup) this.getParent(); final int parentWidth = parent.getWidth(); final int parentHeight = parent.getHeight(); final int width = this.getWidth(); final int height = this.getHeight(); final float horizontalOffsetRange = parentWidth * 0.2f; final float verticalOffsetRange = parentHeight * 0.2f; final float randomXOffset = (float) ( horizontalOffsetRange * Math.random() - horizontalOffsetRange / 2.0f ); final float randomYOffset = (float) ( verticalOffsetRange * Math.random() - verticalOffsetRange / 2.0f ); final float newX = ( parentWidth - width ) / 2.0f + randomXOffset; final float newY = ( parentHeight - height ) / 2.0f + randomYOffset; this.setX( newX ); this.setY( newY ); } public boolean contains( float x, float y ) { return this.getLeft() <= x && x <= this.getRight() && this.getTop() <= y && y <= this.getBottom(); } public static class PlayingCardViewComparator implements Comparator< PlayingCardView > { private final Card.CardComparator mCardComparator; public PlayingCardViewComparator( CardCollection.SortingType sortType ) { mCardComparator = new Card.CardComparator( sortType ); } @Override public int compare( PlayingCardView cardView, PlayingCardView cardView2 ) { return mCardComparator.compare( cardView.getCard(), cardView2.getCard() ); } } }