com.aretha.slidemenu.SlideMenu.java Source code

Java tutorial

Introduction

Here is the source code for com.aretha.slidemenu.SlideMenu.java

Source

/* Copyright (c) 2011-2013 Tang Ke
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.aretha.slidemenu;

import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.GradientDrawable.Orientation;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewDebug.ExportedProperty;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
import android.widget.HorizontalScrollView;
import android.widget.Scroller;

/**
 * User can swipe left/right to show the hide menu
 * 
 * @author Tank
 * 
 */
public class SlideMenu extends ViewGroup {
    private final static int MAX_DURATION = 500;
    private static int STATUS_BAR_HEIGHT;

    public final static int FLAG_DIRECTION_LEFT = 1 << 0;
    public final static int FLAG_DIRECTION_RIGHT = 1 << 1;

    public final static int MODE_SLIDE_WINDOW = 1;
    public final static int MODE_SLIDE_CONTENT = 2;

    public final static int STATE_CLOSE = 1 << 0;
    public final static int STATE_OPEN_LEFT = 1 << 1;
    public final static int STATE_OPEN_RIGHT = 1 << 2;
    public final static int STATE_DRAG = 1 << 3;
    public final static int STATE_SCROLL = 1 << 4;
    public final static int STATE_OPEN_MASK = 6;

    private final static int POSITION_LEFT = -1;
    private final static int POSITION_MIDDLE = 0;
    private final static int POSITION_RIGHT = 1;
    private int mCurrentContentPosition;
    private int mCurrentState;

    private ScrollDetector mScrollDetector;

    private View mContent;
    private View mPrimaryMenu;
    private View mSecondaryMenu;

    private int mTouchSlop;

    private float mPressedX;
    private float mLastMotionX;
    private volatile int mCurrentContentOffset;

    private int mContentBoundsLeft;
    private int mContentBoundsRight;

    private boolean mIsTapContent;
    private Rect mContentHitRect;

    @ExportedProperty
    private Drawable mPrimaryShadowDrawable;
    @ExportedProperty
    private Drawable mSecondaryShadowDrawable;
    @ExportedProperty
    private float mPrimaryShadowWidth;
    @ExportedProperty
    private float mSecondaryShadowWidth;
    private int mSlideDirectionFlag;
    private boolean mIsPendingResolveSlideMode;

    private int mSlideMode = MODE_SLIDE_CONTENT;

    private int mWidth;
    private int mHeight;

    private OnSlideStateChangeListener mSlideStateChangeListener;

    private VelocityTracker mVelocityTracker;
    private Scroller mScroller;

    private static final Interpolator mInterpolator = new Interpolator() {
        public float getInterpolation(float t) {
            t -= 1.0f;
            return t * t * t * t * t + 1.0f;
        }
    };

    public SlideMenu(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        // we want to draw drop shadow of content
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        mVelocityTracker = VelocityTracker.obtain();
        mScroller = new Scroller(context, mInterpolator);
        mContentHitRect = new Rect();
        STATUS_BAR_HEIGHT = (int) getStatusBarHeight(context);
        setWillNotDraw(false);

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SlideMenu, defStyle, 0);

        // Set the shadow attributes
        setPrimaryShadowWidth(a.getDimension(R.styleable.SlideMenu_primaryShadowWidth, 30));
        setSecondaryShadowWidth(a.getDimension(R.styleable.SlideMenu_secondaryShadowWidth, 30));

        Drawable primaryShadowDrawable = a.getDrawable(R.styleable.SlideMenu_primaryShadowDrawable);
        if (null == primaryShadowDrawable) {
            primaryShadowDrawable = new GradientDrawable(Orientation.LEFT_RIGHT,
                    new int[] { Color.TRANSPARENT, Color.argb(99, 0, 0, 0) });
        }
        setPrimaryShadowDrawable(primaryShadowDrawable);

