package pl.polidea.androidflip3d; import java.util.Arrays; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.drawable.AnimationDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.util.Log; import android.view.View; import android.view.animation.AccelerateInterpolator; import android.view.animation.Animation.AnimationListener; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.ImageView.ScaleType; /** * Views that can be swapped on click in 3D animation mode. * */ public class Flip3DView extends FrameLayout { private static final int IMAGE_VIEW_ID = 1; private static final String TAG = Flip3DView.class.getSimpleName(); private static final int DEFAULT_ANIMATION_LENGTH = 500; private static final int DEFAULT_INTERNAL_PADDING = 0; private static final int DEFAULT_INTERNAL_MARGIN = 0; private static final int DEFAULT_FRONT_TO_BACK = RotationDirection.ROTATE_LEFT; private static final int DEFAULT_BACK_TO_FRONT = RotationDirection.ROTATE_RIGHT; private static final ScaleType DEFAULT_SCALE_TYPE = ScaleType.FIT_CENTER; private final FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams( LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT); private final View.OnClickListener clickHidingListener = new View.OnClickListener() { @Override public void onClick(final View view) { Log.d(TAG, "Click ignored: " + ViewIndex.getViewType(view.getId())); } }; private AnimationListener finishFlippingListener; private final FrameLayout[] views = new FrameLayout[ViewIndex.VIEW_NUMBER]; private int internalPadding = DEFAULT_INTERNAL_PADDING; private long animationLength = DEFAULT_ANIMATION_LENGTH; private int frontToBack = DEFAULT_FRONT_TO_BACK; private int backToFront = DEFAULT_BACK_TO_FRONT; private int internalMargin = DEFAULT_INTERNAL_MARGIN; private ScaleType imageScaleType = DEFAULT_SCALE_TYPE; private final OnClickListener listenerDelegate = new OnClickListener() { @Override public void onClick(final View v) { if (listener != null) { listener.onClick(v); } } }; private OnClickListener listener; /** * Sets amount of internal padding. * * @param internalPadding * internal padding in pixels */ public synchronized void setInternalPadding(final int internalPadding) { this.internalPadding = internalPadding; } /** * Set image scale type. * * @param imageScaleType * image scale type */ public synchronized void setImageScaleType(final ScaleType imageScaleType) { this.imageScaleType = imageScaleType; } /** * Sets amount of internal margin. * * @param internalMargin * internal padding in pixels */ public synchronized void setInternalMargin(final int internalMargin) { this.internalMargin = internalMargin; } /** * Sets length of animation flippingx. * * @param animationLength * length of animation in milliseconds */ public synchronized void setAnimationLength(final long animationLength) { this.animationLength = animationLength; } public Flip3DView(final Context context, final AttributeSet attrs, final int defStyle) { super(context, attrs, defStyle); final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Flip3DView); try { parsePaddingAttributes(a); initializeViews(); parseImageAttributes(a); parseOtherAttributes(a); } finally { a.recycle(); } } public Flip3DView(final Context context, final AttributeSet attrs) { this(context, attrs, 0); } public Flip3DView(final Context context) { super(context); initializeViews(); } private synchronized void parsePaddingAttributes(final TypedArray a) { internalPadding = a.getDimensionPixelSize( R.styleable.Flip3DView_internal_padding, DEFAULT_INTERNAL_PADDING); internalMargin = a .getDimensionPixelSize(R.styleable.Flip3DView_internal_margin, DEFAULT_INTERNAL_MARGIN); layoutParams.setMargins(internalMargin, internalMargin, internalMargin, internalMargin); } private synchronized void parseImageAttributes(final TypedArray a) { final Drawable front = a.getDrawable(R.styleable.Flip3DView_src_front); if (front != null) { setImageDrawable(ViewIndex.FRONT_VIEW, front); } final Drawable back = a.getDrawable(R.styleable.Flip3DView_src_back); if (back != null) { setImageDrawable(ViewIndex.BACK_VIEW, back); } } private synchronized void parseOtherAttributes(final TypedArray a) { animationLength = a.getInt( R.styleable.Flip3DView_animation_length_millis, DEFAULT_ANIMATION_LENGTH); frontToBack = a.getInt( R.styleable.Flip3DView_front_to_back_flip_direction, DEFAULT_FRONT_TO_BACK); backToFront = a.getInt( R.styleable.Flip3DView_back_to_front_flip_direction, DEFAULT_BACK_TO_FRONT); } /** * Sets rotation mode for front to back change. * * <pre> * Accepted values are: * 0 - LEFT * 1 - RIGHT * </pre> * * @param direction * rotation direction */ public synchronized void setFrontToBack(final int direction) { this.frontToBack = direction; } /** * Sets rotation mode for back to front change. * * <pre> * Accepted values are: * 0 - LEFT * 1 - RIGHT * </pre> * * @param direction * rotation direction */ public synchronized void setBackToFront(final int direction) { this.backToFront = direction; } /** * Sets layout parameters of the image view depending on the type of the * Drawable. * * @param view * the image view */ private synchronized void setViewParameters(final View view) { view.setLayoutParams(layoutParams); } /** * Sets layout parameters of the image view depending on the type of the * Drawable. * * @param imageView * the image view * @param drawable * drawable to set */ private synchronized void setImageParameters(final ImageView imageView, final Drawable drawable) { imageView.setImageDrawable(drawable); } /** * Sets view to the given side. * * @param viewSide * side of the view * @param view * view to set */ private synchronized void setView(final int viewSide, final FrameLayout view) { if (this.views[viewSide] != null) { this.removeView(this.views[viewSide]); } this.views[viewSide] = view; view.setId(viewSide); setViewParameters(view); this.addView(view); } /** * Sets front view on the control. * * @param viewFront * front view */ public final synchronized void setViewFront(final View viewFront) { setInternalView(ViewIndex.FRONT_VIEW, viewFront); } /** * Sets back view on the control. * * @param viewBack * back view */ public final synchronized void setViewBack(final View viewBack) { setInternalView(ViewIndex.BACK_VIEW, viewBack); } private synchronized void setInternalView(final int viewSide, final View view) { final FrameLayout frame = (FrameLayout) inflate(getContext(), R.layout.view_layout_with_padding, null); frame.setPadding(internalPadding, internalPadding, internalPadding, internalPadding); frame.addView(view); setView(viewSide, frame); } private synchronized void setImageDrawable(final int viewSide, final Drawable drawable) { final FrameLayout frame = (FrameLayout) inflate(getContext(), R.layout.image_layout_with_padding, null); frame.setPadding(internalPadding, internalPadding, internalPadding, internalPadding); final ImageView imageView = new ImageView(this.getContext()) { @Override public void onWindowFocusChanged(final boolean hasWindowFocus) { super.onWindowFocusChanged(hasWindowFocus); if (getDrawable() instanceof AnimationDrawable) { final AnimationDrawable a = (AnimationDrawable) getDrawable(); if (hasWindowFocus) { a.start(); } else { a.stop(); } } } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); if (getDrawable() instanceof AnimationDrawable) { final AnimationDrawable a = (AnimationDrawable) getDrawable(); a.stop(); } } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); if (getDrawable() instanceof AnimationDrawable) { final AnimationDrawable a = (AnimationDrawable) getDrawable(); a.start(); } } }; imageView.setId(IMAGE_VIEW_ID); imageView.setLayoutParams(new FrameLayout.LayoutParams( LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT)); imageView.setScaleType(imageScaleType); Log.v(TAG, "Setting scale to " + imageScaleType); frame.addView(imageView); setImageParameters(imageView, drawable); setView(viewSide, frame); } /** * Sets image drawable for front. * * @param drawable * drawable for front. */ public synchronized void setImageFrontDrawable(final Drawable drawable) { setImageDrawable(ViewIndex.FRONT_VIEW, drawable); } /** * Sets image drawable for back. * * @param drawable * drawable for back. */ public synchronized void setImageBackDrawable(final Drawable drawable) { setImageDrawable(ViewIndex.BACK_VIEW, drawable); } private synchronized void initializeViews() { setImageDrawable(ViewIndex.FRONT_VIEW, new ColorDrawable(Color.BLUE)); setImageDrawable(ViewIndex.BACK_VIEW, new ColorDrawable(Color.RED)); setImageDrawable(ViewIndex.FOREGROUND_VIEW, new ColorDrawable( Color.TRANSPARENT)); } protected synchronized void initializeViewState(final int currentViewIndex) { views[ViewIndex.FOREGROUND_VIEW].setVisibility(View.INVISIBLE); setViewClickability(ViewIndex.FOREGROUND_VIEW, false); views[ViewIndex.BACK_VIEW] .setVisibility(currentViewIndex == ViewIndex.BACK_VIEW ? View.VISIBLE : View.INVISIBLE); setViewClickability(ViewIndex.BACK_VIEW, currentViewIndex == ViewIndex.BACK_VIEW); views[ViewIndex.FRONT_VIEW] .setVisibility(currentViewIndex == ViewIndex.FRONT_VIEW ? View.VISIBLE : View.INVISIBLE); setViewClickability(ViewIndex.FRONT_VIEW, currentViewIndex == ViewIndex.FRONT_VIEW); } public synchronized void setViewClickability(final int viewIndex, final boolean enable) { final FrameLayout frameLayout = views[viewIndex]; frameLayout.setClickable(true); if (enable) { if (viewIndex == ViewIndex.FOREGROUND_VIEW) { // always ignore clicks on foreground view frameLayout.setOnClickListener(clickHidingListener); } else { frameLayout.setOnClickListener(listenerDelegate); } frameLayout.bringToFront(); frameLayout.requestFocus(); } else { frameLayout.setOnClickListener(clickHidingListener); } } public void requestViewIndexFocus(final int viewIndex) { views[viewIndex].requestFocus(); } /** * Starts rotation according to direction. * * @param currentViewIndex * starting index of view which to animate */ public synchronized void startRotation(final int currentViewIndex) { final int direction = currentViewIndex == ViewIndex.FRONT_VIEW ? frontToBack : backToFront; setFlipping(true); final float centerX = getWidth() / 2.0f; final float centerY = getHeight() / 2.0f; final Flip3DAnimation rotation = new Flip3DAnimation(0, RotationDirection.getMultiplier(direction) * 90, centerX, centerY); rotation.setDuration(animationLength); rotation.setFillAfter(true); rotation.setInterpolator(new AccelerateInterpolator()); rotation.setAnimationListener(new GetToTheMiddleOfFlipping( currentViewIndex, views, animationLength, direction, finishFlippingListener)); views[currentViewIndex].startAnimation(rotation); } public synchronized void setFlipping(final boolean flipping) { final View foregroundView = views[ViewIndex.FOREGROUND_VIEW]; if (flipping) { // make sure the view is taking over all the clicks setViewClickability(ViewIndex.FOREGROUND_VIEW, true); foregroundView.setVisibility(VISIBLE); foregroundView.bringToFront(); } else { setViewClickability(ViewIndex.FOREGROUND_VIEW, false); foregroundView.setVisibility(GONE); } } @Override public synchronized void setOnClickListener(final OnClickListener l) { super.setOnClickListener(l); this.listener = l; } /** * Listener to listen for flipping finished. * * @param finishFlippingListener * listener to listen to finish flipping */ public synchronized void setFinishFlippingListener( final AnimationListener finishFlippingListener) { this.finishFlippingListener = finishFlippingListener; } @Override public synchronized String toString() { return "Flip3DView [layoutParams=" + layoutParams + ", clickHidingListener=" + clickHidingListener + ", finishFlippingListener=" + finishFlippingListener + ", views=" + Arrays.toString(views) + ", internalPadding=" + internalPadding + ", animationLength=" + animationLength + ", frontToBack=" + frontToBack + ", backToFront=" + backToFront + ", listenerDelegate=" + listenerDelegate + ", listener=" + listener + "]"; } /** * Cancels all animations running for the view. */ public synchronized void clearAllAnimations() { for (int i = 0; i < ViewIndex.VIEW_NUMBER; i++) { if (views[i] != null) { views[i].clearAnimation(); } } } /** * Returns view of a given index. * * @param viewIndex * index of a view. * @return the view. */ public synchronized FrameLayout getView(final int viewIndex) { return views[viewIndex]; } }