com.pt.treasuretrash.widget.SlidingLayer.java Source code

Java tutorial

Introduction

Here is the source code for com.pt.treasuretrash.widget.SlidingLayer.java

Source

/*
 * SlidingLayer.java
 * 
 * Copyright (C) 2013 6 Wunderkinder GmbH.
 * 
 * @author      Jose L Ugia - @Jl_Ugia
 * @author      Antonio Consuegra - @aconsuegra
 * @author      Cesar Valiente - @CesarValiente
 * @version     1.0
 *
 * 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.pt.treasuretrash.widget;

import java.lang.reflect.Method;
import java.util.Random;

import com.josh.treasuretrash.R;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Point;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.VelocityTrackerCompat;
import android.support.v4.view.ViewConfigurationCompat;
import android.util.AttributeSet;
import android.util.FloatMath;
import android.view.Display;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.WindowManager;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import android.widget.Scroller;

public class SlidingLayer extends FrameLayout {

    private static final String KEY_IS_OPEN = "is_open";

    /**
     * Default value for the position of the layer. STICK_TO_AUTO shall inspect
     * the container and choose a stick mode depending on the position of the
     * layour (ie.: layout is positioned on the right = STICK_TO_RIGHT).
     */
    public static final int STICK_TO_AUTO = 0;

    /**
     * Special value for the position of the layer. STICK_TO_RIGHT means that
     * the view shall be attached to the right side of the screen, and come from
     * there into the viewable area.
     */
    public static final int STICK_TO_RIGHT = -1;

    /**
     * Special value for the position of the layer. STICK_TO_LEFT means that the
     * view shall be attached to the left side of the screen, and come from
     * there into the viewable area.
     */
    public static final int STICK_TO_LEFT = -2;

    /**
     * Special value for the position of the layer. STICK_TO_MIDDLE means that
     * the view will stay attached trying to be in the middle of the screen and
     * allowing dismissing both to right and left side.
     */
    public static final int STICK_TO_MIDDLE = -3;

    /**
     * Special value for the position of the layer. STICK_TO_TOP means that the
     * view will stay attached to the top part of the screen, and come from
     * there into the viewable area.
     */
    public static final int STICK_TO_TOP = -4;

    /**
     * Special value for the position of the layer. STICK_TO_BOTTOM means that
     * the view will stay attached to the bottom part of the screen, and come
     * from there into the viewable area.
     */
    public static final int STICK_TO_BOTTOM = -5;

    private static final int MAX_SCROLLING_DURATION = 600; // in ms
    private static final int MIN_DISTANCE_FOR_FLING = 25; // in dip
    private static final Interpolator sMenuInterpolator = new Interpolator() {
        @Override
        public float getInterpolation(float t) {
            t -= 1.0f;
            return (float) Math.pow(t, 5) + 1.0f;
        }
    };

    /**
     * Sentinel value for no current active pointer. Used by
     * {@link #mActivePointerId}.
     */
    private static final int INVALID_POINTER = -1;
    protected int mActivePointerId = INVALID_POINTER;
    protected VelocityTracker mVelocityTracker;
    protected int mMaximumVelocity;

    private Random mRandom;
    protected Bundle mState;

    private Scroller mScroller;

    private int mShadowWidth;
    private Drawable mShadowDrawable;
    private boolean mForceLayout;

    /**
     * The with of the panel when closed
     */
    private int mOffsetWidth;

    private boolean mDrawingCacheEnabled;
    private int mScreenSide = STICK_TO_AUTO;

    /**
     * If the user taps the layer then we will close it if enabled.
     */
    private boolean closeOnTapEnabled = true;

    /**
     * If the user taps the offset then we will open it if enabled.
     */
    private boolean openOnTapEnabled = true;

    private boolean mEnabled = true;
    private boolean mSlidingFromShadowEnabled = true;
    private boolean mIsDragging;
    private boolean mIsUnableToDrag;
    private int mTouchSlop;

    private float mLastX = -1;
    private float mLastY = -1;

    private float mInitialX = -1;
    private float mInitialY = -1;

    private boolean mIsOpen;
    private boolean mScrolling;

    private OnInteractListener mOnInteractListener;

    private int mMinimumVelocity;
    private int mFlingDistance;
    private boolean mLastTouchAllowed = false;

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

    public SlidingLayer(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    /**
     * Constructor for the sliding layer.<br>
     * By default this panel will
     * <ol>
     * <li>{@link #setStickTo(int)} with param {@link #STICK_TO_AUTO}</li>
     * <li>Use no shadow drawable. (i.e. with width of 0)</li>
     * <li>Close when the panel is tapped</li>
     * <li>Open when the offset is tapped, but will have an offset of 0</li>
     * </ol>
     * 
     * @param context
     *            a reference to an existing context
     * @param attrs
     *            attribute set constructed from attributes set in android .xml
     *            file
     * @param defStyle
     *            style res id
     */
    public SlidingLayer(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        // Style
        final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SlidingLayer);

        // Set the side of the screen
        setStickTo(ta.getInt(R.styleable.SlidingLayer_stickTo, STICK_TO_AUTO));

        // Sets the shadow drawable
        int shadowRes = ta.getResourceId(R.styleable.SlidingLayer_shadowDrawable, -1);
        if (shadowRes != -1) {
            setShadowDrawable(shadowRes);
        }

        // Sets the shadow width
        setShadowWidth((int) ta.getDimension(R.styleable.SlidingLayer_shadowWidth, 0));

        // Sets the ability to close the layer by tapping in any empty space
        closeOnTapEnabled = ta.getBoolean(R.styleable.SlidingLayer_closeOnTapEnabled, true);
        // Sets the ability to open the layout by tapping on any of the exposed
        // closed layer
        openOnTapEnabled = ta.getBoolean(R.styleable.SlidingLayer_openOnTapEnabled, true);

        // How much of the view sticks out when closed
        setOffsetWidth(ta.getDimensionPixelOffset(R.styleable.SlidingLayer_offsetWidth, 0));

        ta.recycle();

        init();
    }

    private void init() {
        setWillNotDraw(false);
        setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
        setFocusable(true);
        final Context context = getContext();
        mScroller = new Scroller(context, sMenuInterpolator);
        final ViewConfiguration configuration = ViewConfiguration.get(context);
        mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
        mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();

        final float density = context.getResources().getDisplayMetrics().density;
        mFlingDistance = (int) (MIN_DISTANCE_FOR_FLING * density);

        mRandom = new Random();
    }

    /**
     * Returns whether the panel is open or not.
     * 
     * @return returns true if the panel is open, false otherwise. Please note
     *         that if the panel was opened with smooth animation this method is
     *         not guaranteed to return true. This method will only return true
     *         after the panel has completely opened.
     */
    public boolean isOpened() {
        return mIsOpen;
    }

    public void openLayer(boolean smoothAnim) {
        openLayer(smoothAnim, false);
    }

    private void openLayer(boolean smoothAnim, boolean forceOpen) {
        switchLayer(true, smoothAnim, forceOpen, 0, 0);
    }

    public void closeLayer(boolean smoothAnim) {
        closeLayer(smoothAnim, false);
    }

    private void closeLayer(boolean smoothAnim, boolean forceClose) {
        switchLayer(false, smoothAnim, forceClose, 0, 0);
    }

    private void switchLayer(boolean open, boolean smoothAnim, boolean forceSwitch) {
        switchLayer(open, smoothAnim, forceSwitch, 0, 0);
    }

    private void switchLayer(final boolean open, final boolean smoothAnim, final boolean forceSwitch,
            final int velocityX, final int velocityY) {
        if (!forceSwitch && open == mIsOpen) {
            setDrawingCacheEnabled(false);
            return;
        }
        if (open) {
            if (mOnInteractListener != null) {
                mOnInteractListener.onOpen();
            }
        } else {
            if (mOnInteractListener != null) {
                mOnInteractListener.onClose();
            }
        }

        mIsOpen = open;

        // Get translation values
        float tx = mLastX - getWidth() / 2;
        float ty = mLastY - getHeight() / 2;

        // Get boolean for velocity check
        boolean noVelocityInStickToMidle = mScreenSide == STICK_TO_MIDDLE && Math.abs(velocityX) < mMinimumVelocity
                && Math.abs(velocityY) < mMinimumVelocity;

        // Follow velocity or translation depending on the case
        int dx = noVelocityInStickToMidle ? (int) tx : velocityX;
        int dy = noVelocityInStickToMidle ? (int) ty : velocityY;

        final int pos[] = getDestScrollPos(dx, dy);

        if (smoothAnim) {
            smoothScrollTo(pos[0], pos[1], Math.max(velocityX, velocityY) / 1000);
        } else {
            completeScroll();
            scrollTo(pos[0], pos[1]);
        }
    }

    /**
     * Sets the listener to be invoked after a switch change
     * {@link OnInteractListener}.
     * 
     * @param listener
     *            Listener to set
     */
    public void setOnInteractListener(OnInteractListener listener) {
        mOnInteractListener = listener;
    }

    /**
     * Sets the shadow of the width which will be included within the view by
     * using padding since it's on the left of the view in this case
     * 
     * @param shadowWidth
     *            Desired width of the shadow
     * @see #getShadowWidth()
     * @see #setShadowDrawable(Drawable)
     * @see #setShadowDrawable(int)
     */
    public void setShadowWidth(final int shadowWidth) {
        mShadowWidth = shadowWidth;
        invalidate(getLeft(), getTop(), getRight(), getBottom());
    }

    /**
     * Sets the shadow width by the value of a resource.
     * 
     * @param resId
     *            The dimension resource id to be set as the shadow width.
     */
    public void setShadowWidthRes(int resId) {
        setShadowWidth((int) getResources().getDimension(resId));
    }

    /**
     * Return the current with of the shadow.
     * 
     * @return The size of the shadow in pixels
     */
    public int getShadowWidth() {
        return mShadowWidth;
    }

    /**
     * Sets a drawable that will be used to create the shadow for the layer.
     * 
     * @param d
     *            Drawable append as a shadow
     */
    public void setShadowDrawable(final Drawable d) {
        mShadowDrawable = d;
        refreshDrawableState();
        setWillNotDraw(false);
        invalidate(getLeft(), getTop(), getRight(), getBottom());
    }

    /**
     * Sets a drawable resource that will be used to create the shadow for the
     * layer.
     * 
     * @param resId
     *            Resource ID of a drawable
     */
    public void setShadowDrawable(int resId) {
        setShadowDrawable(getContext().getResources().getDrawable(resId));
    }

    /**
     * Sets the offset width of the panel. How much sticks out when off screen.
     * 
     * @param offsetWidth
     *            Width of the offset in pixels
     * @see #getOffsetWidth()
     */
    public void setOffsetWidth(int offsetWidth) {
        mOffsetWidth = offsetWidth;
        invalidate(getLeft(), getTop(), getRight(), getBottom());
    }

    /**
     * 
     * @return returns the number of pixels that are visible when the panel is
     *         closed
     */
    public int getOffsetWidth() {
        return mOffsetWidth;
    }

    @Override
    protected boolean verifyDrawable(Drawable who) {
        return super.verifyDrawable(who) || who == mShadowDrawable;
    }

    @Override
    protected void drawableStateChanged() {
        super.drawableStateChanged();
        final Drawable d = mShadowDrawable;
        if (d != null && d.isStateful()) {
            d.setState(getDrawableState());
        }
    }

    public boolean isSlidingEnabled() {
        return mEnabled;
    }

    public void setSlidingEnabled(boolean _enabled) {
        mEnabled = _enabled;
    }

    public boolean isSlidingFromShadowEnabled() {
        return mSlidingFromShadowEnabled;
    }

    public void setSlidingFromShadowEnabled(boolean _slidingShadow) {
        mSlidingFromShadowEnabled = _slidingShadow;
    }

    @Override
    protected Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();
        SavedState state = new SavedState(superState);
        if (mState == null) {
            mState = new Bundle();
        }
        mState.putBoolean(KEY_IS_OPEN, isOpened());
        state.mState = mState;
        return state;
    }

    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        SavedState savedState = (SavedState) state;
        super.onRestoreInstanceState(savedState.getSuperState());
        restoreState(savedState.mState);
    }

    public void restoreState(Parcelable in) {
        mState = (Bundle) in;
        boolean isOpened = mState.getBoolean(KEY_IS_OPEN);
        if (isOpened) {
            openLayer(true);
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {

        if (!mEnabled) {
            return false;
        }

        final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;

        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
            mIsDragging = false;
            mIsUnableToDrag = false;
            mActivePointerId = INVALID_POINTER;
            if (mVelocityTracker != null) {
                mVelocityTracker.recycle();
                mVelocityTracker = null;
            }
            return false;
        }

        if (action != MotionEvent.ACTION_DOWN) {
            if (mIsDragging) {
                return true;
            } else if (mIsUnableToDrag) {
                return false;
            }
        }

        switch (action) {
        case MotionEvent.ACTION_MOVE:
            final int activePointerId = mActivePointerId;
            if (activePointerId == INVALID_POINTER) {
                break;
            }

            final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId);
            if (pointerIndex == -1) {
                mActivePointerId = INVALID_POINTER;
                break;
            }

            final float x = MotionEventCompat.getX(ev, pointerIndex);
            final float dx = x - mLastX;
            final float xDiff = Math.abs(dx);
            final float y = MotionEventCompat.getY(ev, pointerIndex);
            final float dy = y - mLastY;
            final float yDiff = Math.abs(y - mLastY);

            if (xDiff > mTouchSlop && xDiff > yDiff && allowDragingX(dx, mInitialX)) {
                mIsDragging = true;
                mLastX = x;
                setDrawingCacheEnabled(true);
            } else if (yDiff > mTouchSlop && yDiff > xDiff && allowDragingY(dy, mInitialY)) {
                mIsDragging = true;
                mLastY = y;
                setDrawingCacheEnabled(true);
            }
            break;

        case MotionEvent.ACTION_DOWN:
            mActivePointerId = ev.getAction() & (Build.VERSION.SDK_INT >= 8 ? MotionEvent.ACTION_POINTER_INDEX_MASK
                    : MotionEventCompat.ACTION_POINTER_INDEX_MASK);
            mLastX = mInitialX = MotionEventCompat.getX(ev, mActivePointerId);
            mLastY = mInitialY = MotionEventCompat.getY(ev, mActivePointerId);
            if (allowSlidingFromHereX(ev, mInitialX)) {
                mIsDragging = false;
                mIsUnableToDrag = false;
                // If nobody else got the focus we use it to close the layer
                return super.onInterceptTouchEvent(ev);
            } else if (allowSlidingFromHereY(ev, mInitialY)) {
                mIsDragging = false;
                mIsUnableToDrag = false;
                // If nobody else got the focus we use it to close the layer
                return super.onInterceptTouchEvent(ev);
            } else {
                mIsUnableToDrag = true;
            }
            break;
        case MotionEventCompat.ACTION_POINTER_UP:
            onSecondaryPointerUp(ev);
            break;
        }

        if (!mIsDragging) {
            if (mVelocityTracker == null) {
                mVelocityTracker = VelocityTracker.obtain();
            }
            mVelocityTracker.addMovement(ev);
        }

        return mIsDragging;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        // if (!mEnabled || !mIsDragging && !mLastTouchAllowed
        // && !allowSlidingFromHereX(ev, mInitialX)
        // && !allowSlidingFromHereY(ev, mInitialY)) {
        // return false;
        // }
        //
        // final int action = ev.getAction();
        //
        // if (action == MotionEvent.ACTION_UP
        // || action == MotionEvent.ACTION_CANCEL
        // || action == MotionEvent.ACTION_OUTSIDE) {
        // mLastTouchAllowed = false;
        // } else {
        // mLastTouchAllowed = true;
        // }
        //
        // if (mVelocityTracker == null) {
        // mVelocityTracker = VelocityTracker.obtain();
        // }
        // mVelocityTracker.addMovement(ev);
        //
        // switch (action & MotionEventCompat.ACTION_MASK) {
        // case MotionEvent.ACTION_DOWN:
        // completeScroll();
        //
        // // Remember where the motion event started
        // mLastX = mInitialX = ev.getX();
        // mLastY = mInitialY = ev.getY();
        // mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
        // break;
        // case MotionEvent.ACTION_MOVE:
        // if (!mIsDragging) {
        // final int pointerIndex = MotionEventCompat.findPointerIndex(ev,
        // mActivePointerId);
        // if (pointerIndex == -1) {
        // mActivePointerId = INVALID_POINTER;
        // break;
        // }
        // final float x = MotionEventCompat.getX(ev, pointerIndex);
        // final float xDiff = Math.abs(x - mLastX);
        // final float y = MotionEventCompat.getY(ev, pointerIndex);
        // final float yDiff = Math.abs(y - mLastY);
        // if (xDiff > mTouchSlop && xDiff > yDiff) {
        // mIsDragging = true;
        // mLastX = x;
        // setDrawingCacheEnabled(true);
        // } else if (yDiff > mTouchSlop && yDiff > xDiff) {
        // mIsDragging = true;
        // mLastY = y;
        // setDrawingCacheEnabled(true);
        // }
        // }
        // if (mIsDragging) {
        // // Scroll to follow the motion event
        // final int activePointerIndex = MotionEventCompat
        // .findPointerIndex(ev, mActivePointerId);
        // if (activePointerIndex == -1) {
        // mActivePointerId = INVALID_POINTER;
        // break;
        // }
        // final float x = MotionEventCompat.getX(ev, activePointerIndex);
        // final float y = MotionEventCompat.getY(ev, activePointerIndex);
        // final float deltaX = mLastX - x;
        // final float deltaY = mLastY - y;
        // mLastX = x;
        // mLastY = y;
        // final float oldScrollX = getScrollX();
        // final float oldScrollY = getScrollY();
        // float scrollX = oldScrollX + deltaX;
        // float scrollY = oldScrollY + deltaY;
        //
        // // Log.d("Layer", String.format("Layer scrollX[%f],scrollY[%f]",
        // // scrollX, scrollY));
        // final float leftBound, rightBound;
        // final float bottomBound, topBound;
        // switch (mScreenSide) {
        // case STICK_TO_LEFT:
        // topBound = bottomBound = rightBound = 0;
        // leftBound = getWidth(); // How far left we can scroll
        // break;
        // case STICK_TO_MIDDLE:
        // topBound = getHeight();
        // bottomBound = -getHeight();
        // leftBound = getWidth();
        // rightBound = -getWidth();
        // break;
        // case STICK_TO_RIGHT:
        // rightBound = -getWidth();
        // topBound = bottomBound = leftBound = 0;
        // break;
        // case STICK_TO_TOP:
        // topBound = getHeight();
        // bottomBound = rightBound = leftBound = 0;
        // break;
        // case STICK_TO_BOTTOM:
        // topBound = rightBound = leftBound = 0;
        // bottomBound = -getHeight();
        // break;
        // default:
        // topBound = bottomBound = rightBound = leftBound = 0;
        // break;
        // }
        // if (scrollX > leftBound) {
        // scrollX = leftBound;
        // } else if (scrollX < rightBound) {
        // scrollX = rightBound;
        // }
        // if (scrollY > topBound) {
        // scrollY = topBound;
        // } else if (scrollY < bottomBound) {
        // scrollY = bottomBound;
        // }
        //
        // // Keep the precision
        // mLastX += scrollX - (int) scrollX;
        // mLastY += scrollY - (int) scrollY;
        // scrollTo((int) scrollX, (int) scrollY);
        // }
        // break;
        // case MotionEvent.ACTION_UP:
        // if (mIsDragging) {
        // final VelocityTracker velocityTracker = mVelocityTracker;
        // velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
        // final int initialVelocityX = (int) VelocityTrackerCompat
        // .getXVelocity(velocityTracker, mActivePointerId);
        // final int initialVelocityY = (int) VelocityTrackerCompat
        // .getYVelocity(velocityTracker, mActivePointerId);
        // final int scrollX = getScrollX();
        // final int scrollY = getScrollY();
        // final int activePointerIndex = MotionEventCompat
        // .findPointerIndex(ev, mActivePointerId);
        // final float x = MotionEventCompat.getX(ev, activePointerIndex);
        // final float y = MotionEventCompat.getY(ev, activePointerIndex);
        // final int totalDeltaX = (int) (x - mInitialX);
        // final int totalDeltaY = (int) (y - mInitialY);
        //
        // boolean nextStateOpened = determineNextStateOpened(mIsOpen,
        // scrollX, scrollY, initialVelocityX, initialVelocityY,
        // totalDeltaX, totalDeltaY);
        // switchLayer(nextStateOpened, true, true, initialVelocityX,
        // initialVelocityY);
        //
        // mActivePointerId = INVALID_POINTER;
        // endDrag();
        // } else if (mIsOpen && closeOnTapEnabled) {
        // closeLayer(true);
        // } else if (!mIsOpen && openOnTapEnabled) {
        // openLayer(true);
        // }
        // break;
        // case MotionEvent.ACTION_CANCEL:
        // if (mIsDragging) {
        // switchLayer(mIsOpen, true, true);
        // mActivePointerId = INVALID_POINTER;
        // endDrag();
        // }
        // break;
        // case MotionEventCompat.ACTION_POINTER_DOWN: {
        // final int index = MotionEventCompat.getActionIndex(ev);
        // mLastX = MotionEventCompat.getX(ev, index);
        // mLastY = MotionEventCompat.getY(ev, index);
        // mActivePointerId = MotionEventCompat.getPointerId(ev, index);
        // break;
        // }
        // case MotionEventCompat.ACTION_POINTER_UP:
        // onSecondaryPointerUp(ev);
        // mLastX = MotionEventCompat.getX(ev,
        // MotionEventCompat.findPointerIndex(ev, mActivePointerId));
        // mLastY = MotionEventCompat.getY(ev,
        // MotionEventCompat.findPointerIndex(ev, mActivePointerId));
        // break;
        // }
        // if (mActivePointerId == INVALID_POINTER) {
        // mLastTouchAllowed = false;
        // }
        // return true;

        // Prevent swiping left to right for closing the slider.
        getParent().requestDisallowInterceptTouchEvent(true);
        return false;
    }

    private boolean allowSlidingFromHereX(final MotionEvent ev, final float initialX) {
        switch (mScreenSide) {
        case STICK_TO_LEFT:
        case STICK_TO_RIGHT:
        case STICK_TO_MIDDLE:
            if (mIsOpen) {
                return true;
            }
            if (!mIsOpen && mOffsetWidth > 0) {
                switch (mScreenSide) {
                case STICK_TO_LEFT:
                    return initialX <= mOffsetWidth;
                case STICK_TO_RIGHT:
                    return initialX >= getWidth() - mOffsetWidth;
                }
            }
        default:
            return false;
        }
    }

    private boolean allowSlidingFromHereY(final MotionEvent ev, final float initialY) {
        switch (mScreenSide) {
        case STICK_TO_TOP:
        case STICK_TO_BOTTOM:
        case STICK_TO_MIDDLE:
            if (mIsOpen) {
                return true;
            }
            if (!mIsOpen && mOffsetWidth > 0) {
                switch (mScreenSide) {
                case STICK_TO_TOP:
                    return initialY <= mOffsetWidth;
                case STICK_TO_BOTTOM:
                    return initialY >= getHeight() - mOffsetWidth;
                }
            }
        default:
            return false;
        }
    }

    /**
     * Checks if the touch event is valid for dragging the view.
     * 
     * @param dx
     *            changed in delta from the initialX
     * @param initialX
     *            where the touch event started.
     * @return true if you can drag this view, false otherwise
     */
    private boolean allowDragingX(final float dx, final float initialX) {
        if (mIsOpen && getLeft() <= initialX || getRight() >= initialX) {
            switch (mScreenSide) {
            case STICK_TO_RIGHT:
                return dx > 0;
            case STICK_TO_LEFT:
                return dx < 0;
            case STICK_TO_MIDDLE:
                return dx != 0;
            }
        }
        if (!mIsOpen && mOffsetWidth > 0 && dx > 0) {
            switch (mScreenSide) {
            case STICK_TO_LEFT:
                return initialX <= mOffsetWidth && dx > 0;
            case STICK_TO_RIGHT:
                return initialX >= getWidth() - mOffsetWidth && dx < 0;
            case STICK_TO_MIDDLE:
                return dx != 0;
            }
        }
        return false;
    }

    private boolean allowDragingY(final float dy, final float initialY) {
        if (mIsOpen && getTop() <= initialY || getBottom() >= initialY) {
            switch (mScreenSide) {
            case STICK_TO_BOTTOM:
                return mIsOpen && dy > 0;
            case STICK_TO_TOP:
                return mIsOpen && dy < 0;
            case STICK_TO_MIDDLE:
                return mIsOpen && dy != 0;
            }
        }
        if (!mIsOpen && mOffsetWidth > 0 && dy > 0) {
            switch (mScreenSide) {
            case STICK_TO_TOP:
                return initialY <= mOffsetWidth && dy > 0;
            case STICK_TO_BOTTOM:
                return initialY >= getHeight() - mOffsetWidth && dy < 0;
            case STICK_TO_MIDDLE:
                return dy != 0;
            }
        }
        return false;
    }

    /**
     * Based on the current state, position and velocity of the layer we
     * calculate what the next state should be.
     * 
     * @param currentState
     * @param swipeOffsetX
     * @param swipeOffsetY
     * @param velocityX
     * @param velocityY
     * @param deltaX
     * @param deltaY
     * @return true means we should open it, false close it.
     */
    private boolean determineNextStateOpened(final boolean currentState, final float swipeOffsetX,
            final float swipeOffsetY, final int velocityX, final int velocityY, final int deltaX,
            final int deltaY) {
        final boolean targetState;
        final boolean calcX;
        final boolean calcY;

        // Work out which velocity we should listen to.
        switch (mScreenSide) {
        case STICK_TO_TOP:
        case STICK_TO_BOTTOM:
            calcY = true;
            calcX = false;
            break;
        case STICK_TO_RIGHT:
        case STICK_TO_LEFT:
            calcX = true;
            calcY = false;
            break;
        case STICK_TO_MIDDLE:
            calcX = calcY = true;
            break;
        default:
            calcX = calcY = false;
            break;
        }

        if (calcX && Math.abs(deltaX) > mFlingDistance && Math.abs(velocityX) > mMinimumVelocity) {

            targetState = mScreenSide == STICK_TO_RIGHT && velocityX <= 0
                    || mScreenSide == STICK_TO_LEFT && velocityX > 0;

        } else if (calcY && Math.abs(deltaY) > mFlingDistance && Math.abs(velocityY) > mMinimumVelocity) {

            targetState = mScreenSide == STICK_TO_BOTTOM && velocityY <= 0
                    || mScreenSide == STICK_TO_TOP && velocityY > 0;

        } else {
            final int w = getWidth();
            final int h = getHeight();

            switch (mScreenSide) {
            case STICK_TO_RIGHT:
                targetState = swipeOffsetX > -w / 2;
                break;
            case STICK_TO_BOTTOM:
                targetState = swipeOffsetY > -h / 2;
                break;
            case STICK_TO_LEFT:
                targetState = swipeOffsetX < w / 2;
                break;
            case STICK_TO_TOP:
                targetState = swipeOffsetY < h / 2;
                break;
            case STICK_TO_MIDDLE:
                targetState = Math.abs(swipeOffsetX) < w / 2 && Math.abs(swipeOffsetY) < h / 2;
                break;
            default:
                targetState = true;
            }
        }

        return targetState;
    }

    /**
     * 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) {
            setDrawingCacheEnabled(false);
            return;
        }
        int sx = getScrollX();
        int sy = getScrollY();
        int dx = x - sx;
        int dy = y - sy;
        if (dx == 0 && dy == 0) {
            completeScroll();
            if (mIsOpen) {
                if (mOnInteractListener != null) {
                    mOnInteractListener.onOpened();
                }
            } else {
                if (mOnInteractListener != null) {
                    mOnInteractListener.onClosed();
                }
            }
            return;
        }

        setDrawingCacheEnabled(true);
        mScrolling = true;

        final int width = getWidth();
        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 {
            duration = MAX_SCROLLING_DURATION;
        }
        duration = Math.min(duration, MAX_SCROLLING_DURATION);

        mScroller.startScroll(sx, sy, dx, dy, duration);
        invalidate();
    }

    // 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 FloatMath.sin(f);
    }

    private void endDrag() {
        mIsDragging = false;
        mIsUnableToDrag = false;
        mLastTouchAllowed = false;

        if (mVelocityTracker != null) {
            mVelocityTracker.recycle();
            mVelocityTracker = null;
        }
    }

    @Override
    public void setDrawingCacheEnabled(boolean enabled) {

        if (mDrawingCacheEnabled != enabled) {
            super.setDrawingCacheEnabled(enabled);
            mDrawingCacheEnabled = enabled;

            final int l = getChildCount();
            for (int i = 0; i < l; i++) {
                final View child = getChildAt(i);
                if (child.getVisibility() != GONE) {
                    child.setDrawingCacheEnabled(enabled);
                }
            }
        }
    }

    private void onSecondaryPointerUp(MotionEvent ev) {
        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;
            mLastX = MotionEventCompat.getX(ev, newPointerIndex);
            mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
            if (mVelocityTracker != null) {
                mVelocityTracker.clear();
            }
        }
    }

    private void completeScroll() {
        boolean needPopulate = mScrolling;
        if (needPopulate) {
            // Done with scroll, no longer want to cache view drawing.
            setDrawingCacheEnabled(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 (mIsOpen) {
                if (mOnInteractListener != null) {
                    mOnInteractListener.onOpened();
                }
            } else {
                if (mOnInteractListener != null) {
                    mOnInteractListener.onClosed();
                }
            }
        }
        mScrolling = false;
    }

    /**
     * Sets the default location where the SlidingLayer will appear
     * 
     * @param screenSide
     *            The location where the Sliding layer will appear. Possible
     *            values are {@link #STICK_TO_AUTO}, {@link #STICK_TO_BOTTOM},
     *            {@link #STICK_TO_LEFT}, {@link #STICK_TO_MIDDLE}
     *            {@link #STICK_TO_RIGHT}, {@link #STICK_TO_TOP}
     */
    public void setStickTo(int screenSide) {

        if (screenSide != STICK_TO_AUTO) {
            mForceLayout = true;
        }

        mScreenSide = screenSide;
        closeLayer(false, true);
    }

    /**
     * If parameter is set to <code>true</code>, whenever the
     * <code>SlidingLayer</code> is tapped and the SlidingLayer is opened, it
     * will attempt to close. If parameter is set to <code>false</code>, then
     * tapping the <code>SlidingLayer</code> will do nothing
     * 
     * @param _closeOnTapEnabled
     */
    public void setCloseOnTapEnabled(boolean _closeOnTapEnabled) {
        closeOnTapEnabled = _closeOnTapEnabled;
    }

    /**
     * Given that there is a visible offset and it is tapped, if the parameter
     * is set to true it will attempt to open the <code>SlidingLayer</code>. If
     * parameter is false, tapping a visible offset will yield no result.
     * 
     * @param _openOnTapEnabled
     */
    public void setOpenOnTapEnabled(boolean _openOnTapEnabled) {
        openOnTapEnabled = _openOnTapEnabled;
    }

    @SuppressWarnings("deprecation")
    private int getScreenSideAuto(int newLeft, int newRight) {

        int newScreenSide;

        if (mScreenSide == STICK_TO_AUTO) {
            int screenWidth;
            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);
                screenWidth = parameter.x;
            } catch (Exception e) {
                screenWidth = display.getWidth();
            }

            boolean boundToLeftBorder = newLeft == 0;
            boolean boundToRightBorder = newRight == screenWidth;

            if (boundToLeftBorder == boundToRightBorder
                    && getLayoutParams().width == android.view.ViewGroup.LayoutParams.MATCH_PARENT) {
                newScreenSide = STICK_TO_MIDDLE;
            } else if (boundToLeftBorder) {
                newScreenSide = STICK_TO_LEFT;
            } else {
                newScreenSide = STICK_TO_RIGHT;
            }
        } else {
            newScreenSide = mScreenSide;
        }

        return newScreenSide;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        int width = getDefaultSize(0, widthMeasureSpec);
        int height = getDefaultSize(0, heightMeasureSpec);
        setMeasuredDimension(width, height);

        super.onMeasure(getChildMeasureSpec(widthMeasureSpec, 0, width),
                getChildMeasureSpec(heightMeasureSpec, 0, height));
    }

    @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) {
            boolean fix;
            switch (mScreenSide) {
            case STICK_TO_LEFT:
            case STICK_TO_RIGHT:
                fix = w != oldw;
                break;
            case STICK_TO_TOP:
            case STICK_TO_BOTTOM:
                fix = h != oldh;
                break;
            default:
                fix = (w != oldw) || (h != oldh);
            }
            if (fix) {
                completeScroll();
                int[] pos = getDestScrollPos();
                scrollTo(pos[0], pos[1]);
            }
        }
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {

        int screenSide = mScreenSide;

        if (mScreenSide == STICK_TO_AUTO) {
            screenSide = getScreenSideAuto(left, right);
        }

        if (screenSide != mScreenSide || mForceLayout) {

            mForceLayout = false;

            mScreenSide = screenSide;

            if (!mIsOpen) {
                closeLayer(false, true);
            }

            if (mScreenSide == STICK_TO_RIGHT) {
                setPadding(getPaddingLeft() + mShadowWidth, getPaddingTop(), getPaddingRight(), getPaddingBottom());
            } else if (mScreenSide == STICK_TO_BOTTOM) {
                setPadding(getPaddingLeft(), getPaddingTop() + mShadowWidth, getPaddingRight(), getPaddingBottom());
            } else if (mScreenSide == STICK_TO_LEFT) {
                setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight() + mShadowWidth, getPaddingBottom());
            } else if (mScreenSide == STICK_TO_TOP) {
                setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), getPaddingBottom() + mShadowWidth);
            } else if (mScreenSide == STICK_TO_MIDDLE) {
                setPadding(getPaddingLeft() + mShadowWidth, getPaddingTop(), getPaddingRight() + mShadowWidth,
                        getPaddingBottom());
            }
        }

        super.onLayout(changed, left, top, right, bottom);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
    }

    private int[] getDestScrollPos() {
        return getDestScrollPos(0, 0);
    }

    /**
     * Get the x destination based on the velocity
     * 
     * @param xValue
     * @param yValue
     * @return
     * @since 1.0
     * 
     */
    private int[] getDestScrollPos(int xValue, int yValue) {

        int[] pos = new int[2];

        if (mIsOpen) {
            return pos;
        } else {

            switch (mScreenSide) {
            case STICK_TO_RIGHT:
                pos[0] = -getWidth() + mOffsetWidth;
                break;
            case STICK_TO_LEFT:
                pos[0] = getWidth() - mOffsetWidth;
                break;
            case STICK_TO_TOP:
                pos[1] = getHeight() - mOffsetWidth;
                break;
            case STICK_TO_BOTTOM:
                pos[1] = -getHeight() + mOffsetWidth;
                break;
            case STICK_TO_MIDDLE:

                // Calculate slope m to get direction of swiping and apply the
                // same vector until the end of the
                // animation
                float m = 1;

                // If no veocity nor translation (difficult to get) the target
                // is random
                if (xValue == 0 && yValue == 0) {
                    m = mRandom != null ? (float) Math.tan(mRandom.nextFloat() * Math.PI - Math.PI / 2) : 1;
                } else if (xValue == 0) {
                    // Avoid division by 0 (Get the max value of the tan which
                    // is equivalent)
                    m = (float) Math.tan(Math.PI / 2);
                } else {
                    // Get slope
                    m = yValue / (float) xValue;
                }

                if (Math.abs(m) >= 1) {
                    pos[0] = Math.round(getOperationSignForDiffMeasure(xValue) * getHeight() / Math.abs(m)
                            - (mLastX - getWidth() / 2));
                    pos[1] = Math.round(getOperationSignForDiffMeasure(yValue) * getHeight());
                } else {
                    pos[0] = Math.round(getOperationSignForDiffMeasure(xValue) * getWidth());
                    pos[1] = Math.round(getOperationSignForDiffMeasure(yValue) * getWidth() * Math.abs(m)
                            - (mLastY - getHeight() / 2));
                }
                break;

            }

            return pos;
        }
    }

    private int getOperationSignForDiffMeasure(float d) {
        if (mRandom == null) {
            return 1;
        } else {
            return Math.abs(d) < mMinimumVelocity ? mRandom.nextBoolean() ? 1 : -1 : d > 0 ? -1 : 1;
        }
    }

    public int getContentLeft() {
        return getLeft() + getPaddingLeft();
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        // Draw the margin drawable if needed.
        if (mShadowWidth > 0 && mShadowDrawable != null) {
            if (mScreenSide == STICK_TO_RIGHT) {
                mShadowDrawable.setBounds(0, 0, mShadowWidth, getHeight());
            }
            if (mScreenSide == STICK_TO_TOP) {
                mShadowDrawable.setBounds(0, getHeight() - mShadowWidth, getWidth(), getHeight());
            }
            if (mScreenSide == STICK_TO_LEFT) {
                mShadowDrawable.setBounds(getWidth() - mShadowWidth, 0, getWidth(), getHeight());
            }
            if (mScreenSide == STICK_TO_BOTTOM) {
                mShadowDrawable.setBounds(0, 0, getWidth(), mShadowWidth);
            }
            mShadowDrawable.draw(canvas);
        }
    }

    @Override
    public void computeScroll() {
        if (!mScroller.isFinished()) {
            if (mScroller.computeScrollOffset()) {
                final int oldX = getScrollX();
                final int oldY = getScrollY();
                final int x = mScroller.getCurrX();
                final int y = mScroller.getCurrY();

                if (oldX != x || oldY != y) {
                    scrollTo(x, y);
                }

                // We invalidate a slightly larger area now, this was only
                // optimised for right menu previously
                // Keep on drawing until the animation has finished. Just
                // re-draw the necessary part
                invalidate(getLeft() + oldX, getTop() + oldY, getRight() - oldX, getBottom() - oldY);
                return;
            }
        }

        // Done with scroll, clean up state.
        completeScroll();
    }

    /**
     * Handler interface for obtaining updates on the <code>SlidingLayer</code>
     * 's state. <code>OnInteractListener</code> allows for external classes to
     * be notified when the <code>SlidingLayer</code> receives input to be
     * opened or closed.
     */
    public interface OnInteractListener {

        /**
         * This method is called when an attempt is made to open the current
         * <code>SlidingLayer</code>. Note that because of animation, the
         * <code>SlidingLayer</code> may not be visible yet.
         */
        public void onOpen();

        /**
         * This method is called when an attempt is made to close the current
         * <code>SlidingLayer</code>. Note that because of animation, the
         * <code>SlidingLayer</code> may still be visible.
         */
        public void onClose();

        /**
         * this method is executed after <code>onOpen()</code>, when the
         * animation has finished.
         */
        public void onOpened();

        /**
         * this method is executed after <code>onClose()</code>, when the
         * animation has finished and the <code>SlidingLayer</code> is therefore
         * no longer visible.
         */
        public void onClosed();
    }

    static class SavedState extends BaseSavedState {

        Bundle mState;

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

        public SavedState(Parcel in) {
            super(in);
            mState = in.readBundle();
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            super.writeToParcel(dest, flags);
            dest.writeBundle(mState);
        }

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

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