Android仿QQ5.0侧滑菜单ResideMenu

jopen 10年前

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

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

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

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

RESideMenu(ios项目)

https://github.com/romaonthego/RESideMenu

AndroidResideMenu

https://github.com/SpecialCyCi/AndroidResideMenu

ResideLayout

https://github.com/kyze8439690/ResideLayout

 

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

我是基于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 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();   }     /**    * 设置背景图片    *     * @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()    */   @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);       }      }     });    }   }    }
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; // ms   private static final int MIN_DISTANCE_FOR_FLING = 25; // dips     private 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;   }     @Override   protected 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);   }     @Override   protected 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());    }   }     @Override   protected 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());   }     @Override   public 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;     @Override   public 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;   }     @Override   public 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 started     int 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 event      final 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 component      mLastMotionX += 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 menu      setCurrentItem(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;    }   }     @Override   public 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();   }     @Override   protected 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 drawing   private 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);   }     @Override   public boolean dispatchKeyEvent(KeyEvent event) {    // Let the focused view and/or our descendants get the key first    return 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() {     @Override     public 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() {     @Override     public void transformCanvas(Canvas canvas, float percentOpen) {      float scale = (float) (1 - percentOpen * 0.25);      canvas.scale(scale, scale, 0, canvas.getHeight() / 2);     }    });     }

大功告成!

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

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