【案例分享】仿QQ5.0侧滑菜单ResideMenu

来源:互联网 发布:linux如何安装rpm包 编辑:程序博客网 时间:2024/06/01 08:11

本文由 孙国威 原创。如需转载,请注明出处!


为了后续对这个项目进行优化,比如透明度动画、背景图的位移动画,以及性能上的优化。

我把这个项目上传到github上面,请大家随时关注。

github地址 https://github.com/sunguowei


最近项目要做一个QQ5.0的侧滑菜单效果,和传统的侧滑菜单存在着一些差异。想必大家都已经见识过了。

为了不重复发明轮子,先去github上面搜索了一番。

发现了几个类似的,但是还是有一些不同。

下面是搜索到的类似的开源项目。

RESideMenu(ios项目)

https://github.com/romaonthego/RESideMenu


AndroidResideMenu

https://github.com/SpecialCyCi/AndroidResideMenu


ResideLayout

https://github.com/kyze8439690/ResideLayout



研究了一下这些开源项目的源代码。感觉并不是特别适用于我们自己的项目。所以,我自己又研究了一下。最后的效果如下。当然了,还有很多可以优化的地方,后续再慢慢优化。


备注:如果图片动画显示不出来,可以点击这个网址查看。

http://img.blog.csdn.net/20140902225149282?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbWFub2Vs/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast


我是基于SlidingMenu库进行的二次修改,增加了一些转场动画。

大家对这个库应该比较熟悉,下面是SlidingMenu的github地址。非常感谢Jeremy Feinstein提供的这个库,让广大Android Developers省去了非常多的麻烦。

https://github.com/jfeinstein10/SlidingMenu

备注:SlidingMenu使用了SherlockActionBar这个库,配置起来会比较麻烦,在文章的最后我会把demo上传,供大家下载,减去了大家自己配置项目的麻烦。

我主要修改了2个类,SlidingMenu.java和CustonViewAbove.java,只是增加了一些功能,并没有修改原本的功能。

做了修改的地方,我做了中文注释,其实实现很简单,几行代码而已。推荐大家下载Demo,然后自己调试一下。Demo的下载地址在文章的末尾。

