package pl.verdigo.libraries.drawer;
import static android.view.ViewGroup.LayoutParams.FILL_PARENT;
import pl.verdigo.libraries.drawer.internal.IDrawerProxy;
import pl.verdigo.libraries.drawer.internal.LeftDrawer;
import pl.verdigo.libraries.drawer.internal.RightDrawer;
import android.app.ActionBar;
import android.app.Activity;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.view.Gravity;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.view.Window;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import com.actionbarsherlock.R;
import com.actionbarsherlock.internal.nineoldandroids.animation.Animator;
import com.actionbarsherlock.internal.nineoldandroids.animation.Animator.AnimatorListener;
import com.actionbarsherlock.internal.nineoldandroids.animation.ObjectAnimator;
// TODO: Auto-generated Javadoc
/**
* Drawer implementation. TODO create documentation in JavaDoc here.
*
* @author Lukasz Milewski <lukasz.milewski@gmail.com>
*/
public abstract class Drawer implements OnClickListener, OnTouchListener
{
/** The Constant ORIENTATION_BOTH. */
public static final int ORIENTATION_BOTH = 0;
/** The Constant ORIENTATION_POTRAIT. */
public static final int ORIENTATION_POTRAIT = 1;
/** The Constant ORIENTATION_LANDSCAPE. */
public static final int ORIENTATION_LANDSCAPE = 2;
/** The Constant DRAWER_CONTENT_MOVE_PROPORTION. */
protected static final int DRAWER_CONTENT_MOVE_PROPORTION = 5;
/** The Constant DEFAULT_DURATION. */
private static final long DEFAULT_DURATION = 250;
/** The Constant DRAWER_SHADOW_WIDTH. */
private static final int DRAWER_SHADOW_WIDTH = 8;
/** The m activity width. */
protected int mActivityWidth;
/** The m allow close on touch. */
private boolean mAllowCloseOnTouch = true;
/** The m animation duration. */
private long mAnimationDuration = DEFAULT_DURATION;
/** The m animation enabled. */
private boolean mAnimationEnabled = true;
/** The m context. */
private Context mContext;
/** The m decor view. */
private FrameLayout mDecorView;
/** The m deviation. */
protected int mDeviation = 0;
/** The m drawer. */
protected View mDrawer;
/** The m drawer activity. */
protected View mDrawerActivity;
/** The m drawer clickable. */
private ImageView mDrawerClickable;
/** The m drawer content. */
protected LinearLayout mDrawerContent;
/** The m drawer listener. */
private DrawerListener mDrawerListener;
/** The m drawer shadow. */
protected View mDrawerShadow;
/** The m fade drawer. */
protected boolean mFadeDrawer = false;
/** The m drawer width portrait. */
private float mDrawerWidthPortrait = -48;
/** The m drawer width land. */
private float mDrawerWidthLand = -40;
/** The m layout. */
private int mLayout;
/** The m movable. */
protected boolean mMovable = true;
/** The m moved. */
protected boolean mMoved = false;
/** The m move drawer. */
protected boolean mMoveDrawer = false;
/** The m moved beyond margin. */
protected boolean mMovedBeyondMargin = false;
/** The m moved position. */
protected int mMovedPosition = 0;
/** The m need to reinitialize. */
private boolean mNeedToReinitialize = false;
/** The m parent window. */
private Window mParentWindow;
/** The m reuse. */
private boolean mReuse = false;
/** The m scale drawer. */
protected boolean mScaleDrawer = false;
/** The m shadow width. */
protected int mShadowWidth = DRAWER_SHADOW_WIDTH;
/** The m transform3d drawer. */
protected boolean mTransform3dDrawer = false;
/** The m visible. */
private boolean mVisible = false;
/**
* Creates the left drawer.
*
* @param context the context
* @param layout the layout
* @return the drawer
*/
public static Drawer createLeftDrawer(Context context, int layout)
{
return new LeftDrawer(context, layout);
}
/**
* Creates the right drawer.
*
* @param context the context
* @param layout the layout
* @return the drawer
*/
public static Drawer createRightDrawer(Context context, int layout)
{
return new RightDrawer(context, layout);
}
/**
* Creates {@link Drawer} object.
*
* @param context Context
* @param layout Layout to inflate into {@link Drawer}
*/
protected Drawer(Context context, int layout)
{
mContext = context;
mLayout = layout;
if (mContext instanceof Activity)
{
mParentWindow = ((Activity) mContext).getWindow();
}
}
/**
* Calculates duration of animation. When {@link Drawer} is in state of
* moving, duration of animation will be calculated based on the position.
*
* @param show Animation for showing/hiding
* @return time in milliseconds
*/
private long calculateDuration(boolean show)
{
if (mMoved)
{
float ratio = (float) mMovedPosition / getDrawerWidth();
long duration = Math.round(mAnimationDuration * (show ? 1F - ratio : ratio));
return duration >= 0 ? duration : -1 * duration;
}
return mAnimationDuration;
}
/**
* Cancel (dismiss) {@link Drawer}. If animation is enabled it will be
* played.
*/
public final void cancel()
{
if (!mVisible)
{
return;
}
if (mDrawerListener != null)
{
mDrawerListener.onDrawerBeforeCancel();
}
mVisible = false;
mDrawerClickable.setOnClickListener(null);
mDrawerClickable.setOnTouchListener(null);
if (mAnimationEnabled)
{
cancelWithAnimation();
}
else
{
removeDrawer();
}
}
/**
* Plays cancel animation. It slides {@link Drawer} from right to left. If
* drawer is currently moved by touch event, animation will start from
* current position and will be appropriately shortened.
*/
private void cancelWithAnimation()
{
final int start = mMoved ? mMovedPosition : getTargetPosition();
ObjectAnimator anim = ObjectAnimator.ofInt(createDrawerProxy(), "position", start, 0);
anim.setInterpolator(new DecelerateInterpolator());
anim.setDuration(calculateDuration(false));
anim.addListener(new AnimatorListener()
{
public void onAnimationStart(Animator animation)
{
}
public void onAnimationEnd(Animator animation)
{
removeDrawer();
}
public void onAnimationCancel(Animator animation)
{
}
public void onAnimationRepeat(Animator animation)
{
}
});
anim.start();
}
/**
* Cancel (dismiss) {@link Drawer} without animation. This is equivalent to
* <pre>
* boolean previous = drawer.isAnimationEnabled();
* drawer.setAnimationEnabled(false);
* drawer.cancel();
* drawer.setAnimationEnabled(previous);
* </pre>
*/
public final void cancelWithoutAnimation()
{
boolean animationEnabled = mAnimationEnabled;
mAnimationEnabled = false;
cancel();
mAnimationEnabled = animationEnabled;
}
/**
* Creates DrawerProxy object.
*
* @return DrawerProxy object
*/
protected abstract IDrawerProxy createDrawerProxy();
/**
* Gets the target position.
*
* @return the target position
*/
protected abstract int getTargetPosition();
/**
* Returns {@link Drawer} width. Value provided by developer is in DPI,
* therefore it has to be calculated into pixels.
*
* @return Drawer width in pixels
*/
protected int getDrawerWidth()
{
float density = mContext.getResources().getDisplayMetrics().density;
float width = mDrawerWidthPortrait;
if (mContext.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE)
{
width = mDrawerWidthLand;
}
if (width < 0)
{
width = (mActivityWidth / density) - Math.abs(width);
}
//FloatMath.ceil
return (int) Math.ceil(width * density);
}
/**
* Checks if is right drawer.
*
* @return true, if is right drawer
*/
protected abstract boolean isRightDrawer();
/**
* Initialize {@link Drawer}. Drawer's layout is injected into
* most-top-level {@link FrameLayout) possible, this gives us an ability to
* move {@link ActionBar}. Clickable {@link ImageView} is also created to
* handle click and touch events.
*/
@SuppressWarnings("deprecation")
public void init()
{
mDecorView = (FrameLayout) mParentWindow.getDecorView();
mDrawerActivity = (ViewGroup) mDecorView.getChildAt(0);
mActivityWidth = mDrawerActivity.getWidth();
mDrawer = View.inflate(mContext, R.layout.drawer_placeholder, null);
mDrawer.setPadding(0, mDrawerActivity.getPaddingTop(), 0, mDrawerActivity.getPaddingBottom());
mDecorView.addView(mDrawer);
mDrawerShadow = new LinearLayout(mContext);
mDrawerShadow.setVisibility(View.GONE);
mDecorView.addView(mDrawerShadow);
ImageView shadow = new ImageView(mContext);
shadow.setLayoutParams(new LinearLayout.LayoutParams(mShadowWidth, FILL_PARENT));
shadow.setBackgroundResource(isRightDrawer() ? R.drawable.drawer_shadow_right : R.drawable.drawer_shadow_left);
((LinearLayout) mDrawerShadow).addView(shadow);
mDrawerClickable = new ImageView(mContext);
mDrawerClickable.setVisibility(View.GONE);
mDecorView.addView(mDrawerClickable);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(FILL_PARENT, FILL_PARENT);
mDrawerContent = (LinearLayout) findViewById(R.id.drawer_content);
mDrawerContent.addView(View.inflate(mContext, mLayout, null), lp);
updateDrawerWidth();
}
/**
* Is closing {link Drawer} on touch events allowed. Used primarily with Bezel Swipe.
*
* @return Boolean
*/
public boolean isAllowCloseOnTouch()
{
return mAllowCloseOnTouch;
}
/**
* Is animation currently enabled.
*
* @return Boolean
*/
public boolean isAnimationEnabled()
{
return mAnimationEnabled;
}
/**
* Is fading {@link Drawer} enabled.
*
* @return Boolean
*/
public boolean isFadeDrawer()
{
return mFadeDrawer;
}
/**
* Is {@link Drawer} movable with touch events.
*
* @return Boolean
*/
public boolean isMovable()
{
return mMovable;
}
/**
* Is moving content of {@link Drawer} enabled.
*
* @return Boolean
*/
public boolean isMoveDrawer()
{
return mMoveDrawer;
}
/**
* Is scaling of {@link Drawer} enabled.
*
* @return Boolean
*/
public boolean isScaleDrawer()
{
return mScaleDrawer;
}
/**
* Is 3d transformation of {@link Drawer} enabled.
*
* @return Boolean
*/
public boolean isTransform3dDrawer()
{
return mTransform3dDrawer;
}
/**
* Is drawer currently visible. If it is not visible, internal objects are
* destroyed and {@link Drawer} should not be used.
*
* @return Boolean
*/
public boolean isVisible()
{
return mVisible;
}
/**
* Handles click event.
*
* @param view Clicked view
*/
public void onClick(View view)
{
if (view == mDrawerClickable)
{
cancel();
}
}
/**
* Removed {@link Drawer} from parent {@link Activity}.
*/
public void removeDrawer()
{
mMovedBeyondMargin = false;
mMovedPosition = 0;
mDeviation = 0;
mMoved = false;
ViewGroup.LayoutParams lp = ((ViewGroup) mDrawerActivity).getLayoutParams();
lp.width = -1;
mDrawerActivity.setLayoutParams(lp);
mDrawerActivity.setPadding(0, mDrawerActivity.getPaddingTop(), mDrawerActivity.getPaddingRight(), mDrawerActivity.getPaddingBottom());
mDrawerActivity.requestLayout();
mDrawerClickable.setVisibility(View.GONE);
mDrawerShadow.setVisibility(View.GONE);
if (mDrawerListener != null)
{
mDrawerListener.onDrawerAfterCancel();
}
if (mReuse)
{
ViewGroup.LayoutParams params = mDrawer.getLayoutParams();
params.width = 0;
mDrawer.setLayoutParams(params);
return;
}
mDecorView.removeView(mDrawer);
mDecorView.removeView(mDrawerClickable);
mDecorView.removeView(mDrawerShadow);
mNeedToReinitialize = true;
}
/**
* Sets whether closing {@link Drawer} is available on touch events.
*
* @param allowCloseOnTouch true/false
*/
public void setAllowCloseOnTouch(boolean allowCloseOnTouch)
{
mAllowCloseOnTouch = allowCloseOnTouch;
}
/**
* Sets duration of {@link Drawer} open/close animation.
*
* @param animationDuration Duration in milliseconds
*/
public void setAnimationDuration(long animationDuration)
{
mAnimationDuration = animationDuration;
}
/**
* Sets whether animation should be enabled or disabled.
*
* @param animationEnabled true/false
*/
public void setAnimationEnabled(boolean animationEnabled)
{
mAnimationEnabled = animationEnabled;
}
/**
* Sets background {@link Drawable} on {@link Drawer}. This method should be
* used instead of background on provided layout.
*
* @param drawable Drawable
*/
@SuppressWarnings("deprecation")
public void setBackgroundDrawable(Drawable drawable)
{
mDrawerContent.setBackgroundDrawable(drawable);
mDrawerContent.setPadding(0, 0, 0, 0);
}
/**
* Sets background {@link Resource} on {@link Drawer}. This method should be
* used instead of background on provided layout.
*
* @param drawable Resource
*/
public void setBackgroundResource(int drawable)
{
mDrawerContent.setBackgroundResource(drawable);
mDrawerContent.setPadding(0, 0, 0, 0);
}
/**
* Sets {@link DrawerListener} listener.
*
* @param listener New listener
*/
public void setDrawerListener(DrawerListener listener)
{
mDrawerListener = listener;
}
/**
* Sets (@link Drawer) width for portrait and landscape. Negative value will
* result in subtracting width from entire activity width
*
* @param drawerWidth Drawer width in DIPs
*/
public void setDrawerWidth(float drawerWidth)
{
setDrawerWidth(ORIENTATION_BOTH, drawerWidth);
}
/**
* Sets (@link Drawer) width for portrait and/or landscape. Negative value will
* result in subtracting width from entire activity width
*
* @param type Type
* @param drawerWidth Drawer width in DIPs
*/
public void setDrawerWidth(int type, float drawerWidth)
{
if (type == ORIENTATION_BOTH || type == ORIENTATION_POTRAIT)
{
mDrawerWidthPortrait = drawerWidth;
}
if (type == ORIENTATION_BOTH || type == ORIENTATION_LANDSCAPE)
{
mDrawerWidthLand = drawerWidth;
}
}
/**
* Sets whether {@link Drawer} will fade to black on animation.
*
* @param fadeDrawer true/false
*/
public void setFadeDrawer(boolean fadeDrawer)
{
mFadeDrawer = fadeDrawer;
}
/**
* Sets whether {@link Drawer} is movable by touch events.
*
* @param movable true/false
*/
public void setMovable(boolean movable)
{
mMovable = movable;
}
/**
* Sets whether content of {@link Drawer} is move during animation.
*
* @param moveDrawer true/false
*/
public void setMoveDrawer(boolean moveDrawer)
{
mMoveDrawer = moveDrawer;
}
/**
* Sets whether content of {@link Drawer} will be reused or not.
*
* @param reuse true/false
*/
public void setReuse(boolean reuse)
{
mReuse = reuse;
}
/**
* Sets whether content of {@link Drawer} is scaled during animation.
*
* @param scaleDrawer true/false
*/
public void setScaleDrawer(boolean scaleDrawer)
{
this.mScaleDrawer = scaleDrawer;
}
/**
* Sets shadow width.
*
* @param shadowWidth width
*/
public void setShadowWidth(int shadowWidth)
{
this.mShadowWidth = shadowWidth;
}
/**
* Sets whether content of {@link Drawer} is 3d transformed during animation.
* This method is available from Android 3.0 (API level 11). On lower version
* nothing will happen.
*
* @param transform3dDrawer true/false
*/
public void setTransform3dDrawer(boolean transform3dDrawer)
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
{
this.mTransform3dDrawer = transform3dDrawer;
}
}
/**
* Shows {@link Drawer}. If animation is enabled it will be played.
*/
public void show()
{ //isVisible()
if (mVisible)
{
return;
}
if (mNeedToReinitialize)
{
init();
}
if (mDrawerListener != null)
{
mDrawerListener.onDrawerBeforeShow();
}
mMoved = false;
mMovedPosition = 0;
mVisible = true;
if (mAnimationEnabled)
{
showWithAnimation();
}
else
{
IDrawerProxy proxy = createDrawerProxy();
proxy.setPosition(getDrawerWidth());
updateDrawerClickable();
updateDrawerShadow();
finishShowing();
}
}
/**
* Show with touch.
*
* @param deviation the deviation
*/
void showWithTouch(int deviation)
{ //isVisible()
if (mVisible)
{
return;
}
if (mNeedToReinitialize)
{
init();
}
mMoved = true;
mMovedPosition = 0;
mVisible = true;
mDeviation = deviation;
IDrawerProxy proxy = createDrawerProxy();
proxy.setPosition(0);
updateDrawerClickable();
updateDrawerShadow();
}
/**
* Plays show animation. It slides {@link Drawer} from left to right. If
* drawer is currently moved by touch event, animation will start from
* current position and will be appropriately shortened. If this is first
* time, clickable {@link ImageView} will be correctly positioned and
* visible.
*/
protected void showWithAnimation()
{
final int start = mMoved ? mMovedPosition : 0;
boolean decelerate = mMoved && !mAllowCloseOnTouch;
ObjectAnimator anim = ObjectAnimator.ofInt(createDrawerProxy(), "position", start, getTargetPosition());
anim.setInterpolator(decelerate ? new DecelerateInterpolator() : new AccelerateInterpolator());
anim.setDuration(calculateDuration(true));
anim.addListener(new AnimatorListener()
{
public void onAnimationStart(Animator animation)
{
}
public void onAnimationRepeat(Animator animation)
{
}
public void onAnimationEnd(Animator animation)
{
finishShowing();
}
public void onAnimationCancel(Animator animation)
{
finishShowing();
}
});
anim.start();
if (mMoved)
{
return;
}
updateDrawerClickable();
updateDrawerShadow();
}
/**
* Find view by id.
*
* @param viewId the view id
* @return the view
*/
public View findViewById(int viewId)
{
return mDrawer.findViewById(viewId);
}
/**
* Finish showing.
*/
void finishShowing()
{
if (mDrawerListener != null)
{
mDrawerListener.onDrawerAfterShow();
}
}
/**
* Updates clickable {@link ImageView} - position and visibility.
*/
private void updateDrawerClickable()
{
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mDrawerClickable.getLayoutParams();
lp.gravity = (isRightDrawer() ? Gravity.LEFT : Gravity.RIGHT) | Gravity.FILL_VERTICAL;
lp.width = mActivityWidth - getDrawerWidth();
mDrawerClickable.setLayoutParams(lp);
mDrawerClickable.setVisibility(View.VISIBLE);
mDrawerClickable.setClickable(true);
mDrawerClickable.setOnClickListener(this);
mDrawerClickable.setOnTouchListener(this);
}
/**
* Updates shadow - position and visibility.
*/
@SuppressWarnings("deprecation")
private void updateDrawerShadow()
{
View shadow = ((LinearLayout) mDrawerShadow).getChildAt(0);
shadow.setLayoutParams(new LinearLayout.LayoutParams(mShadowWidth, FILL_PARENT));
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mDrawerShadow.getLayoutParams();
lp.gravity = Gravity.FILL_VERTICAL;
lp.width = 0;
mDrawerShadow.setLayoutParams(lp);
mDrawerShadow.setVisibility(View.VISIBLE);
}
/**
* Updates {@link Drawer} width. It is based on {@link Activity} minus
* margin provided in constructor.
*/
private void updateDrawerWidth()
{
mDrawer.getLayoutParams().width = 0;
findViewById(R.id.drawer_content).getLayoutParams().width = getDrawerWidth();
}
}