        Drawable secondaryShadowDrawable = a.getDrawable(R.styleable.SlideMenu_primaryShadowDrawable);
        if (null == secondaryShadowDrawable) {
            secondaryShadowDrawable = new GradientDrawable(Orientation.LEFT_RIGHT,
                    new int[] { Color.argb(99, 0, 0, 0), Color.TRANSPARENT });
        }
        setSecondaryShadowDrawable(secondaryShadowDrawable);

        mSlideDirectionFlag = a.getInt(R.styleable.SlideMenu_slideDirection,
                FLAG_DIRECTION_LEFT | FLAG_DIRECTION_RIGHT);
        setFocusable(true);
        setFocusableInTouchMode(true);
        a.recycle();
    }

    public SlideMenu(Context context, AttributeSet attrs) {
        this(context, attrs, R.attr.slideMenuStyle);
    }

    public SlideMenu(Context context) {
        this(context, null);
    }

    /**
     * Retrieve the height of status bar that defined in system
     * 
     * @param context
     * @return
     */
    public static float getStatusBarHeight(Context context) {
        Resources resources = context.getResources();
        int statusBarIdentifier = resources.getIdentifier("status_bar_height", "dimen", "android");
        if (0 != statusBarIdentifier) {
            return resources.getDimension(statusBarIdentifier);
        }
        return 0;
    }

    /**
     * Resolve the attribute slideMode
     */
    protected void resolveSlideMode() {
        final ViewGroup decorView = (ViewGroup) getRootView();
        final ViewGroup contentContainer = (ViewGroup) decorView.findViewById(android.R.id.content);
        final View content = mContent;
        if (null == decorView || null == content || 0 == getChildCount()) {
            return;
        }

        TypedValue value = new TypedValue();
        getContext().getTheme().resolveAttribute(android.R.attr.windowBackground, value, true);

        switch (mSlideMode) {
        case MODE_SLIDE_WINDOW: {
            // remove this view from parent
            removeViewFromParent(this);
            // copy the layoutparams of content
            LayoutParams contentLayoutParams = new LayoutParams(content.getLayoutParams());
            // remove content view from this view
            removeViewFromParent(content);
            // add content to layout root view
            contentContainer.addView(content);

            // get window with ActionBar
            View decorChild = decorView.getChildAt(0);
            decorChild.setBackgroundResource(0);
            removeViewFromParent(decorChild);
            addView(decorChild, contentLayoutParams);

            // add this view to root view
            decorView.addView(this);
            setBackgroundResource(value.resourceId);
        }
            break;
        case MODE_SLIDE_CONTENT: {
            // remove this view from decor view
            setBackgroundResource(0);
            removeViewFromParent(this);
            // get the origin content view from the content wrapper
            View originContent = contentContainer.getChildAt(0);
            // this is the decor child remove from decor view
            View decorChild = mContent;
            LayoutParams layoutParams = (LayoutParams) decorChild.getLayoutParams();
            // remove the origin content from content wrapper
            removeViewFromParent(originContent);
            // remove decor child from this view
            removeViewFromParent(decorChild);
            // restore the decor child to decor view
            decorChild.setBackgroundResource(value.resourceId);
            decorView.addView(decorChild);
            // add this view to content wrapper
            contentContainer.addView(this);
            // add the origin content to this view
            addView(originContent, layoutParams);
        }
            break;
        }
    }

    @Override
    public void addView(View child, int index, android.view.ViewGroup.LayoutParams params) {
        if (!(params instanceof LayoutParams)) {
            throw new IllegalArgumentException("LayoutParams must a instance of LayoutParams");
        }

        LayoutParams layoutParams = (LayoutParams) params;
        switch (layoutParams.role) {
        case LayoutParams.ROLE_CONTENT:
            mContent = child;
            break;
        case LayoutParams.ROLE_PRIMARY_MENU:
            mPrimaryMenu = child;
            break;
        case LayoutParams.ROLE_SECONDARY_MENU:
            mSecondaryMenu = child;
            break;
        default:
            return;
        }
        super.addView(child, index, params);
    }

    /**
     * Remove view child it's parent node, if the view does not have parent.
     * ignore
     * 
     * @param view
     */
    public static void removeViewFromParent(View view) {
        if (null == view) {
            return;
        }
        ViewGroup parent = (ViewGroup) view.getParent();
        if (null == parent) {
            return;
        }
        parent.removeView(view);
    }

    /**
     * Set the shadow drawable of left side
     * 
     * @param shadowDrawable
     */
    public void setPrimaryShadowDrawable(Drawable shadowDrawable) {
        mPrimaryShadowDrawable = shadowDrawable;
    }

    /**
     * Get the shadow drawable of left side
     * 
     * @return
     */
    public Drawable getPrimaryShadowDrawable() {
        return mPrimaryShadowDrawable;
    }

    /**
     * Get the shadow drawable of right side
     * 
     * @return
     */
    public Drawable getSecondaryShadowDrawable() {
        return mSecondaryShadowDrawable;
    }

    /**
     * Set the shadow drawable of right side
     * 
     * @param secondaryShadowDrawable
     */
    public void setSecondaryShadowDrawable(Drawable secondaryShadowDrawable) {
        this.mSecondaryShadowDrawable = secondaryShadowDrawable;
    }

    /**
     * Get the slide mode current specified
     * 
     * @return
     */
    public int getSlideMode() {
        return mSlideMode;
    }

    /**
     * Set the slide mode:<br/>
     * {@link #MODE_SLIDE_CONTENT} {@link #MODE_SLIDE_WINDOW}
     * 
     * @param slideMode
     */
    public void setSlideMode(int slideMode) {
        if (mSlideMode == slideMode) {
            return;
        }
        mSlideMode = slideMode;
        if (0 == getChildCount()) {
            mIsPendingResolveSlideMode = true;
        } else {
            resolveSlideMode();
        }
    }

    /**
     * Indicate this SlideMenu is open
     * 
     * @return true open, otherwise false
     */
    public boolean isOpen() {
        return (STATE_OPEN_MASK & mCurrentState) != 0;
    }

    /**
     * Open the SlideMenu
     * 
     * @param isSlideLeft
     * @param isAnimated
     */
    public void open(boolean isSlideLeft, boolean isAnimated) {
        if (isOpen()) {
            return;
        }

        int targetOffset = isSlideLeft ? mContentBoundsLeft : mContentBoundsRight;

        if (isAnimated) {
            smoothScrollContentTo(targetOffset);
        } else {
            mScroller.abortAnimation();
            setCurrentOffset(targetOffset);
        }
    }

    /**
     * Close the SlideMenu
     * 
     * @param isAnimated
     */
    public void close(boolean isAnimated) {
        if (STATE_CLOSE == mCurrentState) {
            return;
        }

        if (isAnimated) {
            smoothScrollContentTo(0);
        } else {
            mScroller.abortAnimation();
            setCurrentOffset(0);
        }
    }

    /**
     * Get current slide direction, {@link #FLAG_DIRECTION_LEFT},
     * {@link #FLAG_DIRECTION_RIGHT} or {@link #FLAG_DIRECTION_LEFT}|
     * {@link #FLAG_DIRECTION_RIGHT}
     * 
     * @return
     */
    public int getSlideDirection() {
        return mSlideDirectionFlag;
    }

    /**
     * Set slide direction
     * 
     * @param slideDirectionFlag
     */
    public void setSlideDirection(int slideDirectionFlag) {
        this.mSlideDirectionFlag = slideDirectionFlag;
    }

    /**
     * Set the listener to listen the state change and offset change
     * 
     * @return
     */
    public OnSlideStateChangeListener getOnSlideStateChangeListener() {
        return mSlideStateChangeListener;
    }

    /**
     * Get the current listener
     * 
     * @param slideStateChangeListener
     */
    public void setOnSlideStateChangeListener(OnSlideStateChangeListener slideStateChangeListener) {
        this.mSlideStateChangeListener = slideStateChangeListener;
    }

    /**
     * Get current state
     * 
     * @return
     */
    public int getCurrentState() {
        return mCurrentState;
    }

    /**
     * Set current state
     * 
     * @param currentState
     */
    protected void setCurrentState(int currentState) {
        if (null != mSlideStateChangeListener && currentState != mCurrentState) {
            mSlideStateChangeListener.onSlideStateChange(currentState);
        }
        this.mCurrentState = currentState;
    }

    /**
     * Equals invoke {@link #smoothScrollContentTo(int, float)} with 0 velocity
     * 
     * @param targetOffset
     */
    public void smoothScrollContentTo(int targetOffset) {
        smoothScrollContentTo(targetOffset, 0);
    }

    /**
     * Perform a smooth slide of content, the offset of content will limited to
     * menu width
     * 
     * @param targetOffset
     * @param velocity
     */
    public void smoothScrollContentTo(int targetOffset, float velocity) {
        setCurrentState(STATE_SCROLL);
        int distance = targetOffset - mCurrentContentOffset;
        velocity = Math.abs(velocity);
        int duration = 400;
        if (velocity > 0) {
            duration = 3 * Math.round(1000 * Math.abs(distance / velocity));
        }
        duration = Math.min(duration, MAX_DURATION);
        mScroller.abortAnimation();
        mScroller.startScroll(mCurrentContentOffset, 0, distance, 0, duration);
        invalidate();
    }

    /**
     * Get current {@link ScrollDetector}
     * 
     * @return
     */
    public ScrollDetector getScrollDetector() {
        return mScrollDetector;
    }

    /**
     * Set a {@link ScrollDetector} to detect whether dragable left/right, this is
     * useful for content with {@link ViewPager}, {@link HorizontalScrollView}
     * inside
     * 
     * @param scrollDetector
     */
    public void setScrollDetector(ScrollDetector scrollDetector) {
        this.mScrollDetector = scrollDetector;
    }

    private boolean isTapContent(float x, float y) {
        final View content = mContent;
        if (null != content) {
            content.getHitRect(mContentHitRect);
            return mContentHitRect.contains((int) x, (int) y);
        }
        return false;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        final float x = ev.getX();
        final float y = ev.getY();
        final int currentState = mCurrentState;
        if (STATE_DRAG == currentState || STATE_SCROLL == currentState) {
            return true;
        }
        switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            mPressedX = mLastMotionX = x;
            mIsTapContent = isTapContent(x, y);
            return isOpen() && mIsTapContent;
        case MotionEvent.ACTION_MOVE:
            float distance = x - mPressedX;
            if (Math.abs(distance) >= mTouchSlop && mIsTapContent) {
                if (!canScroll(this, (int) distance, (int) x, (int) y)) {
                    setCurrentState(STATE_DRAG);
                    return true;
                }
            }
        }

        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);
        final float x = event.getX();
        final float y = event.getY();
        final int currentState = mCurrentState;
        final boolean isTapContent = mIsTapContent;

        mVelocityTracker.addMovement(event);

        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            mPressedX = mLastMotionX = x;
            mIsTapContent = isTapContent(x, y);
            if (mIsTapContent) {
                mScroller.abortAnimation();
            }
            mVelocityTracker = VelocityTracker.obtain();
            break;
        case MotionEvent.ACTION_MOVE:
            if (Math.abs(x - mPressedX) >= mTouchSlop && isTapContent && currentState != STATE_DRAG) {
                setCurrentState(STATE_DRAG);
            }
            if (STATE_DRAG != currentState) {
                mLastMotionX = x;
                return false;
            }

            drag(mLastMotionX, x);
            mLastMotionX = x;
            break;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
        case MotionEvent.ACTION_OUTSIDE:
            if (STATE_DRAG == currentState) {
                mVelocityTracker.computeCurrentVelocity(1000);
                endDrag(x, mVelocityTracker.getXVelocity());
            } else if (isTapContent) {
                performContentClick();
            }
            mVelocityTracker.recycle();
            mIsTapContent = false;
            break;
        }

        return true;
    }

    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        if (KeyEvent.ACTION_UP == event.getAction()) {
            final boolean isOpen = isOpen();
            switch (event.getKeyCode()) {
            case KeyEvent.KEYCODE_BACK:
                if (isOpen) {
                    close(true);
                    return true;
                }
                break;
            case KeyEvent.KEYCODE_DPAD_LEFT:
                if (STATE_OPEN_LEFT == mCurrentState) {
                    close(true);
                    return true;
                } else if (!isOpen) {
                    open(true, true);
                    return true;
                }
                break;
            case KeyEvent.KEYCODE_DPAD_RIGHT:
                if (STATE_OPEN_RIGHT == mCurrentState) {
                    close(true);
                    return true;
                } else if (!isOpen) {
                    open(false, true);
                    return true;
                }
                break;
            }
        }
        return super.dispatchKeyEvent(event);
    }

    /**
     * Get current primary menu
     * 
     * @return
     */
    public View getPrimaryMenu() {
        return mPrimaryMenu;
    }

    /**
     * Get current secondary menu
     * 
     * @return
     */
    public View getSecondaryMenu() {
        return mSecondaryMenu;
    }

    /**
     * Perform click on the content
     */
    public void performContentClick() {
        if (isOpen()) {
            smoothScrollContentTo(0);
        }
    }

    protected void drag(float lastX, float x) {
        mCurrentContentOffset += (int) (x - lastX);
        setCurrentOffset(mCurrentContentOffset);
    }

    private void invalideMenuState() {
        mCurrentContentPosition = mCurrentContentOffset < 0 ? POSITION_LEFT
                : (mCurrentContentOffset == 0 ? POSITION_MIDDLE : POSITION_RIGHT);
        switch (mCurrentContentPosition) {
        case POSITION_LEFT:
            invalidateViewVisibility(mPrimaryMenu, View.INVISIBLE);
            invalidateViewVisibility(mSecondaryMenu, View.VISIBLE);
            break;
        case POSITION_MIDDLE:
            invalidateViewVisibility(mPrimaryMenu, View.INVISIBLE);
            invalidateViewVisibility(mSecondaryMenu, View.INVISIBLE);
            break;
        case POSITION_RIGHT:
            invalidateViewVisibility(mPrimaryMenu, View.VISIBLE);
            invalidateViewVisibility(mSecondaryMenu, View.INVISIBLE);
            break;
        }
    }

    @Override
    public boolean shouldDelayChildPressedState() {
        return false;
    }

    private void invalidateViewVisibility(View view, int visibility) {
        if (null != view && view.getVisibility() != visibility) {
            view.setVisibility(visibility);
        }
    }

    protected void endDrag(float x, float velocity) {
        // TODO Not use this to detect whether we should scroll the content
        // temporary
        final int currentContentOffset = mCurrentContentOffset;
        final int currentContentPosition = mCurrentContentPosition;
        boolean velocityMatched = Math.abs(velocity) > 400;
        switch (currentContentPosition) {
        case POSITION_LEFT:
            if ((velocity > 0 && velocityMatched) || (velocity <= 0 && !velocityMatched)) {
                smoothScrollContentTo(0, velocity);
            } else if ((velocity < 0 && velocityMatched) || (velocity >= 0 && !velocityMatched)) {
                smoothScrollContentTo(mContentBoundsLeft, velocity);
            }
            break;
        case POSITION_MIDDLE:
            setCurrentState(STATE_CLOSE);
            break;
        case POSITION_RIGHT:
            if ((velocity > 0 && velocityMatched) || (velocity <= 0 && !velocityMatched)) {
                smoothScrollContentTo(mContentBoundsRight, velocity);
            } else if ((velocity < 0 && velocityMatched) || (velocity >= 0 && !velocityMatched)) {
                smoothScrollContentTo(0, velocity);
            }
            break;
        }
    }

    private void setCurrentOffset(int currentOffset) {
        final int slideDirectionFlag = mSlideDirectionFlag;
        mCurrentContentOffset = Math
                .min((slideDirectionFlag & FLAG_DIRECTION_RIGHT) == FLAG_DIRECTION_RIGHT ? mContentBoundsRight : 0,
                        Math.max(currentOffset,
                                (slideDirectionFlag & FLAG_DIRECTION_LEFT) == FLAG_DIRECTION_LEFT
                                        ? mContentBoundsLeft
                                        : 0));
        if (null != mSlideStateChangeListener) {
            float slideOffsetPercent = 0;
            final int currentContentOffset = mCurrentContentOffset;
            if (0 < currentContentOffset) {
                slideOffsetPercent = currentContentOffset * 1.0f / mContentBoundsRight;
            } else if (0 > currentContentOffset) {
                slideOffsetPercent = -currentContentOffset * 1.0f / mContentBoundsLeft;
            }
            mSlideStateChangeListener.onSlideOffsetChange(slideOffsetPercent);
        }
        invalideMenuState();
        invalidate();
        requestLayout();
    }

    @Override
    public void computeScroll() {
        if (STATE_SCROLL == mCurrentState || isOpen()) {
            if (mScroller.computeScrollOffset()) {
                setCurrentOffset(mScroller.getCurrX());
            } else {
                setCurrentState(mCurrentContentOffset == 0 ? STATE_CLOSE
                        : (mCurrentContentOffset > 0 ? STATE_OPEN_LEFT : STATE_OPEN_RIGHT));
            }
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final int count = getChildCount();
        final int slideMode = mSlideMode;
        final int statusBarHeight = STATUS_BAR_HEIGHT;

        int maxChildWidth = 0, maxChildHeight = 0;
        for (int index = 0; index < count; index++) {
            View child = getChildAt(index);
            LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();
            switch (layoutParams.role) {
            case LayoutParams.ROLE_CONTENT:
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
                break;
            case LayoutParams.ROLE_PRIMARY_MENU:
            case LayoutParams.ROLE_SECONDARY_MENU:
                measureChild(child, widthMeasureSpec,
                        slideMode == MODE_SLIDE_WINDOW ? MeasureSpec.makeMeasureSpec(
                                MeasureSpec.getSize(heightMeasureSpec) - statusBarHeight,
                                MeasureSpec.getMode(heightMeasureSpec)) : heightMeasureSpec);
                break;
            }

            maxChildWidth = Math.max(maxChildWidth, child.getMeasuredWidth());
            maxChildHeight = Math.max(maxChildHeight, child.getMeasuredHeight());
        }
        maxChildWidth += getPaddingLeft() + getPaddingRight();
        maxChildHeight += getPaddingTop() + getPaddingBottom();

        setMeasuredDimension(resolveSize(maxChildWidth, widthMeasureSpec),
                resolveSize(maxChildHeight, heightMeasureSpec));
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        View parent = (View) getParent();
        if (android.R.id.content != parent.getId() && MODE_SLIDE_CONTENT == mSlideMode) {
            throw new IllegalStateException("SlidingMenu must be the root of layout");
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        final int count = getChildCount();
        final int paddingLeft = getPaddingLeft();
        final int paddingRight = getPaddingRight();
        final int paddingTop = getPaddingTop();
        final int statusBarHeight = mSlideMode == MODE_SLIDE_WINDOW ? STATUS_BAR_HEIGHT : 0;
        for (int index = 0; index < count; index++) {
            View child = getChildAt(index);
            final int measureWidth = child.getMeasuredWidth();
            final int measureHeight = child.getMeasuredHeight();
            LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();
            switch (layoutParams.role) {
            case LayoutParams.ROLE_CONTENT:
                // we should display the content in front of all other views
                child.bringToFront();
                child.layout(mCurrentContentOffset + paddingLeft, paddingTop,
                        paddingLeft + measureWidth + mCurrentContentOffset, paddingTop + measureHeight);
                break;
            case LayoutParams.ROLE_PRIMARY_MENU:
                mContentBoundsRight = measureWidth;
                child.layout(paddingLeft, statusBarHeight + paddingTop, paddingLeft + measureWidth,
                        statusBarHeight + paddingTop + measureHeight);
                break;
            case LayoutParams.ROLE_SECONDARY_MENU:
                mContentBoundsLeft = -measureWidth;
                child.layout(r - paddingRight - measureWidth, statusBarHeight + paddingTop, r - paddingRight,
                        statusBarHeight + t + measureHeight);
                break;
            default:
                continue;
            }
        }
    }

    protected final boolean canScroll(View v, int dx, int x, int y) {
        if (null == mScrollDetector) {
            return false;
        }

        if (v instanceof ViewGroup) {
            final ViewGroup viewGroup = (ViewGroup) v;
            final int scrollX = v.getScrollX();
            final int scrollY = v.getScrollY();

            final int childCount = viewGroup.getChildCount();
            for (int index = 0; index < childCount; index++) {
                View child = viewGroup.getChildAt(index);
                final int left = child.getLeft();
                final int top = child.getTop();
                if (x + scrollX >= left && x + scrollX < child.getRight() && y + scrollY >= top
                        && y + scrollY < child.getBottom()
                        && (mScrollDetector.isScrollable(child, dx, x + scrollX - left, y + scrollY - top)
                                || canScroll(child, dx, x + scrollX - left, y + scrollY - top))) {
                    return true;
                }
            }
        }

        return ViewCompat.canScrollHorizontally(v, -dx);
    }

    public float getPrimaryShadowWidth() {
        return mPrimaryShadowWidth;
    }

    public void setPrimaryShadowWidth(float primaryShadowWidth) {
        this.mPrimaryShadowWidth = primaryShadowWidth;
        invalidate();
    }

    public float getSecondaryShadowWidth() {
        return mSecondaryShadowWidth;
    }

    public void setSecondaryShadowWidth(float secondaryShadowWidth) {
        this.mSecondaryShadowWidth = secondaryShadowWidth;
        invalidate();
    }

    @Override
    public void draw(Canvas canvas) {
        super.draw(canvas);
        drawShadow(canvas);
    }

    private void drawShadow(Canvas canvas) {
        if (null == mContent) {
            return;
        }
        final int left = mContent.getLeft();
        final int width = mWidth;
        final int height = mHeight;
        mPrimaryShadowDrawable.setBounds((int) (left - mPrimaryShadowWidth), 0, left, height);
        mPrimaryShadowDrawable.draw(canvas);

        mSecondaryShadowDrawable.setBounds(left + width, 0, (int) (width + left + mSecondaryShadowWidth), height);
        mSecondaryShadowDrawable.draw(canvas);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = w;
        mHeight = h;

        if (mIsPendingResolveSlideMode) {
            resolveSlideMode();
        }
    }

    @Override
    public android.view.ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
        LayoutParams layoutParams = new LayoutParams(getContext(), attrs);
        return layoutParams;
    }

    @Override
    protected Parcelable onSaveInstanceState() {
        SavedState savedState = new SavedState(super.onSaveInstanceState());
        savedState.primaryShadowWidth = mPrimaryShadowWidth;
        savedState.secondaryShadaryWidth = mSecondaryShadowWidth;
        savedState.slideDirectionFlag = mSlideDirectionFlag;
        savedState.slideMode = mSlideMode;
        savedState.currentState = mCurrentState;
        savedState.currentContentOffset = mCurrentContentOffset;
        return savedState;
    }

    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        SavedState savedState = (SavedState) state;
        super.onRestoreInstanceState(savedState.getSuperState());
        mPrimaryShadowWidth = savedState.primaryShadowWidth;
        mSecondaryShadowWidth = savedState.secondaryShadaryWidth;
        mSlideDirectionFlag = savedState.slideDirectionFlag;
        setSlideMode(savedState.slideMode);
        mCurrentState = savedState.currentState;
        mCurrentContentOffset = savedState.currentContentOffset;

        invalideMenuState();
        requestLayout();
        invalidate();
    }

    public static class SavedState extends BaseSavedState {
        public float primaryShadowWidth;
        public float secondaryShadaryWidth;
        public int slideDirectionFlag;
        public int slideMode;
        public int currentState;
        public int currentContentOffset;

        SavedState(Parcelable superState) {
            super(superState);
        }

        private SavedState(Parcel in) {
            super(in);
            primaryShadowWidth = in.readFloat();
            secondaryShadaryWidth = in.readFloat();
            slideDirectionFlag = in.readInt();
            slideMode = in.readInt();
            currentState = in.readInt();
            currentContentOffset = in.readInt();
        }

        @Override
        public void writeToParcel(Parcel out, int flags) {
            super.writeToParcel(out, flags);
            out.writeFloat(primaryShadowWidth);
            out.writeFloat(secondaryShadaryWidth);
            out.writeInt(slideDirectionFlag);
            out.writeInt(slideMode);
            out.writeInt(currentState);
            out.writeInt(currentContentOffset);
        }

        public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
            @Override
            public SavedState createFromParcel(Parcel in) {
                return new SavedState(in);
            }

            @Override
            public SavedState[] newArray(int size) {
                return new SavedState[size];
            }
        };
    }

    /**
     * Add view role for {@link #SlidingMenu}
     * 
     * @author Tank
     * 
     */
    public static class LayoutParams extends MarginLayoutParams {
        public final static int ROLE_CONTENT = 0;
        public final static int ROLE_PRIMARY_MENU = 1;
        public final static int ROLE_SECONDARY_MENU = 2;

        public int role;

        public LayoutParams(Context context, AttributeSet attrs) {
            super(context, attrs);

            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SlideMenu_Layout, 0, 0);

            final int indexCount = a.getIndexCount();
            for (int index = 0; index < indexCount; index++) {
                switch (a.getIndex(index)) {
                case R.styleable.SlideMenu_Layout_layout_role:
                    role = a.getInt(R.styleable.SlideMenu_Layout_layout_role, -1);
                    break;
                }
            }

            switch (role) {
            // content should match whole SlidingMenu
            case ROLE_CONTENT:
                width = MATCH_PARENT;
                height = MATCH_PARENT;
                break;
            case ROLE_SECONDARY_MENU:
            case ROLE_PRIMARY_MENU:
                height = MATCH_PARENT;
                break;
            default:
                throw new IllegalArgumentException("You must specified a layout_role for this view");
            }
            a.recycle();
        }

        public LayoutParams(int width, int height) {
            super(width, height);
        }

        public LayoutParams(int width, int height, int role) {
            super(width, height);
            this.role = role;
        }

        public LayoutParams(android.view.ViewGroup.LayoutParams layoutParams) {
            super(layoutParams);

            if (layoutParams instanceof LayoutParams) {
                role = ((LayoutParams) layoutParams).role;
            }
        }
    }

    public interface OnSlideStateChangeListener {
        /**
         * Invoked when slide state change
         * 
         * @param slideState
         *            {@link SlideMenu#STATE_CLOSE},{@link SlideMenu#STATE_OPEN}
         *            ,{@link SlideMenu#STATE_DRAG},
         *            {@link SlideMenu#STATE_SCROLL}
         */
        public void onSlideStateChange(int slideState);

        /**
         * Invoked when slide offset change
         * 
         * @param offsetPercent
         *            negative means slide left, otherwise slide right
         */
        public void onSlideOffsetChange(float offsetPercent);
    }
}