废话不多说,直接上代码,略微有点长。

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;        /** 整体的背景,用一个ImageView代替 */private ImageView mViewBackground;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);                /** SlidingMenu是一个RelativeLayout,这里把背景图ImageView添加到RelativeLayout的最底层。*/LayoutParams backgroundParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);mViewBackground = new ImageView(context);mViewBackground.setScaleType(ImageView.ScaleType.CENTER_CROP);addView(mViewBackground, backgroundParams);                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 CustomViewAbovemViewAbove.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 xmlint 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);elsesetBehindOffset(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 backgroundTypedArray 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 assetsdecorChild.setBackgroundResource(background);decor.removeView(decorChild);decor.addView(this);setContent(decorChild);break;case SLIDING_CONTENT:mActionbarOverlay = actionbarOverlay;// take the above view out ofViewGroup 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 backgroundsif (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();}/** * 设置背景图片 *  * @param resid */public void setBackgroundImage(int resid) {mViewBackground.setBackgroundResource(resid);}/** * 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);}/** * 设置右侧视图的转场动画 *  * @param t *            the new above canvas transformer */public void setAboveCanvasTransformer(CanvasTransformer t) {mViewAbove.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() */@Overrideprotected 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) */@Overrideprotected 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")@Overrideprotected 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);}}});}}}
public class CustomViewAbove extends ViewGroup {private static final String TAG = "CustomViewAbove";private static final boolean DEBUG = false;private static final boolean USE_CACHE = false;private static final int MAX_SETTLE_DURATION = 600; // msprivate static final int MIN_DISTANCE_FOR_FLING = 25; // dipsprivate static final Interpolator sInterpolator = new Interpolator() {public float getInterpolation(float t) {t -= 1.0f;return t * t * t * t * t + 1.0f;}};private View mContent;private int mCurItem;private Scroller mScroller;private boolean mScrollingCacheEnabled;private boolean mScrolling;private boolean mIsBeingDragged;private boolean mIsUnableToDrag;private int mTouchSlop;private float mInitialMotionX;/** * Position of the last motion event. */private float mLastMotionX;private float mLastMotionY;/** * ID of the active pointer. This is used to retain consistency during * drags/flings if multiple pointers are used. */protected int mActivePointerId = INVALID_POINTER;/** * Sentinel value for no current active pointer. * Used by {@link #mActivePointerId}. */private static final int INVALID_POINTER = -1;        /** 保存转场动画的变量*/private CanvasTransformer mTransformer;/** * Determines speed during touch scrolling */protected VelocityTracker mVelocityTracker;private int mMinimumVelocity;protected int mMaximumVelocity;private int mFlingDistance;private CustomViewBehind mViewBehind;//private int mMode;private boolean mEnabled = true;private OnPageChangeListener mOnPageChangeListener;private OnPageChangeListener mInternalPageChangeListener;//private OnCloseListener mCloseListener;//private OnOpenListener mOpenListener;private OnClosedListener mClosedListener;private OnOpenedListener mOpenedListener;private List<View> mIgnoredViews = new ArrayList<View>();//private int mScrollState = SCROLL_STATE_IDLE;/** * Callback interface for responding to changing state of the selected page. */public interface OnPageChangeListener {/** * This method will be invoked when the current page is scrolled, either as part * of a programmatically initiated smooth scroll or a user initiated touch scroll. * * @param position Position index of the first page currently being displayed. *                 Page position+1 will be visible if positionOffset is nonzero. * @param positionOffset Value from [0, 1) indicating the offset from the page at position. * @param positionOffsetPixels Value in pixels indicating the offset from position. */public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels);/** * This method will be invoked when a new page becomes selected. Animation is not * necessarily complete. * * @param position Position index of the new selected page. */public void onPageSelected(int position);}/** * Simple implementation of the {@link OnPageChangeListener} interface with stub * implementations of each method. Extend this if you do not intend to override * every method of {@link OnPageChangeListener}. */public static class SimpleOnPageChangeListener implements OnPageChangeListener {public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {// This space for rent}public void onPageSelected(int position) {// This space for rent}public void onPageScrollStateChanged(int state) {// This space for rent}}public CustomViewAbove(Context context) {this(context, null);}public CustomViewAbove(Context context, AttributeSet attrs) {super(context, attrs);initCustomViewAbove();}void initCustomViewAbove() {setWillNotDraw(false);setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);setFocusable(true);final Context context = getContext();mScroller = new Scroller(context, sInterpolator);final ViewConfiguration configuration = ViewConfiguration.get(context);mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();setInternalPageChangeListener(new SimpleOnPageChangeListener() {public void onPageSelected(int position) {if (mViewBehind != null) {switch (position) {case 0:case 2:mViewBehind.setChildrenEnabled(true);break;case 1:mViewBehind.setChildrenEnabled(false);break;}}}});final float density = context.getResources().getDisplayMetrics().density;mFlingDistance = (int) (MIN_DISTANCE_FOR_FLING * density);}/** * Set the currently selected page. If the CustomViewPager has already been through its first * layout there will be a smooth animated transition between the current item and the * specified item. * * @param item Item index to select */public void setCurrentItem(int item) {setCurrentItemInternal(item, true, false);}/** * Set the currently selected page. * * @param item Item index to select * @param smoothScroll True to smoothly scroll to the new item, false to transition immediately */public void setCurrentItem(int item, boolean smoothScroll) {setCurrentItemInternal(item, smoothScroll, false);}public int getCurrentItem() {return mCurItem;}void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) {setCurrentItemInternal(item, smoothScroll, always, 0);}void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) {if (!always && mCurItem == item) {setScrollingCacheEnabled(false);return;}item = mViewBehind.getMenuPage(item);final boolean dispatchSelected = mCurItem != item;mCurItem = item;final int destX = getDestScrollX(mCurItem);if (dispatchSelected && mOnPageChangeListener != null) {mOnPageChangeListener.onPageSelected(item);}if (dispatchSelected && mInternalPageChangeListener != null) {mInternalPageChangeListener.onPageSelected(item);}if (smoothScroll) {smoothScrollTo(destX, 0, velocity);} else {completeScroll();scrollTo(destX, 0);}}/** * Set a listener that will be invoked whenever the page changes or is incrementally * scrolled. See {@link OnPageChangeListener}. * * @param listener Listener to set */public void setOnPageChangeListener(OnPageChangeListener listener) {mOnPageChangeListener = listener;}/*public void setOnOpenListener(OnOpenListener l) {mOpenListener = l;}public void setOnCloseListener(OnCloseListener l) {mCloseListener = l;} */public void setOnOpenedListener(OnOpenedListener l) {mOpenedListener = l;}public void setOnClosedListener(OnClosedListener l) {mClosedListener = l;}/** * Set a separate OnPageChangeListener for internal use by the support library. * * @param listener Listener to set * @return The old listener that was set, if any. */OnPageChangeListener setInternalPageChangeListener(OnPageChangeListener listener) {OnPageChangeListener oldListener = mInternalPageChangeListener;mInternalPageChangeListener = listener;return oldListener;}public void addIgnoredView(View v) {if (!mIgnoredViews.contains(v)) {mIgnoredViews.add(v);}}public void removeIgnoredView(View v) {mIgnoredViews.remove(v);}public void clearIgnoredViews() {mIgnoredViews.clear();}// We want the duration of the page snap animation to be influenced by the distance that// the screen has to travel, however, we don't want this duration to be effected in a// purely linear fashion. Instead, we use this method to moderate the effect that the distance// of travel has on the overall snap duration.float distanceInfluenceForSnapDuration(float f) {f -= 0.5f; // center the values about 0.f *= 0.3f * Math.PI / 2.0f;return (float) FloatMath.sin(f);}public int getDestScrollX(int page) {switch (page) {case 0:case 2:return mViewBehind.getMenuLeft(mContent, page);case 1:return mContent.getLeft();}return 0;}private int getLeftBound() {return mViewBehind.getAbsLeftBound(mContent);}private int getRightBound() {return mViewBehind.getAbsRightBound(mContent);}public int getContentLeft() {return mContent.getLeft() + mContent.getPaddingLeft();}public boolean isMenuOpen() {return mCurItem == 0 || mCurItem == 2;}private boolean isInIgnoredView(MotionEvent ev) {Rect rect = new Rect();for (View v : mIgnoredViews) {v.getHitRect(rect);if (rect.contains((int)ev.getX(), (int)ev.getY())) return true;}return false;}public int getBehindWidth() {if (mViewBehind == null) {return 0;} else {return mViewBehind.getBehindWidth();}}public int getChildWidth(int i) {switch (i) {case 0:return getBehindWidth();case 1:return mContent.getWidth();default:return 0;}}public boolean isSlidingEnabled() {return mEnabled;}public void setSlidingEnabled(boolean b) {mEnabled = b;}/** * Like {@link View#scrollBy}, but scroll smoothly instead of immediately. * * @param x the number of pixels to scroll by on the X axis * @param y the number of pixels to scroll by on the Y axis */void smoothScrollTo(int x, int y) {smoothScrollTo(x, y, 0);}/** * Like {@link View#scrollBy}, but scroll smoothly instead of immediately. * * @param x the number of pixels to scroll by on the X axis * @param y the number of pixels to scroll by on the Y axis * @param velocity the velocity associated with a fling, if applicable. (0 otherwise) */void smoothScrollTo(int x, int y, int velocity) {if (getChildCount() == 0) {// Nothing to do.setScrollingCacheEnabled(false);return;}int sx = getScrollX();int sy = getScrollY();int dx = x - sx;int dy = y - sy;if (dx == 0 && dy == 0) {completeScroll();if (isMenuOpen()) {if (mOpenedListener != null)mOpenedListener.onOpened();} else {if (mClosedListener != null)mClosedListener.onClosed();}return;}setScrollingCacheEnabled(true);mScrolling = true;final int width = getBehindWidth();final int halfWidth = width / 2;final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / width);final float distance = halfWidth + halfWidth *distanceInfluenceForSnapDuration(distanceRatio);int duration = 0;velocity = Math.abs(velocity);if (velocity > 0) {duration = 4 * Math.round(1000 * Math.abs(distance / velocity));} else {final float pageDelta = (float) Math.abs(dx) / width;duration = (int) ((pageDelta + 1) * 100);duration = MAX_SETTLE_DURATION;}duration = Math.min(duration, MAX_SETTLE_DURATION);mScroller.startScroll(sx, sy, dx, dy, duration);invalidate();}public void setContent(View v) {if (mContent != null) this.removeView(mContent);mContent = v;addView(mContent);}public View getContent() {return mContent;}public void setCustomViewBehind(CustomViewBehind cvb) {mViewBehind = cvb;}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int width = getDefaultSize(0, widthMeasureSpec);int height = getDefaultSize(0, heightMeasureSpec);setMeasuredDimension(width, height);final int contentWidth = getChildMeasureSpec(widthMeasureSpec, 0, width);final int contentHeight = getChildMeasureSpec(heightMeasureSpec, 0, height);mContent.measure(contentWidth, contentHeight);}@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);// Make sure scroll position is set correctly.if (w != oldw) {// [ChrisJ] - This fixes the onConfiguration change for orientation issue..// maybe worth having a look why the recomputeScroll pos is screwing// up?completeScroll();scrollTo(getDestScrollX(mCurItem), getScrollY());}}@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {final int width = r - l;final int height = b - t;mContent.layout(0, 0, width, height);}public void setAboveOffset(int i) {//RelativeLayout.LayoutParams params = ((RelativeLayout.LayoutParams)mContent.getLayoutParams());//params.setMargins(i, params.topMargin, params.rightMargin, params.bottomMargin);mContent.setPadding(i, mContent.getPaddingTop(), mContent.getPaddingRight(), mContent.getPaddingBottom());}@Overridepublic void computeScroll() {if (!mScroller.isFinished()) {if (mScroller.computeScrollOffset()) {int oldX = getScrollX();int oldY = getScrollY();int x = mScroller.getCurrX();int y = mScroller.getCurrY();if (oldX != x || oldY != y) {scrollTo(x, y);pageScrolled(x);}// Keep on drawing until the animation has finished.invalidate();return;}}// Done with scroll, clean up state.completeScroll();}private void pageScrolled(int xpos) {final int widthWithMargin = getWidth();final int position = xpos / widthWithMargin;final int offsetPixels = xpos % widthWithMargin;final float offset = (float) offsetPixels / widthWithMargin;onPageScrolled(position, offset, offsetPixels);}/** * This method will be invoked when the current page is scrolled, either as part * of a programmatically initiated smooth scroll or a user initiated touch scroll. * If you override this method you must call through to the superclass implementation * (e.g. super.onPageScrolled(position, offset, offsetPixels)) before onPageScrolled * returns. * * @param position Position index of the first page currently being displayed. *                 Page position+1 will be visible if positionOffset is nonzero. * @param offset Value from [0, 1) indicating the offset from the page at position. * @param offsetPixels Value in pixels indicating the offset from position. */protected void onPageScrolled(int position, float offset, int offsetPixels) {if (mOnPageChangeListener != null) {mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels);}if (mInternalPageChangeListener != null) {mInternalPageChangeListener.onPageScrolled(position, offset, offsetPixels);}}private void completeScroll() {boolean needPopulate = mScrolling;if (needPopulate) {// Done with scroll, no longer want to cache view drawing.setScrollingCacheEnabled(false);mScroller.abortAnimation();int oldX = getScrollX();int oldY = getScrollY();int x = mScroller.getCurrX();int y = mScroller.getCurrY();if (oldX != x || oldY != y) {scrollTo(x, y);}if (isMenuOpen()) {if (mOpenedListener != null)mOpenedListener.onOpened();} else {if (mClosedListener != null)mClosedListener.onClosed();}}mScrolling = false;}protected int mTouchMode = SlidingMenu.TOUCHMODE_MARGIN;public void setTouchMode(int i) {mTouchMode = i;}public int getTouchMode() {return mTouchMode;}private boolean thisTouchAllowed(MotionEvent ev) {int x = (int) (ev.getX() + mScrollX);if (isMenuOpen()) {return mViewBehind.menuOpenTouchAllowed(mContent, mCurItem, x);} else {switch (mTouchMode) {case SlidingMenu.TOUCHMODE_FULLSCREEN:return !isInIgnoredView(ev);case SlidingMenu.TOUCHMODE_NONE:return false;case SlidingMenu.TOUCHMODE_MARGIN:return mViewBehind.marginTouchAllowed(mContent, x);}}return false;}private boolean thisSlideAllowed(float dx) {boolean allowed = false;if (isMenuOpen()) {allowed = mViewBehind.menuOpenSlideAllowed(dx);} else {allowed = mViewBehind.menuClosedSlideAllowed(dx);}if (DEBUG)Log.v(TAG, "this slide allowed " + allowed + " dx: " + dx);return allowed;}private int getPointerIndex(MotionEvent ev, int id) {int activePointerIndex = MotionEventCompat.findPointerIndex(ev, id);if (activePointerIndex == -1)mActivePointerId = INVALID_POINTER;return activePointerIndex;}private boolean mQuickReturn = false;@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {if (!mEnabled)return false;final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;if (DEBUG)if (action == MotionEvent.ACTION_DOWN)Log.v(TAG, "Received ACTION_DOWN");if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP|| (action != MotionEvent.ACTION_DOWN && mIsUnableToDrag)) {endDrag();return false;}switch (action) {case MotionEvent.ACTION_MOVE:determineDrag(ev);break;case MotionEvent.ACTION_DOWN:int index = MotionEventCompat.getActionIndex(ev);mActivePointerId = MotionEventCompat.getPointerId(ev, index);if (mActivePointerId == INVALID_POINTER)break;mLastMotionX = mInitialMotionX = MotionEventCompat.getX(ev, index);mLastMotionY = MotionEventCompat.getY(ev, index);if (thisTouchAllowed(ev)) {mIsBeingDragged = false;mIsUnableToDrag = false;if (isMenuOpen() && mViewBehind.menuTouchInQuickReturn(mContent, mCurItem, ev.getX() + mScrollX)) {mQuickReturn = true;}} else {mIsUnableToDrag = true;}break;case MotionEventCompat.ACTION_POINTER_UP:onSecondaryPointerUp(ev);break;}if (!mIsBeingDragged) {if (mVelocityTracker == null) {mVelocityTracker = VelocityTracker.obtain();}mVelocityTracker.addMovement(ev);}return mIsBeingDragged || mQuickReturn;}@Overridepublic boolean onTouchEvent(MotionEvent ev) {if (!mEnabled)return false;if (!mIsBeingDragged && !thisTouchAllowed(ev))return false;//if (!mIsBeingDragged && !mQuickReturn)//return false;final int action = ev.getAction();if (mVelocityTracker == null) {mVelocityTracker = VelocityTracker.obtain();}mVelocityTracker.addMovement(ev);switch (action & MotionEventCompat.ACTION_MASK) {case MotionEvent.ACTION_DOWN:/* * If being flinged and user touches, stop the fling. isFinished * will be false if being flinged. */completeScroll();// Remember where the motion event startedint index = MotionEventCompat.getActionIndex(ev);mActivePointerId = MotionEventCompat.getPointerId(ev, index);mLastMotionX = mInitialMotionX = ev.getX();break;case MotionEvent.ACTION_MOVE:if (!mIsBeingDragged) {determineDrag(ev);if (mIsUnableToDrag)return false;}if (mIsBeingDragged) {// Scroll to follow the motion eventfinal int activePointerIndex = getPointerIndex(ev, mActivePointerId);if (mActivePointerId == INVALID_POINTER)break;final float x = MotionEventCompat.getX(ev, activePointerIndex);final float deltaX = mLastMotionX - x;mLastMotionX = x;float oldScrollX = getScrollX();float scrollX = oldScrollX + deltaX;final float leftBound = getLeftBound();final float rightBound = getRightBound();if (scrollX < leftBound) {scrollX = leftBound;} else if (scrollX > rightBound) {scrollX = rightBound;}// Don't lose the rounded componentmLastMotionX += scrollX - (int) scrollX;scrollTo((int) scrollX, getScrollY());pageScrolled((int) scrollX);}break;case MotionEvent.ACTION_UP:if (mIsBeingDragged) {final VelocityTracker velocityTracker = mVelocityTracker;velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(velocityTracker, mActivePointerId);final int scrollX = getScrollX();final float pageOffset = (float) (scrollX - getDestScrollX(mCurItem)) / getBehindWidth();final int activePointerIndex = getPointerIndex(ev, mActivePointerId);if (mActivePointerId != INVALID_POINTER) {final float x = MotionEventCompat.getX(ev, activePointerIndex);final int totalDelta = (int) (x - mInitialMotionX);int nextPage = determineTargetPage(pageOffset, initialVelocity, totalDelta);setCurrentItemInternal(nextPage, true, true, initialVelocity);} else {setCurrentItemInternal(mCurItem, true, true, initialVelocity);}mActivePointerId = INVALID_POINTER;endDrag();} else if (mQuickReturn && mViewBehind.menuTouchInQuickReturn(mContent, mCurItem, ev.getX() + mScrollX)) {// close the menusetCurrentItem(1);endDrag();}break;case MotionEvent.ACTION_CANCEL:if (mIsBeingDragged) {setCurrentItemInternal(mCurItem, true, true);mActivePointerId = INVALID_POINTER;endDrag();}break;case MotionEventCompat.ACTION_POINTER_DOWN: {final int indexx = MotionEventCompat.getActionIndex(ev);mLastMotionX = MotionEventCompat.getX(ev, indexx);mActivePointerId = MotionEventCompat.getPointerId(ev, indexx);break;}case MotionEventCompat.ACTION_POINTER_UP:onSecondaryPointerUp(ev);int pointerIndex = getPointerIndex(ev, mActivePointerId);if (mActivePointerId == INVALID_POINTER)break;mLastMotionX = MotionEventCompat.getX(ev, pointerIndex);break;}return true;}private void determineDrag(MotionEvent ev) {final int activePointerId = mActivePointerId;final int pointerIndex = getPointerIndex(ev, activePointerId);if (activePointerId == INVALID_POINTER || pointerIndex == INVALID_POINTER)return;final float x = MotionEventCompat.getX(ev, pointerIndex);final float dx = x - mLastMotionX;final float xDiff = Math.abs(dx);final float y = MotionEventCompat.getY(ev, pointerIndex);final float dy = y - mLastMotionY;final float yDiff = Math.abs(dy);if (xDiff > (isMenuOpen()?mTouchSlop/2:mTouchSlop) && xDiff > yDiff && thisSlideAllowed(dx)) {startDrag();mLastMotionX = x;mLastMotionY = y;setScrollingCacheEnabled(true);// TODO add back in touch slop check} else if (xDiff > mTouchSlop) {mIsUnableToDrag = true;}}@Overridepublic void scrollTo(int x, int y) {super.scrollTo(x, y);mScrollX = x;mViewBehind.scrollBehindTo(mContent, x, y);   ((SlidingMenu)getParent()).manageLayers(getPercentOpen());if (mTransformer != null) {invalidate();}}private int determineTargetPage(float pageOffset, int velocity, int deltaX) {int targetPage = mCurItem;if (Math.abs(deltaX) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) {if (velocity > 0 && deltaX > 0) {targetPage -= 1;} else if (velocity < 0 && deltaX < 0){targetPage += 1;}} else {targetPage = (int) Math.round(mCurItem + pageOffset);}return targetPage;}protected float getPercentOpen() {return Math.abs(mScrollX-mContent.getLeft()) / getBehindWidth();}@Overrideprotected void dispatchDraw(Canvas canvas) {// 这句要注释掉,否则会出现2个右侧的视图,一个有转场动画,一个没有转场动画                // super.dispatchDraw(canvas);// Draw the margin drawable if needed.mViewBehind.drawShadow(mContent, canvas);mViewBehind.drawFade(mContent, canvas, getPercentOpen());mViewBehind.drawSelector(mContent, canvas, getPercentOpen());// 设置右侧视图的转场效果,主要是修改Canvas。if (mTransformer != null) {canvas.save();mTransformer.transformCanvas(canvas, getPercentOpen());super.dispatchDraw(canvas);canvas.restore();} else {super.dispatchDraw(canvas);}}// variables for drawingprivate float mScrollX = 0.0f;private void onSecondaryPointerUp(MotionEvent ev) {if (DEBUG) Log.v(TAG, "onSecondaryPointerUp called");final int pointerIndex = MotionEventCompat.getActionIndex(ev);final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);if (pointerId == mActivePointerId) {// This was our active pointer going up. Choose a new// active pointer and adjust accordingly.final int newPointerIndex = pointerIndex == 0 ? 1 : 0;mLastMotionX = MotionEventCompat.getX(ev, newPointerIndex);mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);if (mVelocityTracker != null) {mVelocityTracker.clear();}}}private void startDrag() {mIsBeingDragged = true;mQuickReturn = false;}private void endDrag() {mQuickReturn = false;mIsBeingDragged = false;mIsUnableToDrag = false;mActivePointerId = INVALID_POINTER;if (mVelocityTracker != null) {mVelocityTracker.recycle();mVelocityTracker = null;}}private void setScrollingCacheEnabled(boolean enabled) {if (mScrollingCacheEnabled != enabled) {mScrollingCacheEnabled = enabled;if (USE_CACHE) {final int size = getChildCount();for (int i = 0; i < size; ++i) {final View child = getChildAt(i);if (child.getVisibility() != GONE) {child.setDrawingCacheEnabled(enabled);}}}}}/** * Tests scrollability within child views of v given a delta of dx. * * @param v View to test for horizontal scrollability * @param checkV Whether the view v passed should itself be checked for scrollability (true), *               or just its children (false). * @param dx Delta scrolled in pixels * @param x X coordinate of the active touch point * @param y Y coordinate of the active touch point * @return true if child views of v can be scrolled by delta of dx. */protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {if (v instanceof ViewGroup) {final ViewGroup group = (ViewGroup) v;final int scrollX = v.getScrollX();final int scrollY = v.getScrollY();final int count = group.getChildCount();// Count backwards - let topmost views consume scroll distance first.for (int i = count - 1; i >= 0; i--) {final View child = group.getChildAt(i);if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&canScroll(child, true, dx, x + scrollX - child.getLeft(),y + scrollY - child.getTop())) {return true;}}}return checkV && ViewCompat.canScrollHorizontally(v, -dx);}@Overridepublic boolean dispatchKeyEvent(KeyEvent event) {// Let the focused view and/or our descendants get the key firstreturn super.dispatchKeyEvent(event) || executeKeyEvent(event);}/** * You can call this function yourself to have the scroll view perform * scrolling from a key event, just as if the event had been dispatched to * it by the view hierarchy. * * @param event The key event to execute. * @return Return true if the event was handled, else false. */public boolean executeKeyEvent(KeyEvent event) {boolean handled = false;if (event.getAction() == KeyEvent.ACTION_DOWN) {switch (event.getKeyCode()) {case KeyEvent.KEYCODE_DPAD_LEFT:handled = arrowScroll(FOCUS_LEFT);break;case KeyEvent.KEYCODE_DPAD_RIGHT:handled = arrowScroll(FOCUS_RIGHT);break;case KeyEvent.KEYCODE_TAB:if (Build.VERSION.SDK_INT >= 11) {// The focus finder had a bug handling FOCUS_FORWARD and FOCUS_BACKWARD// before Android 3.0. Ignore the tab key on those devices.if (KeyEventCompat.hasNoModifiers(event)) {handled = arrowScroll(FOCUS_FORWARD);} else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_SHIFT_ON)) {handled = arrowScroll(FOCUS_BACKWARD);}}break;}}return handled;}public boolean arrowScroll(int direction) {View currentFocused = findFocus();if (currentFocused == this) currentFocused = null;boolean handled = false;View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused,direction);if (nextFocused != null && nextFocused != currentFocused) {if (direction == View.FOCUS_LEFT) {handled = nextFocused.requestFocus();} else if (direction == View.FOCUS_RIGHT) {// If there is nothing to the right, or this is causing us to// jump to the left, then what we really want to do is page right.if (currentFocused != null && nextFocused.getLeft() <= currentFocused.getLeft()) {handled = pageRight();} else {handled = nextFocused.requestFocus();}}} else if (direction == FOCUS_LEFT || direction == FOCUS_BACKWARD) {// Trying to move left and nothing there; try to page.handled = pageLeft();} else if (direction == FOCUS_RIGHT || direction == FOCUS_FORWARD) {// Trying to move right and nothing there; try to page.handled = pageRight();}if (handled) {playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));}return handled;}boolean pageLeft() {if (mCurItem > 0) {setCurrentItem(mCurItem-1, true);return true;}return false;}boolean pageRight() {if (mCurItem < 1) {setCurrentItem(mCurItem+1, true);return true;}return false;}public void setCanvasTransformer(CanvasTransformer t) {mTransformer = t;}}
如果想要使用这个侧滑菜单的动画效果,直接替换这两个类即可。同时,并不会影响SlidingMenu的固有功能。

下面看看如何配置SlidingMenu实例。

SlidingMenu sm = getSlidingMenu();sm.setBehindOffsetRes(R.dimen.slidingmenu_offset);sm.setFadeEnabled(false);sm.setBehindScrollScale(0.25f);sm.setFadeDegree(0.25f);// 配置背景图片sm.setBackgroundImage(R.drawable.img_frame_background);// 设置专场动画效果sm.setBehindCanvasTransformer(new SlidingMenu.CanvasTransformer() {@Overridepublic void transformCanvas(Canvas canvas, float percentOpen) {float scale = (float) (percentOpen * 0.25 + 0.75);canvas.scale(scale, scale, -canvas.getWidth() / 2,canvas.getHeight() / 2);}});sm.setAboveCanvasTransformer(new SlidingMenu.CanvasTransformer() {@Overridepublic void transformCanvas(Canvas canvas, float percentOpen) {float scale = (float) (1 - percentOpen * 0.25);canvas.scale(scale, scale, 0, canvas.getHeight() / 2);}});}

大功告成!

最后,附上Demo的下载地址。

GitHub https://github.com/sunguowei/Android-ResideMenu

CSDN资源   http://download.csdn.net/detail/manoel/7857771

百度网盘    http://pan.baidu.com/s/1jGrASui


关于这个框架后期的优化,请关注我的github。

https://github.com/sunguowei


Okay,要说的就这么多,希望能给大家带来一些帮助。

66 5