package org.holoeverywhere.slidingmenu;
import java.lang.reflect.Method;
import org.holoeverywhere.LayoutInflater;
import org.holoeverywhere.app.Activity;
import org.holoeverywhere.slidingmenu.CustomViewAbove.OnPageChangeListener;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.Display;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.Window;
import android.view.WindowManager;
import android.widget.RelativeLayout;
public class SlidingMenu extends RelativeLayout {
/**
* The Interface CanvasTransformer.
*/
public interface CanvasTransformer {
/**
* Transform canvas.
*
* @param canvas the canvas
* @param percentOpen the percent open
*/
public void transformCanvas(Canvas canvas, float percentOpen);
}
/**
* The listener interface for receiving onClosed events. The class that is
* interested in processing a onClosed event implements this interface, and
* the object created with that class is registered with a component using
* the component's <code>addOnClosedListener<code> method. When
* the onClosed event occurs, that object's appropriate
* method is invoked.
*
* @see OnClosedEvent
*/
public interface OnClosedListener {
/**
* On closed.
*/
public void onClosed();
}
/**
* The listener interface for receiving onClose events. The class that is
* interested in processing a onClose event implements this interface, and
* the object created with that class is registered with a component using
* the component's <code>addOnCloseListener<code> method. When
* the onClose event occurs, that object's appropriate
* method is invoked.
*
* @see OnCloseEvent
*/
public interface OnCloseListener {
/**
* On close.
*/
public void onClose();
}
/**
* The listener interface for receiving onOpened events. The class that is
* interested in processing a onOpened event implements this interface, and
* the object created with that class is registered with a component using
* the component's <code>addOnOpenedListener<code> method. When
* the onOpened event occurs, that object's appropriate
* method is invoked.
*
* @see OnOpenedEvent
*/
public interface OnOpenedListener {
/**
* On opened.
*/
public void onOpened();
}
/**
* The listener interface for receiving onOpen events. The class that is
* interested in processing a onOpen event implements this interface, and
* the object created with that class is registered with a component using
* the component's <code>addOnOpenListener<code> method. When
* the onOpen event occurs, that object's appropriate
* method is invoked
*/
public interface OnOpenListener {
/**
* On open.
*/
public void onOpen();
}
public static class SavedState extends BaseSavedState {
public static final Parcelable.Creator<SavedState> CREATOR =
new Parcelable.Creator<SavedState>() {
@Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
private final boolean mBehindShowing;
private SavedState(Parcel in) {
super(in);
mBehindShowing = in.readByte() != 0;
}
public SavedState(Parcelable superState, boolean isBehindShowing) {
super(superState);
mBehindShowing = isBehindShowing;
}
/*
* (non-Javadoc)
* @see android.view.AbsSavedState#writeToParcel(android.os.Parcel, int)
*/
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeByte(mBehindShowing ? (byte) 1 : 0);
}
}
/**
* Constant value for use with setTouchModeAbove(). Allows the SlidingMenu
* to be opened with a swipe gesture anywhere on the screen
*/
public static final int TOUCHMODE_FULLSCREEN = 1;
/**
* Constant value for use with setTouchModeAbove(). Allows the SlidingMenu
* to be opened with a swipe gesture on the screen's margin
*/
public static final int TOUCHMODE_MARGIN = 0;
/**
* Constant value for use with setTouchModeAbove(). Denies the SlidingMenu
* to be opened with a swipe gesture
*/
public static final int TOUCHMODE_NONE = 2;
/**
* Attach a given SlidingMenu to a given Activity
*
* @param activity the Activity to attach to
* @param sm the SlidingMenu to be attached
* @param slidingTitle whether the title is slid with the above view
*/
public static void attachSlidingMenu(Activity activity, SlidingMenu sm, boolean slidingTitle) {
if (sm.getParent() != null) {
throw new IllegalStateException("SlidingMenu cannot be attached to another view when" +
" calling the static method attachSlidingMenu");
}
if (slidingTitle) {
// get the window background
TypedArray a = activity.getTheme().obtainStyledAttributes(new int[] {
android.R.attr.windowBackground
});
int background = a.getResourceId(0, 0);
a.recycle();
// move everything into the SlidingMenu
ViewGroup decor = (ViewGroup) activity.getWindow().getDecorView();
ViewGroup decorChild = (ViewGroup) decor.getChildAt(0);
decor.removeAllViews();
// save ActionBar themes that have transparent assets
decorChild.setBackgroundResource(background);
sm.setContent(decorChild);
decor.addView(sm);
} else {
// take the above view out of
ViewGroup content = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT);
View above = content.getChildAt(0);
content.removeAllViews();
sm.setContent(above);
content.addView(sm, android.view.ViewGroup.LayoutParams.MATCH_PARENT,
android.view.ViewGroup.LayoutParams.MATCH_PARENT);
}
}
private OnCloseListener mCloseListener;
private OnOpenListener mOpenListener;
private CustomViewAbove mViewAbove;
private CustomViewBehind mViewBehind;
/**
* Instantiates a new SlidingMenu.
*
* @param context the associated Context
*/
public SlidingMenu(Context context) {
this(context, null);
}
/**
* Instantiates a new SlidingMenu.
*
* @param context the associated Context
* @param attrs the attrs
*/
public SlidingMenu(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
/**
* Instantiates a new SlidingMenu.
*
* @param context the associated Context
* @param attrs the attrs
* @param defStyle the def style
*/
public SlidingMenu(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
LayoutParams behindParams = new LayoutParams(
android.view.ViewGroup.LayoutParams.MATCH_PARENT,
android.view.ViewGroup.LayoutParams.MATCH_PARENT);
mViewBehind = new CustomViewBehind(context);
addView(mViewBehind, behindParams);
LayoutParams aboveParams = new LayoutParams(
android.view.ViewGroup.LayoutParams.MATCH_PARENT,
android.view.ViewGroup.LayoutParams.MATCH_PARENT);
aboveParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
mViewAbove = new CustomViewAbove(context);
addView(mViewAbove, aboveParams);
// register the CustomViewBehind2 with the CustomViewAbove
mViewAbove.setCustomViewBehind(mViewBehind);
mViewBehind.setCustomViewAbove(mViewAbove);
mViewAbove.setOnPageChangeListener(new OnPageChangeListener() {
public static final int POSITION_CLOSE = 1;
public static final int POSITION_OPEN = 0;
@Override
public void onPageScrolled(int position, float positionOffset,
int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
if (position == POSITION_OPEN && mOpenListener != null) {
mOpenListener.onOpen();
} else if (position == POSITION_CLOSE && mCloseListener != null) {
mCloseListener.onClose();
}
}
});
// now style everything!
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SlidingMenu);
// set the above and behind views if defined in xml
int viewAbove = ta.getResourceId(R.styleable.SlidingMenu_viewAbove, -1);
if (viewAbove != -1) {
setContent(viewAbove);
}
int viewBehind = ta.getResourceId(R.styleable.SlidingMenu_viewBehind, -1);
if (viewBehind != -1) {
setMenu(viewBehind);
}
int touchModeAbove = ta.getInt(R.styleable.SlidingMenu_touchModeAbove, TOUCHMODE_MARGIN);
setTouchModeAbove(touchModeAbove);
int touchModeBehind = ta.getInt(R.styleable.SlidingMenu_touchModeBehind, TOUCHMODE_MARGIN);
setTouchModeBehind(touchModeBehind);
int offsetBehind = (int) ta.getDimension(R.styleable.SlidingMenu_behindOffset, -1);
int widthBehind = (int) ta.getDimension(R.styleable.SlidingMenu_behindWidth, -1);
if (offsetBehind != -1 && widthBehind != -1) {
throw new IllegalStateException(
"Cannot set both behindOffset and behindWidth for a SlidingMenu");
} else if (offsetBehind != -1) {
setBehindOffset(offsetBehind);
} else if (widthBehind != -1) {
setBehindWidth(widthBehind);
} else {
setBehindOffset(0);
}
float scrollOffsetBehind = ta.getFloat(R.styleable.SlidingMenu_behindScrollScale, 0.33f);
setBehindScrollScale(scrollOffsetBehind);
int shadowRes = ta.getResourceId(R.styleable.SlidingMenu_shadowDrawable, -1);
if (shadowRes != -1) {
setShadowDrawable(shadowRes);
}
int shadowWidth = (int) ta.getDimension(R.styleable.SlidingMenu_shadowWidth, 0);
setShadowWidth(shadowWidth);
boolean fadeEnabled = ta.getBoolean(R.styleable.SlidingMenu_behindFadeEnabled, true);
setFadeEnabled(fadeEnabled);
float fadeDeg = ta.getFloat(R.styleable.SlidingMenu_behindFadeDegree, 0.66f);
setFadeDegree(fadeDeg);
boolean selectorEnabled = ta.getBoolean(R.styleable.SlidingMenu_selectorEnabled, false);
setSelectorEnabled(selectorEnabled);
int selectorRes = ta.getResourceId(R.styleable.SlidingMenu_selectorDrawable, -1);
if (selectorRes != -1) {
setSelectorDrawable(selectorRes);
}
ta.recycle();
}
/*
* (non-Javadoc)
* @see android.view.ViewGroup#fitSystemWindows(android.graphics.Rect)
*/
@Override
protected boolean fitSystemWindows(Rect insets) {
int leftPadding = getPaddingLeft() + insets.left;
int rightPadding = getPaddingRight() + insets.right;
int topPadding = insets.top;
int bottomPadding = insets.bottom;
setPadding(leftPadding, topPadding, rightPadding, bottomPadding);
return super.fitSystemWindows(insets);
}
/**
* Gets the behind offset.
*
* @return The margin on the right of the screen that the behind view
* scrolls to
*/
public int getBehindOffset() {
return ((RelativeLayout.LayoutParams) mViewBehind.getLayoutParams()).rightMargin;
}
/**
* Gets the behind scroll scale.
*
* @return The scale of the parallax scroll
*/
public float getBehindScrollScale() {
return mViewAbove.getScrollScale();
}
/**
* Gets the touch mode above.
*
* @return the touch mode above
*/
public int getTouchModeAbove() {
return mViewAbove.getTouchMode();
}
/**
* Returns whether the menu can be swiped to close
*
* @return the touch mode behind, either {@link #TOUCHMODE_MARGIN
* TOUCHMODE_MARGIN}, {@link #TOUCHMODE_FULLSCREEN
* TOUCHMODE_FULLSCREEN}, or {@link #TOUCHMODE_NONE TOUCHMODE_NONE}
*/
public int getTouchModeBehind() {
return mViewBehind.getTouchMode();
}
/**
* Checks if is the behind view showing.
*
* @return Whether or not the behind view is showing
*/
public boolean isBehindShowing() {
return mViewAbove.getCurrentItem() == 0;
}
/**
* Checks if is sliding enabled.
*
* @return true, if is sliding enabled
*/
public boolean isSlidingEnabled() {
return mViewAbove.isSlidingEnabled();
}
/*
* (non-Javadoc)
* @see android.view.View#onRestoreInstanceState(android.os.Parcelable)
*/
@Override
protected void onRestoreInstanceState(Parcelable state) {
if (!(state instanceof SavedState)) {
super.onRestoreInstanceState(state);
return;
}
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
if (ss.mBehindShowing) {
showBehind();
} else {
showAbove();
}
}
/*
* (non-Javadoc)
* @see android.view.View#onSaveInstanceState()
*/
@Override
protected Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState, isBehindShowing());
return ss;
}
/**
* Sets the above offset.
*
* @param i the new above offset, in pixels
*/
public void setAboveOffset(int i) {
// RelativeLayout.LayoutParams params =
// ((RelativeLayout.LayoutParams)mViewAbove.getLayoutParams());
// int bottom = params.bottomMargin;
// int top = params.topMargin;
// int right = params.rightMargin;
// params.setMargins(i, top, right, bottom);
// this.requestLayout();
mViewAbove.setAboveOffset(i);
}
/**
* Sets the above offset.
*
* @param resID The dimension resource id to be set as the above offset.
*/
public void setAboveOffsetRes(int resID) {
int i = (int) getContext().getResources().getDimension(resID);
setAboveOffset(i);
}
/**
* Sets the behind canvas transformer.
*
* @param t the new behind canvas transformer
*/
public void setBehindCanvasTransformer(CanvasTransformer t) {
mViewBehind.setCanvasTransformer(t);
}
/**
* Sets the behind offset.
*
* @param i The margin, in pixels, on the right of the screen that the
* behind view scrolls to.
*/
public void setBehindOffset(int i) {
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) mViewBehind
.getLayoutParams();
int bottom = params.bottomMargin;
int top = params.topMargin;
int left = params.leftMargin;
params.setMargins(left, top, i, bottom);
OnGlobalLayoutListener layoutListener = new OnGlobalLayoutListener() {
@Override
@SuppressWarnings("deprecation")
public void onGlobalLayout() {
showAbove();
mViewAbove.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
};
mViewAbove.getViewTreeObserver().addOnGlobalLayoutListener(layoutListener);
mViewAbove.requestLayout();
}
/**
* Sets the behind offset.
*
* @param resID The dimension resource id to be set as the behind offset.
* The menu, when open, will leave this width margin on the right
* of the screen.
*/
public void setBehindOffsetRes(int resID) {
int i = (int) getContext().getResources().getDimension(resID);
setBehindOffset(i);
}
/**
* Sets the behind scroll scale.
*
* @param f The scale of the parallax scroll (i.e. 1.0f scrolls 1 pixel for
* every 1 pixel that the above view scrolls and 0.0f scrolls 0
* pixels)
*/
public void setBehindScrollScale(float f) {
mViewAbove.setScrollScale(f);
}
/**
* Sets the behind width.
*
* @param i The width the Sliding Menu will open to, in pixels
*/
@SuppressWarnings("deprecation")
public void setBehindWidth(int i) {
int width;
Display display = ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay();
try {
Class<?> cls = Display.class;
Class<?>[] parameterTypes = {
Point.class
};
Point parameter = new Point();
Method method = cls.getMethod("getSize", parameterTypes);
method.invoke(display, parameter);
width = parameter.x;
} catch (Exception e) {
width = display.getWidth();
}
setBehindOffset(width - i);
}
/**
* Sets the behind width.
*
* @param res The dimension resource id to be set as the behind width
* offset. The menu, when open, will open this wide.
*/
public void setBehindWidthRes(int res) {
int i = (int) getContext().getResources().getDimension(res);
setBehindWidth(i);
}
/**
* Set the above view content from a layout resource. The resource will be
* inflated, adding all top-level views to the above view.
*
* @param res the new content
*/
public void setContent(int res) {
setContent(LayoutInflater.from(getContext()).inflate(res, null));
}
/**
* Set the above view content to the given View.
*
* @param view The desired content to display.
*/
public void setContent(View view) {
mViewAbove.setContent(view);
mViewAbove.invalidate();
showAbove();
}
/**
* Sets how much the SlidingMenu fades in and out. Fade must be enabled, see
* {@link #setFadeEnabled(boolean) setFadeEnabled(boolean)}
*
* @param f the new fade degree, between 0.0f and 1.0f
*/
public void setFadeDegree(float f) {
mViewAbove.setBehindFadeDegree(f);
}
/**
* Enables or disables the SlidingMenu's fade in and out
*
* @param b true to enable fade, false to disable it
*/
public void setFadeEnabled(boolean b) {
mViewAbove.setBehindFadeEnabled(b);
}
/**
* Set the behind view (menu) content from a layout resource. The resource
* will be inflated, adding all top-level views to the behind view.
*
* @param res the new content
*/
public void setMenu(int res) {
setMenu(LayoutInflater.from(getContext()).inflate(res, null));
}
/**
* Set the behind view (menu) content to the given View.
*
* @param view The desired content to display.
*/
public void setMenu(View v) {
mViewBehind.setMenu(v);
mViewBehind.invalidate();
}
/**
* Sets the OnClosedListener. {@link OnClosedListener#onClosed()
* OnClosedListener.onClosed()} will be called after the SlidingMenu is
* closed
*
* @param listener the new OnClosedListener
*/
public void setOnClosedListener(OnClosedListener listener) {
mViewAbove.setOnClosedListener(listener);
}
/**
* Sets the OnCloseListener. {@link OnCloseListener#onClose()
* OnCloseListener.onClose()} will be called when the SlidingMenu is closed
*
* @param listener the new setOnCloseListener
*/
public void setOnCloseListener(OnCloseListener listener) {
// mViewAbove.setOnCloseListener(listener);
mCloseListener = listener;
}
/**
* Sets the OnOpenedListener. {@link OnOpenedListener#onOpened()
* OnOpenedListener.onOpened()} will be called after the SlidingMenu is
* opened
*
* @param listener the new OnOpenedListener
*/
public void setOnOpenedListener(OnOpenedListener listener) {
mViewAbove.setOnOpenedListener(listener);
}
/**
* Sets the OnOpenListener. {@link OnOpenListener#onOpen()
* OnOpenListener.onOpen()} will be called when the SlidingMenu is opened
*
* @param listener the new OnOpenListener
*/
public void setOnOpenListener(OnOpenListener listener) {
// mViewAbove.setOnOpenListener(listener);
mOpenListener = listener;
}
/**
* Sets the selected view. The selector will be drawn here
*
* @param v the new selected view
*/
public void setSelectedView(View v) {
mViewAbove.setSelectedView(v);
}
/**
* Sets the selector drawable.
*
* @param b the new selector bitmap
*/
public void setSelectorBitmap(Bitmap b) {
mViewAbove.setSelectorBitmap(b);
}
/**
* Sets the selector drawable.
*
* @param res a resource ID for the selector drawable
*/
public void setSelectorDrawable(int res) {
mViewAbove.setSelectorBitmap(BitmapFactory.decodeResource(getResources(), res));
}
/**
* Enables or disables whether the selector is drawn
*
* @param b true to draw the selector, false to not draw the selector
*/
public void setSelectorEnabled(boolean b) {
mViewAbove.setSelectorEnabled(true);
}
/**
* Sets the shadow drawable.
*
* @param d the new shadow drawable
*/
public void setShadowDrawable(Drawable d) {
mViewAbove.setShadowDrawable(d);
}
/**
* Sets the shadow drawable.
*
* @param resId the resource ID of the new shadow drawable
*/
public void setShadowDrawable(int resId) {
mViewAbove.setShadowDrawable(resId);
}
/**
* Sets the shadow width.
*
* @param pixels the new shadow width, in pixels
*/
public void setShadowWidth(int pixels) {
mViewAbove.setShadowWidth(pixels);
}
/**
* Sets the shadow width.
*
* @param resId The dimension resource id to be set as the shadow width.
*/
public void setShadowWidthRes(int resId) {
setShadowWidth((int) getResources().getDimension(resId));
}
/**
* Sets the sliding enabled.
*
* @param b true to enable sliding, false to disable it.
*/
public void setSlidingEnabled(boolean b) {
mViewAbove.setSlidingEnabled(b);
}
/**
* Sets whether or not the SlidingMenu is in static mode (i.e. nothing is
* moving and everything is showing)
*
* @param b true to set static mode, false to disable static mode.
*/
public void setStatic(boolean b) {
if (b) {
setSlidingEnabled(false);
mViewAbove.setCustomViewBehind(null);
mViewAbove.setCurrentItem(1);
mViewBehind.setCurrentItem(0);
} else {
mViewAbove.setCurrentItem(1);
mViewBehind.setCurrentItem(1);
mViewAbove.setCustomViewBehind(mViewBehind);
setSlidingEnabled(true);
}
}
/**
* Controls whether the SlidingMenu can be opened with a swipe gesture.
* Options are {@link #TOUCHMODE_MARGIN TOUCHMODE_MARGIN},
* {@link #TOUCHMODE_FULLSCREEN TOUCHMODE_FULLSCREEN}, or
* {@link #TOUCHMODE_NONE TOUCHMODE_NONE}
*
* @param i the new touch mode
*/
public void setTouchModeAbove(int i) {
if (i != TOUCHMODE_FULLSCREEN && i != TOUCHMODE_MARGIN
&& i != TOUCHMODE_NONE) {
throw new IllegalStateException("TouchMode must be set to either" +
"TOUCHMODE_FULLSCREEN or TOUCHMODE_MARGIN or TOUCHMODE_NONE.");
}
mViewAbove.setTouchMode(i);
}
/**
* Controls whether the SlidingMenu can be closed with a swipe gesture.
* Options are {@link #TOUCHMODE_MARGIN TOUCHMODE_MARGIN},
* {@link #TOUCHMODE_FULLSCREEN TOUCHMODE_FULLSCREEN}, or
* {@link #TOUCHMODE_NONE TOUCHMODE_NONE}
*
* @param i the new touch mode
*/
public void setTouchModeBehind(int i) {
if (i != TOUCHMODE_FULLSCREEN && i != TOUCHMODE_MARGIN
&& i != TOUCHMODE_NONE) {
throw new IllegalStateException("TouchMode must be set to either" +
"TOUCHMODE_FULLSCREEN or TOUCHMODE_MARGIN or TOUCHMODE_NONE.");
}
mViewBehind.setTouchMode(i);
}
/**
* Closes the menu and shows the above view.
*/
public void showAbove() {
showAbove(true);
}
/**
* Closes the menu and shows the above view.
*
* @param animate true to animate the transition, false to ignore animation
*/
public void showAbove(boolean animate) {
mViewAbove.setCurrentItem(1, animate);
}
/**
* Opens the menu and shows the behind view.
*/
public void showBehind() {
showBehind(true);
}
/**
* Opens the menu and shows the behind view.
*
* @param animate true to animate the transition, false to ignore animation
*/
public void showBehind(boolean animate) {
mViewAbove.setCurrentItem(0, animate);
}
/**
* Toggle the SlidingMenu. If it is open, it will be closed, and vice versa.
*/
public void toggle() {
toggle(true);
}
/**
* Toggle the SlidingMenu. If it is open, it will be closed, and vice versa.
*
* @param animate true to animate the transition, false to ignore animation
*/
public void toggle(boolean animate) {
if (isBehindShowing()) {
showAbove(animate);
} else {
showBehind(animate);
}
}
}