仿qq最新侧滑菜单

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

我把这个项目上传到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 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的下载地址。

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

扫码关注云+社区

领取腾讯云代金券