package pl.polidea.androidflip3d;
import java.util.List;
import android.util.Log;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
/**
* Stores state of the flip3D view. It is stored so that the view can actually
* be detached from the information - and the views to be reused in classes like
* gallery, list etc. - generally everywhere where views can be reused.
*
*/
public class Flip3DViewState {
private static final String TAG = Flip3DViewState.class.getSimpleName();
private final View.OnClickListener clickListener = new View.OnClickListener() {
@Override
public void onClick(final View view) {
targetViewIndex = ViewIndex.getTheOtherViewIndex(currentViewIndex);
startRotationToTheOtherSide(true, true);
}
};
private final int id;
public Flip3DViewState(final int id) {
this.id = id;
}
/**
* Listener serving end of animation.
*
*/
public class FinishFlipping implements AnimationListener {
private final int newStateViewIndex;
public FinishFlipping(final int targetViewIndex) {
this.newStateViewIndex = targetViewIndex;
}
@Override
public void onAnimationStart(final Animation animation) {
// do nothing
}
@Override
public void onAnimationEnd(final Animation animation) {
oneSideFlippingEnded(newStateViewIndex);
}
@Override
public void onAnimationRepeat(final Animation animation) {
// do nothing
}
}
/**
* View attached to the state.
*/
private Flip3DView view;
/**
* Listener to handle the flipping events.
*
*/
public interface Flip3DViewListener {
/**
* Reported when flipping is started. Either on click or when forced
* (and previously set on the other side). It will not be triggered when
* forced and it is either on correct side or still flipping.
*
* @param viewState
* viewstate originating the event
* @param startingSide
* starting side of flipping (0 - FRONT, 1- BACK)
* @param manuallyTriggered
* true when view was actually clicked and not forced to flip
*/
void onStartedFlipping(Flip3DViewState viewState, int startingSide, boolean manuallyTriggered);
/**
* Reported when flipping is successfully finished. It will not be
* triggered when flipping is being forced - so when we will flip back
* immediately because we are coming back. It will also be called when
* animation is canceled because view is moved out of the visible area.
*
* @param viewState
* view originating the event
* @param endingSide
* ending side of flipping (0 - FRONT, 1- BACK)
* @param manuallyTriggered
* true when view was actually clicked and not forced to flip
* by some other means. Note that the manuallyTriggered
* status can be cancelled if the view is forced before it
* manages to flip
*/
void onFinishedFlipping(Flip3DViewState viewState, int endingSide, boolean manuallyTriggered);
}
private int currentViewIndex = ViewIndex.FRONT_VIEW;
private int targetViewIndex = ViewIndex.FRONT_VIEW;
private boolean flipping = false;
private boolean beingForced = false;
private Flip3DViewListener flip3dViewListener = null;
public synchronized Flip3DViewListener getFlip3dViewListener() {
return flip3dViewListener;
}
public synchronized void setFlip3dViewListener(final Flip3DViewListener flip3dViewListener) {
this.flip3dViewListener = flip3dViewListener;
}
public synchronized int getCurrentViewIndex() {
return currentViewIndex;
}
public synchronized int getTargetViewIndex() {
return targetViewIndex;
}
public synchronized boolean isFlipping() {
return flipping;
}
public synchronized boolean isBeingForced() {
return beingForced;
}
public synchronized void setCurrentViewIndex(final int currentViewIndex) {
this.currentViewIndex = currentViewIndex;
}
public synchronized void setTargetViewIndex(final int targetViewIndex) {
this.targetViewIndex = targetViewIndex;
}
public synchronized void setFlipping(final boolean flipping) {
this.flipping = flipping;
if (view != null) {
view.setFlipping(flipping);
}
}
public synchronized void setBeingForced(final boolean beingForced) {
this.beingForced = beingForced;
}
public synchronized Flip3DView getView() {
return view;
}
public synchronized void setView(final Flip3DView view) {
if (this.view != null) {
this.currentViewIndex = targetViewIndex;
if (flipping) {
setStateAfterFlippingFinished(!beingForced);
}
}
this.targetViewIndex = currentViewIndex;
this.flipping = false;
this.beingForced = false;
this.view = view;
if (view != null) {
view.setOnClickListener(clickListener);
view.initializeViewState(currentViewIndex);
}
}
/**
* Force specified side of the view with animation (only does animation if
* needed - if view is already in desired state, does nothing).
*
* @param viewIndex
* index to which
*/
public synchronized void forceFlipTo(final int viewIndex) {
if (beingForced) {
Log.d(TAG, id + ": Already forced to " + ViewIndex.getViewType(targetViewIndex));
return;
}
Log.d(TAG, id + ": Forcing move to " + ViewIndex.getViewType(viewIndex));
targetViewIndex = viewIndex;
if (flipping) {
Log.d(TAG, id + ": Already flipping. Set target to " + ViewIndex.getViewType(viewIndex));
beingForced = true;
} else if (currentViewIndex == targetViewIndex) {
Log.d(TAG, id + ": Already in the right state: " + ViewIndex.getViewType(viewIndex));
} else {
Log.d(TAG, id + ": Not flipping but need to flip back to " + ViewIndex.getViewType(viewIndex));
beingForced = true;
startRotationToTheOtherSide(true, false);
}
}
/**
* Animation or other means should use it in order to notify that flipping
* has finished.
*
* @param newStateIndex
* index to which we just flipped
*/
private synchronized void oneSideFlippingEnded(final int newStateIndex) {
Log.d(TAG, id + ": Ended flipping to " + ViewIndex.getViewType(newStateIndex));
currentViewIndex = newStateIndex;
setFlipping(false);
if (beingForced) {
if (targetViewIndex == newStateIndex || view == null) {
beingForced = false;
setStateAfterFlippingFinished(false);
} else {
Log.d(TAG, id + ": Flipping back, forcibly to " + ViewIndex.getViewType(targetViewIndex));
startRotationToTheOtherSide(false, false);
}
} else {
setStateAfterFlippingFinished(true);
}
}
private static String getMode(final boolean manuallyTriggered) {
return manuallyTriggered ? "<Manual>" : "<Forced>";
}
/**
* Set state after we know that flipping has actually been finished
* (finally).
*
* @param manuallyTriggered
* if the flip has been manually triggered
*/
private synchronized void setStateAfterFlippingFinished(final boolean manuallyTriggered) {
Log.d(TAG,
id + ": " + getMode(manuallyTriggered) + ": Flipping finished to "
+ ViewIndex.getViewType(targetViewIndex));
currentViewIndex = targetViewIndex;
if (view != null) {
view.requestViewIndexFocus(currentViewIndex);
view.setViewClickability(currentViewIndex, true);
}
if (flip3dViewListener != null) {
flip3dViewListener.onFinishedFlipping(this, currentViewIndex, manuallyTriggered);
}
}
private synchronized void startRotationToTheOtherSide(final boolean notifyListener, final boolean manuallyTriggered) {
setFlipping(true);
final int theOtherSide = ViewIndex.getTheOtherViewIndex(currentViewIndex);
if (notifyListener && flip3dViewListener != null) {
flip3dViewListener.onStartedFlipping(this, currentViewIndex, manuallyTriggered);
}
if (view == null) {
Log.d(TAG, "View is null so finishing immediately");
oneSideFlippingEnded(ViewIndex.getTheOtherViewIndex(targetViewIndex));
} else {
Log.d(TAG, "View is not null so setting clikability now");
view.setViewClickability(currentViewIndex, false);
view.setFinishFlippingListener(new FinishFlipping(theOtherSide));
view.startRotation(currentViewIndex);
}
}
/**
* Sets listener to fliping events.
*
* @param flip3dViewListener
* listener.
*/
public synchronized void setFlip3DViewListener(final Flip3DViewListener flip3dViewListener) {
this.flip3dViewListener = flip3dViewListener;
}
/**
* Get Id of the control.
*
* @return
*/
public int getId() {
return id;
}
/**
* Attaches or re-attaches view to the state.
*
* @param i
* position
* @param viewStates
* list of states
* @param view
* view to attach
* @param <State>
* state type
* @return view state with the view attached - view should be set in it
* after preparing.
*/
public static <State extends Flip3DViewState> State attachViewToViewState(final int i,
final List<State> viewStates, final Flip3DView view) {
final int oldViewId = view.getId();
if (oldViewId >= 0 && oldViewId < viewStates.size()) {
final State oldState = viewStates.get(oldViewId);
oldState.setView(view); // detach view from old state
}
return viewStates.get(i);
}
/**
* Detaches view in case it is owned by the state.
*
* @param view
* view to detach
*/
public synchronized void detachView(final Flip3DView view) {
if (flipping) {
view.clearAllAnimations();
}
}
}