public class SlidingMenu extends RelativeLayout {
private static final String TAG = SlidingMenu.class.getSimpleName();
public static final int SLIDING_WINDOW = 0;
public static final int SLIDING_CONTENT = 1;
private boolean mActionbarOverlay = false;
/**
* 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(). 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(). Denies the SlidingMenu
* to be opened with a swipe gesture
*/
public static final int TOUCHMODE_NONE = 2;
/**
* Constant value for use with setMode(). Puts the menu to the left of the
* content.
*/
public static final int LEFT = 0;
/**
* Constant value for use with setMode(). Puts the menu to the right of the
* content.
*/
public static final int RIGHT = 1;
/**
* Constant value for use with setMode(). Puts menus to the left and right
* of the content.
*/
public static final int LEFT_RIGHT = 2;
private CustomViewAbove mViewAbove;
private CustomViewBehind mViewBehind;
private OnOpenListener mOpenListener;
private OnOpenListener mSecondaryOpenListner;
private OnCloseListener mCloseListener;
/**
* 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();
}
/**
* 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 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 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 Interface CanvasTransformer.
*/
public interface CanvasTransformer {
/**
* Transform canvas.
*
* @param canvas
* the canvas
* @param percentOpen
* the percent open
*/
public void transformCanvas(Canvas canvas, float percentOpen);
}
/**
* Instantiates a new SlidingMenu.
*
* @param context
* the associated Context
*/
public SlidingMenu(Context context) {
this(context, null);
}
/**
* Instantiates a new SlidingMenu and attach to Activity.
*
* @param activity
* the activity to attach slidingmenu
* @param slideStyle
* the slidingmenu style
*/
public SlidingMenu(Activity activity, int slideStyle) {
this(activity, null);
this.attachToActivity(activity, slideStyle);
}
/**
* 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(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
mViewBehind = new CustomViewBehind(context);
addView(mViewBehind, behindParams);
LayoutParams aboveParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
mViewAbove = new CustomViewAbove(context);
addView(mViewAbove, aboveParams);
// register the CustomViewBehind with the CustomViewAbove
mViewAbove.setCustomViewBehind(mViewBehind);
mViewBehind.setCustomViewAbove(mViewAbove);
mViewAbove.setOnPageChangeListener(new OnPageChangeListener() {
public static final int POSITION_OPEN = 0;
public static final int POSITION_CLOSE = 1;
public static final int POSITION_SECONDARY_OPEN = 2;
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
public void onPageSelected(int position) {
if (position == POSITION_OPEN && mOpenListener != null) {
mOpenListener.onOpen();
} else if (position == POSITION_CLOSE && mCloseListener != null) {
mCloseListener.onClose();
} else if (position == POSITION_SECONDARY_OPEN && mSecondaryOpenListner != null) {
mSecondaryOpenListner.onOpen();
}
}
});
// now style everything!
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SlidingMenu);
// set the above and behind views if defined in xml
int mode = ta.getInt(R.styleable.SlidingMenu_mode, LEFT);
setMode(mode);
int viewAbove = ta.getResourceId(R.styleable.SlidingMenu_viewAbove, -1);
if (viewAbove != -1) {
setContent(viewAbove);
} else {
setContent(new FrameLayout(context));
}
int viewBehind = ta.getResourceId(R.styleable.SlidingMenu_viewBehind, -1);
if (viewBehind != -1) {
setMenu(viewBehind);
} else {
setMenu(new FrameLayout(context));
}
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_fadeEnabled, true);
setFadeEnabled(fadeEnabled);
float fadeDeg = ta.getFloat(R.styleable.SlidingMenu_fadeDegree, 0.33f);
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();
}
/**
* Attaches the SlidingMenu to an entire Activity
*
* @param activity
* the Activity
* @param slideStyle
* either SLIDING_CONTENT or SLIDING_WINDOW
*/
public void attachToActivity(Activity activity, int slideStyle) {
attachToActivity(activity, slideStyle, false);
}
/**
* Attaches the SlidingMenu to an entire Activity
*
* @param activity
* the Activity
* @param slideStyle
* either SLIDING_CONTENT or SLIDING_WINDOW
* @param actionbarOverlay
* whether or not the ActionBar is overlaid
*/
public void attachToActivity(Activity activity, int slideStyle, boolean actionbarOverlay) {
if (slideStyle != SLIDING_WINDOW && slideStyle != SLIDING_CONTENT)
throw new IllegalArgumentException("slideStyle must be either SLIDING_WINDOW or SLIDING_CONTENT");
if (getParent() != null)
throw new IllegalStateException("This SlidingMenu appears to already be attached");
// get the window background
TypedArray a = activity.getTheme().obtainStyledAttributes(new int[] { android.R.attr.windowBackground });
int background = a.getResourceId(0, 0);
a.recycle();
switch (slideStyle) {
case SLIDING_WINDOW:
mActionbarOverlay = false;
ViewGroup decor = (ViewGroup) activity.getWindow().getDecorView();
ViewGroup decorChild = (ViewGroup) decor.getChildAt(0);
// save ActionBar themes that have transparent assets
decorChild.setBackgroundResource(background);
decor.removeView(decorChild);
decor.addView(this);
setContent(decorChild);
break;
case SLIDING_CONTENT:
mActionbarOverlay = actionbarOverlay;
// take the above view out of
ViewGroup contentParent = (ViewGroup) activity.findViewById(android.R.id.content);
View content = contentParent.getChildAt(0);
contentParent.removeView(content);
contentParent.addView(this);
setContent(content);
// save people from having transparent backgrounds
if (content.getBackground() == null)
content.setBackgroundResource(background);
break;
}
}
/**
* 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);
showContent();
}
/**
* Retrieves the current content.
*
* @return the current content
*/
public View getContent() {
return mViewAbove.getContent();
}
/**
* 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.setContent(v);
}
/**
* Retrieves the main menu.
*
* @return the main menu
*/
public View getMenu() {
return mViewBehind.getContent();
}
/**
* Set the secondary behind view (right 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 setSecondaryMenu(int res) {
setSecondaryMenu(LayoutInflater.from(getContext()).inflate(res, null));
}
/**
* Set the secondary behind view (right menu) content to the given View.
*
* @param view
* The desired content to display.
*/
public void setSecondaryMenu(View v) {
mViewBehind.setSecondaryContent(v);
// mViewBehind.invalidate();
}
/**
* Retrieves the current secondary menu (right).
*
* @return the current menu
*/
public View getSecondaryMenu() {
return mViewBehind.getSecondaryContent();
}
/**
* Sets the sliding enabled.
*
* @param b
* true to enable sliding, false to disable it.
*/
public void setSlidingEnabled(boolean b) {
mViewAbove.setSlidingEnabled(b);
}
/**
* Checks if is sliding enabled.
*
* @return true, if is sliding enabled
*/
public boolean isSlidingEnabled() {
return mViewAbove.isSlidingEnabled();
}
/**
* Sets which side the SlidingMenu should appear on.
*
* @param mode
* must be either SlidingMenu.LEFT or SlidingMenu.RIGHT
*/
public void setMode(int mode) {
if (mode != LEFT && mode != RIGHT && mode != LEFT_RIGHT) {
throw new IllegalStateException("SlidingMenu mode must be LEFT, RIGHT, or LEFT_RIGHT");
}
mViewBehind.setMode(mode);
}
/**
* Returns the current side that the SlidingMenu is on.
*
* @return the current mode, either SlidingMenu.LEFT or SlidingMenu.RIGHT
*/
public int getMode() {
return mViewBehind.getMode();
}
/**
* 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);
}
}
/**
* Opens the menu and shows the menu view.
*/
public void showMenu() {
showMenu(true);
}
/**
* Opens the menu and shows the menu view.
*
* @param animate
* true to animate the transition, false to ignore animation
*/
public void showMenu(boolean animate) {
mViewAbove.setCurrentItem(0, animate);
}
/**
* Opens the menu and shows the secondary menu view. Will default to the
* regular menu if there is only one.
*/
public void showSecondaryMenu() {
showSecondaryMenu(true);
}
/**
* Opens the menu and shows the secondary (right) menu view. Will default to
* the regular menu if there is only one.
*
* @param animate
* true to animate the transition, false to ignore animation
*/
public void showSecondaryMenu(boolean animate) {
mViewAbove.setCurrentItem(2, animate);
}
/**
* Closes the menu and shows the above view.
*/
public void showContent() {
showContent(true);
}
/**
* Closes the menu and shows the above view.
*
* @param animate
* true to animate the transition, false to ignore animation
*/
public void showContent(boolean animate) {
mViewAbove.setCurrentItem(1, 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 (isMenuShowing()) {
showContent(animate);
} else {
showMenu(animate);
}
}
/**
* Checks if is the behind view showing.
*
* @return Whether or not the behind view is showing
*/
public boolean isMenuShowing() {
return mViewAbove.getCurrentItem() == 0 || mViewAbove.getCurrentItem() == 2;
}
/**
* Checks if is the behind view showing.
*
* @return Whether or not the behind view is showing
*/
public boolean isSecondaryMenuShowing() {
return mViewAbove.getCurrentItem() == 2;
}
/**
* 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;
}
/**
* 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);
mViewBehind.setWidthOffset(i);
}
/**
* 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 above offset.
*
* @param i
* the new above offset, in pixels
*/
public void setAboveOffset(int i) {
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 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);
}
/**
* Gets the behind scroll scale.
*
* @return The scale of the parallax scroll
*/
public float getBehindScrollScale() {
return mViewBehind.getScrollScale();
}
/**
* Gets the touch mode margin threshold
*
* @return the touch mode margin threshold
*/
public int getTouchmodeMarginThreshold() {
return mViewBehind.getMarginThreshold();
}
/**
* Set the touch mode margin threshold
*
* @param touchmodeMarginThreshold
*/
public void setTouchmodeMarginThreshold(int touchmodeMarginThreshold) {
mViewBehind.setMarginThreshold(touchmodeMarginThreshold);
}
/**
* 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) {
if (f < 0 && f > 1)
throw new IllegalStateException("ScrollScale must be between 0 and 1");
mViewBehind.setScrollScale(f);
}
/**
* Sets the behind canvas transformer.
*
* @param t
* the new behind canvas transformer
*/
public void setBehindCanvasTransformer(CanvasTransformer t) {
mViewBehind.setCanvasTransformer(t);
}
/**
* Gets the touch mode above.
*
* @return the touch mode above
*/
public int getTouchModeAbove() {
return mViewAbove.getTouchMode();
}
/**
* 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 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 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);
}
/**
* Sets the shadow drawable.
*
* @param resId
* the resource ID of the new shadow drawable
*/
public void setShadowDrawable(int resId) {
setShadowDrawable(getContext().getResources().getDrawable(resId));
}
/**
* Sets the shadow drawable.
*
* @param d
* the new shadow drawable
*/
public void setShadowDrawable(Drawable d) {
mViewBehind.setShadowDrawable(d);
}
/**
* Sets the secondary (right) shadow drawable.
*
* @param resId
* the resource ID of the new shadow drawable
*/
public void setSecondaryShadowDrawable(int resId) {
setSecondaryShadowDrawable(getContext().getResources().getDrawable(resId));
}
/**
* Sets the secondary (right) shadow drawable.
*
* @param d
* the new shadow drawable
*/
public void setSecondaryShadowDrawable(Drawable d) {
mViewBehind.setSecondaryShadowDrawable(d);
}
/**
* 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 shadow width.
*
* @param pixels
* the new shadow width, in pixels
*/
public void setShadowWidth(int pixels) {
mViewBehind.setShadowWidth(pixels);
}
/**
* 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) {
mViewBehind.setFadeEnabled(b);
}
/**
* 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) {
mViewBehind.setFadeDegree(f);
}
/**
* 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) {
mViewBehind.setSelectorEnabled(true);
}
/**
* Sets the selected view. The selector will be drawn here
*
* @param v
* the new selected view
*/
public void setSelectedView(View v) {
mViewBehind.setSelectedView(v);
}
/**
* Sets the selector drawable.
*
* @param res
* a resource ID for the selector drawable
*/
public void setSelectorDrawable(int res) {
mViewBehind.setSelectorBitmap(BitmapFactory.decodeResource(getResources(), res));
}
/**
* Sets the selector drawable.
*
* @param b
* the new selector bitmap
*/
public void setSelectorBitmap(Bitmap b) {
mViewBehind.setSelectorBitmap(b);
}
/**
* Add a View ignored by the Touch Down event when mode is Fullscreen
*
* @param v
* a view to be ignored
*/
public void addIgnoredView(View v) {
mViewAbove.addIgnoredView(v);
}
/**
* Remove a View ignored by the Touch Down event when mode is Fullscreen
*
* @param v
* a view not wanted to be ignored anymore
*/
public void removeIgnoredView(View v) {
mViewAbove.removeIgnoredView(v);
}
/**
* Clear the list of Views ignored by the Touch Down event when mode is
* Fullscreen
*/
public void clearIgnoredViews() {
mViewAbove.clearIgnoredViews();
}
/**
* 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 OnOpenListner for secondary menu {@link OnOpenListener#onOpen()
* OnOpenListener.onOpen()} will be called when the secondary SlidingMenu is
* opened
*
* @param listener
* the new OnOpenListener
*/
public void setSecondaryOnOpenListner(OnOpenListener listener) {
mSecondaryOpenListner = listener;
}
/**
* Sets the OnCloseListener. {@link OnCloseListener#onClose()
* OnCloseListener.onClose()} will be called when any one of 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 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);
}
public static class SavedState extends BaseSavedState {
private final int mItem;
public SavedState(Parcelable superState, int item) {
super(superState);
mItem = item;
}
private SavedState(Parcel in) {
super(in);
mItem = in.readInt();
}
public int getItem() {
return mItem;
}
/*
* (non-Javadoc)
*
* @see android.view.AbsSavedState#writeToParcel(android.os.Parcel, int)
*/
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeInt(mItem);
}
public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
/*
* (non-Javadoc)
*
* @see android.view.View#onSaveInstanceState()
*/
@Override
protected Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState, mViewAbove.getCurrentItem());
return ss;
}
/*
* (non-Javadoc)
*
* @see android.view.View#onRestoreInstanceState(android.os.Parcelable)
*/
@Override
protected void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
mViewAbove.setCurrentItem(ss.getItem());
}
/*
* (non-Javadoc)
*
* @see android.view.ViewGroup#fitSystemWindows(android.graphics.Rect)
*/
@SuppressLint("NewApi")
@Override
protected boolean fitSystemWindows(Rect insets) {
int leftPadding = insets.left;
int rightPadding = insets.right;
int topPadding = insets.top;
int bottomPadding = insets.bottom;
if (!mActionbarOverlay) {
Log.v(TAG, "setting padding!");
setPadding(leftPadding, topPadding, rightPadding, bottomPadding);
}
return true;
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void manageLayers(float percentOpen) {
if (Build.VERSION.SDK_INT < 11)
return;
boolean layer = percentOpen > 0.0f && percentOpen < 1.0f;
final int layerType = layer ? View.LAYER_TYPE_HARDWARE : View.LAYER_TYPE_NONE;
if (layerType != getContent().getLayerType()) {
getHandler().post(new Runnable() {
public void run() {
Log.v(TAG, "changing layerType. hardware? " + (layerType == View.LAYER_TYPE_HARDWARE));
getContent().setLayerType(layerType, null);
getMenu().setLayerType(layerType, null);
if (getSecondaryMenu() != null) {
getSecondaryMenu().setLayerType(layerType, null);
}
}
});
}
}
<Scroll效果研究-系统ScrollView源码分析>
http://www.eoeandroid.com/thread-553375-1-1.html
自定义控件教程第一篇
http://www.eoeandroid.com/thread-548644-1-1.html
SlidingMenu设计思路三个主要的ViewGroup, 主ViewGroup里面包含两个重叠的ViewGroup,盖在上面的就是显示内容,而下面的就是菜单
上面的侧滑以后露出后面的View~
主ViewGroup作为一个自定义控件,里面的内容和菜单利用自定义属性设置
SlidingMenu(继承RelativeLayout),就是这个主ViewGroup,
其中又包含两个,内容CustomViewAbove和菜单CustomViewBehind(都继承ViewGroup),
SlidingMenu提供两个自定义属性,供使用者传入两个布局id,对应主体内容和菜单布局,
此外还有其他一些属性供开发者设置菜单效果
下面是代码分析
----------------------------------------------------------------------------------------
构造函数中主要代码如下:
新建两个自定义控件CustomViewAbove/Behind将其addView到SlidingMenu中,
然后在通过属性获取到view的id后会在setContent/Menu方法中设置对应布局
下面是content处理相关代码,menu同理
mViewAbove的setContent方法代码为
-----------------------------------------------------------------------------------------
CustViewAbove类,包含主要内容的类,即显示在SlidingMenu上面/前端的部分
由于SlidingMenu实现效果重点在于滑动,滑动前端显示的内容然后展现出下面的菜单部分,
所以重中之重自然就是这个CustViewAbove类里的onTouchEvent里的处理了
以下是核心方法ouTouchEvent中的分析,请先看完教程 Scroll效果研究-系统ScrollView源码分析
SlidingMenu的CustomViewAbove和ScrollView处理没什么区别,看注释就知道很多都是直接拷贝过去的
同理分按下,滑动,抬起几部分看
1.按下
记住开始在哪里点下的,这里只记录了x轴坐标,因为侧滑只关注横线移动~
mActivityPointerId是记录多点触碰的activity第一个点的id,总之是用来处理多点除控时拖动的稳定性
2.滑动
首先是determineDrag方法,即确定当前动作是否算是一个我们需要的拖动事件,是否要消费处理之~
方法如下
其中x/yDiff是x和y轴的移动距离长度,
mTouchSlop是系统对于拖动的最低长度判断,即至少移动多少多少距离,才算是一个拖动的动作~
所以判断的条件就是:
当菜单没打开时,xDiff大于mTouchSlop时才视为一个我们所需要的拖动,打开时则只要一半即可,
并且要同时满足xDiff>yDiff,即拖动是一个横向大于45度的
thisSlideAllowed可以理解为是判断是否是在菜单打开时touch到了菜单部分的位置
总结起来就是
如果拖动距离达到最小阀值且大于横向45度角度,且是touch到主体部分,则为我们需要的触摸事件
然后记录x,y的赋值给mLastMotionX/Y
当determineDrag判断这是一个我们需要的拖动事件了以后,就开始让aboveView随着动作滚动了
首先是最后一行pageScrolled(( int) scrollX);设置监听数据的,先无视
最重要是scrollTo方法,在系统的scrollTo基础上又加了点其他处理,如下
下面代码中manageLayers是提高性能用的,不深入研究了
super.scrollTo就是让aboveView随着手势拖动滚动,这个比较简单
此外,
使用SlidingMenu的时候可以注意到,滚动上面主体内容展示后面菜单时,菜单也有一个滚动展开效果
这个mViewBehind.scrollBehindTo( mContent, x, y);就是处理这个的
这里先知道作用,分析完above部分后再详细分析behind部分,会介绍这个作用
然后是抬起方法,对于SlidingMenu,如果使用过肯定会有一定了解,以下是一些效果需求
此外,
使用SlidingMenu的时候可以注意到,滚动上面主体内容展示后面菜单时,菜单也有一个滚动展开效果
这个mViewBehind.scrollBehindTo( mContent, x, y);就是处理这个的
这里先知道作用,分析完above部分后再详细分析behind部分,会介绍这个作用
如果菜单打开到一定位置,则抬起时菜单会完全打开
如果菜单打开部分太小,则抬起时菜单会收回去
还要有甩的处理,随着手势甩开菜单,甩关闭菜单~
下面是代码部分
直接定位到SlidingMenu中特殊处理的部分
pageOffset为菜单当前打开比例,是一个0~1的值,比如打开一半了比例就是0.5,
该值可以是正可以是负,用于表示方向,向右为正向左为负~
计算方法为
mCurItem为当前页索引,可能是0,1,2
0代表左边的菜单打开状态,1是没有菜单打开,2是右边菜单打开
计算出页pageOffset后再根据速度和移动距离最终算出目标页位置,方法如下
速度和距离达到最小值后,则根据速度和距离的正负控制当前页索引加或减1,
当达不到if条件时,则计算当前页索引mCurItem与菜单打开状态比例值pageOffset和的四舍五入值~
举个else处理的例子
比如当前页为1即未打开菜单状态,此时左移了菜单宽度十分之三的距离,即pageOffset=-0.3
那结果就是 1 - 0.3 = 0.7 取四舍五入就是1~ 当前页还是1,即速度不够时移动三分之一抬起菜单还会收回去
还是刚才的条件,左移换成了十分之八的距离,即pageOffset=-0.8,
结果就是1 - 0.8 = 0.2 四舍五入就是0~ 即当前页应该是0左菜单了
以上简单总结就是,速度距离足够后,根据速度控制左右滑动来显示菜单或者内容
如果速度不够,则移动超过菜单一半距离时就切换菜单打开关闭状态,不然就收回去
注意,determineTargetPage方法只是获取目标页位置,实际跳转工作是下面的方法
至于duration滑动持续时间的计算算法就不研究了~Scroller用法也不介绍了,之前ScrollView教程里有
------------------------------------------------------------------------------------------------------
以上部分其实看懂ScrollView原理以后看完全没压力理解,但还有一个没介绍的知识点,touch事件分发问题~
问题场景:
在菜单打开的时候要进行判断,
如果点击在主体部分,则自动收回菜单,即让主体部分消费这个touch事件,
如果此时点击在菜单部分,那above中应该不消费此事件,要将其传给behind部分,
然后让behind中的listview或者button等自定义设置的控件去获取处理touch事件~
事件分发可以参考
http://www.eoeandroid.com/thread-277371-1-1.html
此外还有菜单状态打开时点主体部分则直接关闭菜单,或者back键直接关闭菜单等,原理上面都介绍了,
这些逻辑方面的处理还有其他一些优化部分就不介绍了~
------------------------------------------------------------------------------------------------------
菜单部分CustomViewBehind中内容不多,主要有两大块内容
1.滚动
研究主体内容部分的时候已经提到过,当前端内容部分拖动显示/关闭菜单时,菜单也有一个随之滚动的效果~
2.效果绘制
包括阴影,淡入淡出效果等
绘制部分比较复杂需要以后有时间专门介绍,先介绍滚动相关的部分即1
相关方法如下,该方法在CustomViewAbove的scrollTo中调用
只分析LEFT的情况(请他情况同理)
比如菜单的width即getBehindWidth的宽度为300
那从菜单关闭状态一直滚到菜单完全打开状态,above主体内容x轴上的scroll变化就是0 ~ -300
此时如果mScrollScale即滚动比例为0.3
那按照上面scrollBehindTo中的算法, behind菜单部分x轴上的scroll变化就是
(0+300)*0.3 ~ (-300+300)*0.3即100~0
如果比例换成0.6,那behind的x变化就是180~0
极端情况下
1.mScrollScale=0, behind的x变化为 0~0, 此时会发现behind菜单部分在打开关闭的过程中无任何滚动
2.mScrollScale=1,behind的换标为300~0, 此时的效果就是主体和菜单紧挨着以同一个速度滚动
注意,这里的x为滚动的偏移量,即scrollTo使用的参数,
比如scrollView中,scrollTo(0, 100)会往下滚动到y偏移量100的位置,
此时scrollView里面的内容视觉上看其实是向上移了~
这里同理
虽然朝右拖动的时候view看上去是朝右运动,但x轴上滚动偏移量是减少的
------------------------------------------------------------------------------------------------------
最后是demo,自定义底部滑出菜单控件
根据SlidingMenu修改的,删除了SlidingMenu中大量代码,只保留了最核心的部分方便理解~
缺陷
限定死了behind菜单部分的高度和底部运动的比例值等在SlidingMenu中是可以设定的信息
老样子,回复可以免积分
链接:http://pan.baidu.com/s/1bnCPQiF 密码:pa0v