Android Open Source - NYU-BusTracker-Android Multiple Orientation Sliding Drawer






From Project

Back to project page NYU-BusTracker-Android.

License

The source code is released under:

Apache License

If you think the Android project NYU-BusTracker-Android listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

package com.nyubustracker.helpers;
// www .  j a  va2 s .  c om
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.SoundEffectConstants;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;

import com.nyubustracker.R;

/*
 * Copyright (c) 2001 - 2012 Sileria, Inc.
 *
 * 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.
 */

/**
 * MultipleOrientationSlidingDrawer is a copy of SlidingTray (http://aniqroid.sileria.com/doc/api/com/sileria/android/view/SlidingTray.html)
 * which is a modification of {@link android.widget.SlidingDrawer} with two major changes:
 * <ul>
 * <li>It lets you create the drawer programmatically instead of just via xml</li>
 * <li>Secondly you can {@link #setOrientation(Orientation)} to any 4 corners of the parent</li>
 * </ul>
 * <p/>
 * A SlidingDrawer hides content out of the screen and allows the user to drag a handle
 * to bring the content on screen. SlidingDrawer can be used vertically or horizontally.
 * <p/>
 * A special widget composed of two children views: the handle, that the users drags,
 * and the content, attached to the handle and dragged with it.
 * <p/>
 * SlidingDrawer should be used as an overlay inside layouts. This means SlidingDrawer
 * should only be used inside of a FrameLayout or a RelativeLayout for instance. The
 * size of the SlidingDrawer defines how much space the content will occupy once slid
 * out so SlidingDrawer should usually use match_parent for both its dimensions.
 * <p/>
 * <strong>Coding Example:</strong>
 * <blockquote><pre>
 * // handle
 * Button handle = new Button( this );
 * handle.setText( "Push Me" );
 * <p/>
 * // content
 * TextView content = T.newText( "Sample Text." );
 * <p/>
 * // drawer
 * MultipleOrientationSlidingDrawer drawer = new MultipleOrientationSlidingDrawer( this, handle, content, MultipleOrientationSlidingDrawer.Orientation.TOP );
 * </pre></blockquote>
 * <p/>
 * <p/>
 * <strong>XML Example:</strong>
 * <p/>
 * <blockquote><pre class="prettyprint">
 * &lt;com.your.app.MultipleOrientationSlidingDrawer
 * xmlns:custom="http://schemas.android.com/apk/res-auto/com.your.app"
 * android:id="@+id/drawer"
 * android:layout_width="match_parent"
 * android:layout_height="match_parent"
 * custom:orientation="top"
 * custom:handle="@+id/handle_id"
 * custom:content="@+id/content"&gt;
 * <p/>
 * &lt;ImageView
 * android:id="@+id/handle_id"
 * android:layout_width="88dip"
 * android:layout_height="44dip" /&gt;
 * <p/>
 * &lt;GridView
 * android:id="@+id/content"
 * android:layout_width="match_parent"
 * android:layout_height="match_parent" /&gt;
 * <p/>
 * &lt;/com.your.app.MultipleOrientationSlidingDrawer&gt;
 * </pre></blockquote>
 *
 * @author Ahmed Shakil
 * @author PatrickF
 */
public class MultipleOrientationSlidingDrawer extends ViewGroup {

    private static final int TAP_THRESHOLD = 6;
    private static final float MAXIMUM_TAP_VELOCITY = 100.0f;
    private static final float MAXIMUM_MINOR_VELOCITY = 150.0f;
    private static final float MAXIMUM_MAJOR_VELOCITY = 200.0f;
    private static final float MAXIMUM_ACCELERATION = 2000.0f;
    private static final int VELOCITY_UNITS = 1000;
    private static final int MSG_ANIMATE = 1000;
    private static final int ANIMATION_FRAME_DURATION = 1000 / 60;

    private static final int EXPANDED_FULL_OPEN = -10001;
    private static final int COLLAPSED_FULL_CLOSED = -10002;

    private final int mHandleId;
    private final int mContentId;
    private final Rect mFrame = new Rect();
    private final Rect mInvalidate = new Rect();
    private final Handler mHandler = new SlidingHandler();
    private final int mTapThreshold;
    private final int mMaximumTapVelocity;
    private final int mMaximumMinorVelocity;
    private final int mMaximumMajorVelocity;
    private final int mMaximumAcceleration;
    private final int mVelocityUnits;
    private View mHandle;
    private View mContent;
    private boolean mTracking;
    private boolean mLocked;
    private VelocityTracker mVelocityTracker;
    private Orientation mOrientation;
    private Side mHandlePos;
    private boolean mVertical;
    private boolean mInvert;
    private boolean mExpanded;
    private int mBottomOffset;
    private int mTopOffset;
    private int mHandleHeight;
    private int mHandleWidth;
    private int mHandlePad;
    private OnDrawerOpenListener mOnDrawerOpenListener;
    private OnDrawerCloseListener mOnDrawerCloseListener;
    private OnDrawerScrollListener mOnDrawerScrollListener;
    private float mAnimatedAcceleration;
    private float mAnimatedVelocity;
    private float mAnimationPosition;
    private long mAnimationLastTime;
    private long mCurrentAnimationTime;
    private int mTouchDelta;
    private boolean mAnimating;
    private boolean mAllowSingleTap = true;
    private boolean mAnimateOnClick = true;

    /**
     * Construct a <code>MultipleOrientationSlidingDrawer</code> object programmatically with the specified
     * <code>handle</code>, <code>content</code> and <code>orientation</code>.
     *
     * @param context     Activity context
     * @param handle      Cannot be <code>null</code>
     * @param content     Cannot be <code>null</code>
     * @param orientation TOP, LEFT, BOTTOM or RIGHT.
     */
    public MultipleOrientationSlidingDrawer(Context context, View handle, View content, Orientation orientation) {
        super(context);

        // handle
        if (handle == null) throw new NullPointerException("Handle cannot be null.");
        addView(mHandle = handle);
        mHandle.setOnClickListener(new DrawerToggler());

        // content
        if (content == null) throw new IllegalArgumentException("Content cannot be null.");
        addView(mContent = content);
        mContent.setVisibility(View.GONE);

        mHandleId = mContentId = 0;

        setOrientation(orientation);

        final float density = getResources().getDisplayMetrics().density;
        mTapThreshold = (int) (TAP_THRESHOLD * density + 0.5f);
        mMaximumTapVelocity = (int) (MAXIMUM_TAP_VELOCITY * density + 0.5f);
        mMaximumMinorVelocity = (int) (MAXIMUM_MINOR_VELOCITY * density + 0.5f);
        mMaximumMajorVelocity = (int) (MAXIMUM_MAJOR_VELOCITY * density + 0.5f);
        mMaximumAcceleration = (int) (MAXIMUM_ACCELERATION * density + 0.5f);
        mVelocityUnits = (int) (VELOCITY_UNITS * density + 0.5f);
    }

    /**
     * Creates a new SlidingDrawer from a specified set of attributes defined in XML.
     *
     * @param context The application's environment.
     * @param attrs   The attributes defined in XML.
     */
    public MultipleOrientationSlidingDrawer(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    /**
     * Creates a new SlidingDrawer from a specified set of attributes defined in XML.
     *
     * @param context  The application's environment.
     * @param attrs    The attributes defined in XML.
     * @param defStyle The style to apply to this widget.
     */
    public MultipleOrientationSlidingDrawer(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MultipleOrientationSlidingDrawer, defStyle, 0);

        int orientation = a.getInteger(R.styleable.MultipleOrientationSlidingDrawer_orientation, Orientation.TOP.value);
        setOrientation(Orientation.getByValue(orientation));
        mHandlePos = Side.getByValue(orientation);
        mBottomOffset = (int) a.getDimension(R.styleable.MultipleOrientationSlidingDrawer_bottomOffset, 0.0f);
        mTopOffset = (int) a.getDimension(R.styleable.MultipleOrientationSlidingDrawer_topOffset, 0.0f);
        mAllowSingleTap = a.getBoolean(R.styleable.MultipleOrientationSlidingDrawer_allowSingleTap, true);
        mAnimateOnClick = a.getBoolean(R.styleable.MultipleOrientationSlidingDrawer_animateOnClick, true);


        int handleId = a.getResourceId(R.styleable.MultipleOrientationSlidingDrawer_handle, 0);
        if (handleId == 0) {
            throw new IllegalArgumentException("The handle attribute is required and must refer " + "to a valid child.");
        }

        int contentId = a.getResourceId(R.styleable.MultipleOrientationSlidingDrawer_content, 0);
        if (contentId == 0) {
            throw new IllegalArgumentException("The content attribute is required and must refer " + "to a valid child.");
        }

        if (handleId == contentId) {
            throw new IllegalArgumentException("The content and handle attributes must refer " + "to different children.");
        }

        mHandleId = handleId;
        mContentId = contentId;

        final float density = getResources().getDisplayMetrics().density;
        mTapThreshold = (int) (TAP_THRESHOLD * density + 0.5f);
        mMaximumTapVelocity = (int) (MAXIMUM_TAP_VELOCITY * density + 0.5f);
        mMaximumMinorVelocity = (int) (MAXIMUM_MINOR_VELOCITY * density + 0.5f);
        mMaximumMajorVelocity = (int) (MAXIMUM_MAJOR_VELOCITY * density + 0.5f);
        mMaximumAcceleration = (int) (MAXIMUM_ACCELERATION * density + 0.5f);
        mVelocityUnits = (int) (VELOCITY_UNITS * density + 0.5f);

        //        a.recycle();

        setAlwaysDrawnWithCacheEnabled(false);
    }

    /**
     * Get the current orientation of this sliding tray.
     */
    public Orientation getOrientation() {
        return mOrientation;
    }

    /**
     * Lets you change the orientation of the sliding tray at runtime.
     * <p/>
     * Orientation must be from one of TOP, LEFT, BOTTOM, RIGHT.
     *
     * @param orientation orientation of the sliding tray.
     */
    public void setOrientation(Orientation orientation) {
        mOrientation = orientation;

        mVertical = mOrientation == Orientation.BOTTOM || mOrientation == Orientation.TOP;
        mInvert = mOrientation == Orientation.LEFT || mOrientation == Orientation.TOP;

        requestLayout();
        invalidate();
    }

    /**
     * Get the current positioning of this sliding tray handle.
     */
    public Side getHandlePosition() {
        return mHandlePos;
    }

    /**
     * Change the handle positioning of the sliding tray at runtime.
     * <p/>
     * HandlePos must be {@link Side#TOP}, {@link Side#CENTER} or {@link Side#BOTTOM} for horizontal orientation
     * or must be {@link Side#LEFT}, {@link Side#CENTER} or {@link Side#RIGHT} for vertical orientation.
     * <p/>
     * Default is {@linkplain Side#CENTER}.
     *
     * @param side Handle Pos of the drawer handle.
     */
    public void setHandlePosition(Side side) {
        mHandlePos = side;
        requestLayout();
        invalidate();
    }

    /**
     * Add padding to drawer handle when handle is not centered.
     * <p/>
     * Note this padding is only effective when handle is not centered.
     *
     * @param padding padding in pixels.
     */
    public void setHandlePadding(int padding) {
        mHandlePad = padding;
        requestLayout();
        invalidate();
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        if (mLocked) {
            return false;
        }

        final int action = event.getAction();

        float x = event.getX();
        float y = event.getY();

        final Rect frame = mFrame;
        final View handle = mHandle;

        handle.getHitRect(frame);
        if (!mTracking && !frame.contains((int) x, (int) y)) {
            return false;
        }

        if (action == MotionEvent.ACTION_DOWN) {
            mTracking = true;

            handle.setPressed(true);
            // Must be called before prepareTracking()
            prepareContent();

            // Must be called after prepareContent()
            if (mOnDrawerScrollListener != null) {
                mOnDrawerScrollListener.onScrollStarted();
            }

            final int pt = getSide();
            mTouchDelta = (int) (y - pt);
            prepareTracking(pt);
            mVelocityTracker.addMovement(event);
        }

        return true;
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        final long drawingTime = getDrawingTime();
        final View handle = mHandle;
        final Orientation orientation = mOrientation;

        drawChild(canvas, handle, drawingTime);

        if (mTracking || mAnimating) {
            final Bitmap cache = mContent.getDrawingCache();
            if (cache != null) {
                // called when opening

                switch (orientation) {
                    case TOP:
                        canvas.drawBitmap(cache, 0, handle.getTop() - cache.getHeight(), null);
                        break;
                    case LEFT:
                        canvas.drawBitmap(cache, handle.getLeft() - cache.getWidth(), 0, null);
                        break;
                    case BOTTOM:
                        canvas.drawBitmap(cache, 0, handle.getBottom(), null);
                        break;
                    case RIGHT:
                        canvas.drawBitmap(cache, handle.getRight(), 0, null);
                        break;
                }
            }
            else {
                // called when closing
                canvas.save();
                switch (orientation) {
                    case TOP:
                        canvas.translate(0, handle.getTop() - mContent.getHeight());
                        break;
                    case LEFT:
                        canvas.translate(handle.getLeft() - mContent.getWidth(), 0);
                        break;
                    case BOTTOM:
                        canvas.translate(0, handle.getTop() - mTopOffset);
                        break;
                    case RIGHT:
                        canvas.translate(handle.getLeft() - mTopOffset, 0);
                        break;
                }
                drawChild(canvas, mContent, drawingTime);
                canvas.restore();
            }
        }
        else if (mExpanded) {
            drawChild(canvas, mContent, drawingTime);
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (mTracking) {
            return;
        }

        final int width = r - l;
        final int height = b - t;

        final View handle = mHandle;

        int childWidth = handle.getMeasuredWidth();
        int childHeight = handle.getMeasuredHeight();

        int childLeft = 0;
        int childTop = 0;

        final View content = mContent;


        switch (mOrientation) {

            case TOP:
                switch (mHandlePos) {
                    case LEFT:
                        childLeft = mHandlePad;
                        break;
                    case RIGHT:
                        childLeft = width - childWidth - mHandlePad;
                        break;
                    default:
                        childLeft = (width - childWidth) / 2;
                        break;
                }
                childTop = mExpanded ? height - childHeight - mTopOffset : -mBottomOffset;

                content.layout(0, height - childHeight - mTopOffset - content.getMeasuredHeight(), content.getMeasuredWidth(), height - childHeight - mTopOffset);
                break;

            case BOTTOM:
                switch (mHandlePos) {
                    case LEFT:
                        childLeft = mHandlePad;
                        break;
                    case RIGHT:
                        childLeft = width - childWidth - mHandlePad;
                        break;
                    default:
                        childLeft = (width - childWidth) / 2;
                        break;
                }
                childTop = mExpanded ? mTopOffset : height - childHeight + mBottomOffset;

                content.layout(0, mTopOffset + childHeight, content.getMeasuredWidth(), mTopOffset + childHeight + content.getMeasuredHeight());
                break;

            case RIGHT:
                childLeft = mExpanded ? mTopOffset : width - childWidth + mBottomOffset;
                switch (mHandlePos) {
                    case TOP:
                        childTop = mHandlePad;
                        break;
                    case BOTTOM:
                        childTop = height - childHeight - mHandlePad;
                        break;
                    default:
                        childTop = (height - childHeight) / 2;
                        break;
                }

                content.layout(mTopOffset + childWidth, 0, mTopOffset + childWidth + content.getMeasuredWidth(), content.getMeasuredHeight());
                break;

            case LEFT:
                childLeft = mExpanded ? width - childWidth - mTopOffset : -mBottomOffset;
                switch (mHandlePos) {
                    case TOP:
                        childTop = mHandlePad;
                        break;
                    case BOTTOM:
                        childTop = height - childHeight - mHandlePad;
                        break;
                    default:
                        childTop = (height - childHeight) / 2;
                        break;
                }

                content.layout(width - childWidth - mTopOffset - content.getMeasuredWidth(), 0, width - childWidth - mTopOffset, content.getMeasuredHeight());
                break;
        }

        handle.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
        mHandleHeight = handle.getHeight();
        mHandleWidth = handle.getWidth();
    }

    private void prepareContent() {

        if (mAnimating) {
            return;
        }

        // Something changed in the content, we need to honor the layout request
        // before creating the cached bitmap
        final View content = mContent;
        if (content.isLayoutRequested()) {
            measureContent();
        }

        // Try only once... we should really loop but it's not a big deal
        // if the draw was cancelled, it will only be temporary anyway
        content.getViewTreeObserver().dispatchOnPreDraw();
        content.buildDrawingCache();

        content.setVisibility(View.GONE);
    }

    private int getSide() {
        return mVertical ? mHandle.getTop() : mHandle.getLeft();
    }

    private void prepareTracking(int position) {
        mTracking = true;
        mVelocityTracker = VelocityTracker.obtain();
        boolean opening = !mExpanded;
        if (opening) {
            mAnimatedAcceleration = mMaximumAcceleration;
            mAnimatedVelocity = mMaximumMajorVelocity;
            switch (mOrientation) {
                case TOP:
                case LEFT:
                    mAnimationPosition = mBottomOffset;
                    break;
                case BOTTOM:
                    mAnimationPosition = mBottomOffset + getHeight() - mHandleHeight;
                    break;
                case RIGHT:
                    mAnimationPosition = mBottomOffset + getWidth() - mHandleWidth;
                    break;
            }

            moveHandle((int) mAnimationPosition);
            mAnimating = true;
            mHandler.removeMessages(MSG_ANIMATE);
            long now = SystemClock.uptimeMillis();
            mAnimationLastTime = now;
            mCurrentAnimationTime = now + ANIMATION_FRAME_DURATION;
            mAnimating = true;
        }
        else {
            if (mAnimating) {
                mAnimating = false;
                mHandler.removeMessages(MSG_ANIMATE);
            }
            moveHandle(position);
        }
    }

    public void measureContent() {
        final View content = mContent;
        if (mVertical) {
            final int childHeight = mHandle.getHeight();
            int height = getBottom() - getTop() - childHeight - mTopOffset;
            content.measure(MeasureSpec.makeMeasureSpec(getRight() - getLeft(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
            if (mOrientation == Orientation.TOP) {
                content.layout(0, height - content.getMeasuredHeight(), content.getMeasuredWidth(), height);
            }
            else {
                content.layout(0, mTopOffset + childHeight, content.getMeasuredWidth(), mTopOffset + childHeight + content.getMeasuredHeight());
            }

        }
        else {
            final int childWidth = mHandle.getWidth();
            int width = getRight() - getLeft() - childWidth - mTopOffset;
            content.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(getBottom() - getTop(), MeasureSpec.EXACTLY));
            if (mOrientation == Orientation.RIGHT) {
                content.layout(childWidth + mTopOffset, 0, mTopOffset + childWidth + content.getMeasuredWidth(), content.getMeasuredHeight());
            }
            else {
                content.layout(width - content.getMeasuredWidth(), 0, width, content.getMeasuredHeight());
            }
        }
    }

    private void moveHandle(int position) {
        final View handle = mHandle;

        switch (mOrientation) {
            case TOP:
                if (position == EXPANDED_FULL_OPEN) {
                    handle.offsetTopAndBottom(getBottom() - getTop() - mTopOffset - mHandleHeight - handle.getTop());
                    invalidate();
                }
                else if (position == COLLAPSED_FULL_CLOSED) {
                    handle.offsetTopAndBottom(-mBottomOffset - handle.getTop());
                    invalidate();
                }
                else {
                    final int top = handle.getTop();
                    int deltaY = position - top;
                    if (position < -mBottomOffset) {
                        deltaY = -mBottomOffset - top;
                    }
                    else if (position > getBottom() - getTop() - mTopOffset - mHandleHeight) {
                        deltaY = getBottom() - getTop() - mTopOffset - mHandleHeight - top;
                    }
                    handle.offsetTopAndBottom(deltaY);

                    final Rect frame = mFrame;
                    final Rect region = mInvalidate;

                    handle.getHitRect(frame);
                    region.set(frame);

                    region.union(frame.left, frame.top - deltaY, frame.right, frame.bottom - deltaY);
                    region.union(0, frame.bottom - deltaY, getWidth(), frame.bottom - deltaY + mContent.getHeight());

                    // todo fix the region calc.
                    // invalidate( region );
                    invalidate();
                }
                break;

            case BOTTOM:
                if (position == EXPANDED_FULL_OPEN) {
                    handle.offsetTopAndBottom(mTopOffset - handle.getTop());
                    invalidate();
                }
                else if (position == COLLAPSED_FULL_CLOSED) {
                    handle.offsetTopAndBottom(mBottomOffset + getBottom() - getTop() - mHandleHeight - handle.getTop());
                    invalidate();
                }
                else {
                    final int top = handle.getTop();
                    int deltaY = position - top;
                    if (position < mTopOffset) {
                        deltaY = mTopOffset - top;
                    }
                    else if (deltaY > mBottomOffset + getBottom() - getTop() - mHandleHeight - top) {
                        deltaY = mBottomOffset + getBottom() - getTop() - mHandleHeight - top;
                    }
                    handle.offsetTopAndBottom(deltaY);

                    final Rect frame = mFrame;
                    final Rect region = mInvalidate;

                    handle.getHitRect(frame);
                    region.set(frame);

                    region.union(frame.left, frame.top - deltaY, frame.right, frame.bottom - deltaY);
                    region.union(0, frame.bottom - deltaY, getWidth(), frame.bottom - deltaY + mContent.getHeight());

                    invalidate(region);
                }
                break;

            case RIGHT:
                if (position == EXPANDED_FULL_OPEN) {
                    handle.offsetLeftAndRight(mTopOffset - handle.getLeft());
                    invalidate();
                }
                else if (position == COLLAPSED_FULL_CLOSED) {
                    handle.offsetLeftAndRight(-mBottomOffset);
                    invalidate();
                }
                else {
                    final int left = handle.getLeft();
                    int deltaX = position - left;
                    if (position < mTopOffset) {
                        deltaX = mTopOffset - left;
                    }
                    else if (deltaX > mBottomOffset + getRight() - getLeft() - mHandleWidth - left) {
                        deltaX = mBottomOffset + getRight() - getLeft() - mHandleWidth - left;
                    }
                    handle.offsetLeftAndRight(deltaX);

                    final Rect frame = mFrame;
                    final Rect region = mInvalidate;

                    handle.getHitRect(frame);
                    region.set(frame);

                    region.union(frame.left - deltaX, frame.top, frame.right - deltaX, frame.bottom);
                    region.union(frame.right - deltaX, 0, frame.right - deltaX + mContent.getWidth(), getHeight());

                    invalidate(region);
                }
                break;

            case LEFT:
                if (position == EXPANDED_FULL_OPEN) {
                    handle.offsetLeftAndRight(getRight() - getLeft() - mTopOffset - mHandleWidth - handle.getLeft());
                    invalidate();
                }
                else if (position == COLLAPSED_FULL_CLOSED) {
                    handle.offsetLeftAndRight(-mBottomOffset - handle.getLeft());
                    invalidate();
                }
                else {
                    final int left = handle.getLeft();
                    int deltaX = position - left;
                    if (position < -mBottomOffset) {
                        deltaX = -mBottomOffset - left;
                    }
                    else if (position > getRight() - getLeft() - mTopOffset - mHandleWidth) {
                        deltaX = getRight() - getLeft() - mTopOffset - mHandleWidth - left;
                    }
                    handle.offsetLeftAndRight(deltaX);

                    final Rect frame = mFrame;
                    final Rect region = mInvalidate;

                    handle.getHitRect(frame);
                    region.set(frame);

                    region.union(frame.left - deltaX, frame.top, frame.right - deltaX, frame.bottom);
                    region.union(frame.right - deltaX, 0, frame.right - deltaX + mContent.getWidth(), getHeight());

                    invalidate(region);
                }
                break;
        }
    }

    private int getOppositeSide() {
        int pt = 0;
        switch (mOrientation) {
            case TOP:
                pt = mHandle.getBottom();
                break;
            case LEFT:
                pt = mHandle.getRight();
                break;
            case BOTTOM:
                pt = mHandle.getTop();
                break;
            case RIGHT:
                pt = mHandle.getLeft();
                break;
        }
        return pt;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (mLocked) {
            return true;
        }

        if (mTracking) {
            mVelocityTracker.addMovement(event);
            final int action = event.getAction();
            switch (action) {
                case MotionEvent.ACTION_MOVE:
                    moveHandle((int) (mVertical ? event.getY() : event.getX()) - mTouchDelta);
                    break;
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL: {
                    final VelocityTracker velocityTracker = mVelocityTracker;
                    velocityTracker.computeCurrentVelocity(mVelocityUnits);

                    float yVelocity = velocityTracker.getYVelocity();
                    float xVelocity = velocityTracker.getXVelocity();
                    boolean negative;

                    final boolean vertical = mVertical;
                    if (vertical) {
                        negative = yVelocity < 0;
                        if (xVelocity < 0) {
                            xVelocity = -xVelocity;
                        }
                        if (xVelocity > mMaximumMinorVelocity) {
                            xVelocity = mMaximumMinorVelocity;
                        }
                    }
                    else {
                        negative = xVelocity < 0;
                        if (yVelocity < 0) {
                            yVelocity = -yVelocity;
                        }
                        if (yVelocity > mMaximumMinorVelocity) {
                            yVelocity = mMaximumMinorVelocity;
                        }
                    }

                    float velocity = (float) Math.hypot(xVelocity, yVelocity);
                    if (negative) {
                        velocity = -velocity;
                    }

                    final int top = mHandle.getTop();
                    final int left = mHandle.getLeft();

                    if (Math.abs(velocity) < mMaximumTapVelocity) {

                        if (inThreshold(top, left)) {
                            if (mAllowSingleTap) {
                                playSoundEffect(SoundEffectConstants.CLICK);

                                if (mExpanded) {
                                    //animateClose(vertical ? top : left);
                                    animateClose(getSide());
                                }
                                else {
                                    animateOpen(getSide());
                                    //animateOpen(vertical ? top : left);
                                }
                            }
                            else {
                                performFling(vertical ? top : left, velocity, false);
                            }

                        }
                        else {
                            performFling(vertical ? top : left, velocity, false);
                        }
                    }
                    else {
                        performFling(vertical ? top : left, velocity, false);
                    }
                }
                break;
            }
        }

        return mTracking || mAnimating || super.onTouchEvent(event);
    }

    @Override
    protected void onFinishInflate() {
        if (mHandleId > 0) {
            mHandle = findViewById(mHandleId);
            if (mHandle == null) {
                throw new IllegalArgumentException("The handle attribute is must refer to an existing child.");
            }
            mHandle.setOnClickListener(new DrawerToggler());
        }

        if (mContentId > 0) {
            mContent = findViewById(mContentId);
            if (mContent == null) {
                throw new IllegalArgumentException("The content attribute is must refer to an existing child.");
            }
            mContent.setVisibility(View.GONE);
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);

        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);

        if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
            throw new RuntimeException("SlidingDrawer cannot have UNSPECIFIED dimensions");
        }

        final View handle = mHandle;
        measureChild(handle, widthMeasureSpec, heightMeasureSpec);

        if (mVertical) {
            int height = heightSpecSize - handle.getMeasuredHeight() - mTopOffset;
            mContent.measure(MeasureSpec.makeMeasureSpec(widthSpecSize, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
        }
        else {
            int width = widthSpecSize - handle.getMeasuredWidth() - mTopOffset;
            mContent.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(heightSpecSize, MeasureSpec.EXACTLY));
        }

        setMeasuredDimension(widthSpecSize, heightSpecSize);
    }

    private boolean inThreshold(int top, int left) {
        switch (mOrientation) {
            case TOP:
                return (!mExpanded && top < mTapThreshold - mBottomOffset) || (mExpanded && top > getBottom() - getTop() - mHandleHeight - mTopOffset - mTapThreshold);
            case LEFT:
                return (!mExpanded && left < mTapThreshold - mBottomOffset) || (mExpanded && left > getRight() - getLeft() - mHandleWidth - mTopOffset - mTapThreshold);
            case BOTTOM:
                return (mExpanded && top < mTapThreshold + mTopOffset) || (!mExpanded && top > mBottomOffset + getBottom() - getTop() - mHandleHeight - mTapThreshold);
            case RIGHT:
                return (mExpanded && left < mTapThreshold + mTopOffset) || (!mExpanded && left > mBottomOffset + getRight() - getLeft() - mHandleWidth - mTapThreshold);
        }
        return false;
    }

    private void animateClose(int position) {
        prepareTracking(position);
        performFling(position, mMaximumAcceleration * (mInvert ? -1 : 1), true);
    }

    private void animateOpen(int position) {
        prepareTracking(position);
        performFling(position, mMaximumAcceleration * (mInvert ? 1 : -1), true);
    }

    private void performFling(int position, float velocity, boolean always) {
        mAnimationPosition = position;
        mAnimatedVelocity = velocity;

        if (mExpanded) {
            if (mInvert) {
                if (always || (velocity < -mMaximumMajorVelocity || (position < (mVertical ? getHeight() : getWidth()) / 2 && velocity > -mMaximumMajorVelocity))) {
                    // We are expanded and are now going to animate away.
                    mAnimatedAcceleration = -mMaximumAcceleration;
                    if (velocity > 0) {
                        mAnimatedVelocity = 0;
                    }
                }
                else {
                    // We are expanded, but they didn't move sufficiently to cause
                    // us to retract.  Animate back to the expanded position.
                    mAnimatedAcceleration = mMaximumAcceleration;
                    if (velocity < 0) {
                        mAnimatedVelocity = 0;
                    }
                }
            }
            else if (always || (velocity > mMaximumMajorVelocity || (position > mTopOffset + (mVertical ? mHandleHeight : mHandleWidth) && velocity > -mMaximumMajorVelocity))) {
                // We are expanded, but they didn't move sufficiently to cause
                // us to retract.  Animate back to the collapsed position.
                mAnimatedAcceleration = mMaximumAcceleration;
                if (velocity < 0) {
                    mAnimatedVelocity = 0;
                }
            }
            else {
                // We are expanded and are now going to animate away.
                mAnimatedAcceleration = -mMaximumAcceleration;
                if (velocity > 0) {
                    mAnimatedVelocity = 0;
                }
            }
        }
        else {
            //else if (!always && (velocity > mMaximumMajorVelocity ||
            //    (position > (mVertical ? getHeight() : getWidth()) / 2 &&
            //        velocity > -mMaximumMajorVelocity))) {
            if ((velocity > mMaximumMajorVelocity || (position > (mVertical ? getHeight() : getWidth()) / 2 && velocity > -mMaximumMajorVelocity))) {
                // We are collapsed, and they moved enough to allow us to expand.
                mAnimatedAcceleration = mMaximumAcceleration;
                if (velocity < 0) {
                    mAnimatedVelocity = 0;
                }
            }
            else {
                // We are collapsed, but they didn't move sufficiently to cause
                // us to retract.  Animate back to the collapsed position.
                mAnimatedAcceleration = -mMaximumAcceleration;
                if (velocity > 0) {
                    mAnimatedVelocity = 0;
                }
            }
        }

        //    if (mInvert)
        //      mAnimatedAcceleration *= -1;
        long now = SystemClock.uptimeMillis();
        mAnimationLastTime = now;
        mCurrentAnimationTime = now + ANIMATION_FRAME_DURATION;
        mAnimating = true;
        mHandler.removeMessages(MSG_ANIMATE);
        mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE), mCurrentAnimationTime);
        stopTracking();
    }

    private void stopTracking() {
        mHandle.setPressed(false);
        mTracking = false;

        if (mOnDrawerScrollListener != null) {
            mOnDrawerScrollListener.onScrollEnded();
        }

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

    private void doAnimation() {
        if (mAnimating) {

            incrementAnimation();

            if (mInvert) {
                if (mAnimationPosition >= (mVertical ? getHeight() : getWidth()) - mTopOffset) {
                    mAnimating = false;
                    openDrawer();
                }
                else if (mAnimationPosition < -mBottomOffset) {
                    mAnimating = false;
                    closeDrawer();
                }
                else {
                    moveHandle((int) mAnimationPosition);
                    mCurrentAnimationTime += ANIMATION_FRAME_DURATION;
                    mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE), mCurrentAnimationTime);
                }
            }
            else {
                if (mAnimationPosition >= mBottomOffset + (mVertical ? getHeight() : getWidth()) - 1) {
                    mAnimating = false;
                    closeDrawer();
                }
                else if (mAnimationPosition < mTopOffset) {
                    mAnimating = false;
                    openDrawer();
                }
                else {
                    moveHandle((int) mAnimationPosition);
                    mCurrentAnimationTime += ANIMATION_FRAME_DURATION;
                    mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE), mCurrentAnimationTime);
                }
            }
        }
    }

    private void incrementAnimation() {
        long now = SystemClock.uptimeMillis();
        float t = (now - mAnimationLastTime) / 1000.0f;                   // ms -> s
        final float position = mAnimationPosition;
        final float v = mAnimatedVelocity;                                // px/s
        final float a = mAnimatedAcceleration;                            // px/s/s
        mAnimationPosition = position + (v * t) + (0.5f * a * t * t);     // px
        mAnimatedVelocity = v + (a * t);                                  // px/s
        mAnimationLastTime = now;                                         // ms
    }

    private void openDrawer() {
        moveHandle(EXPANDED_FULL_OPEN);
        mContent.setVisibility(View.VISIBLE);

        if (mExpanded) {
            return;
        }

        mExpanded = true;

        if (mOnDrawerOpenListener != null) {
            mOnDrawerOpenListener.onDrawerOpened();
        }
    }

    private void closeDrawer() {
        moveHandle(COLLAPSED_FULL_CLOSED);
        mContent.setVisibility(View.GONE);
        mContent.destroyDrawingCache();

        if (!mExpanded) {
            return;
        }

        mExpanded = false;

        if (mOnDrawerCloseListener != null) {
            mOnDrawerCloseListener.onDrawerClosed();
        }
    }

    /**
     * Toggles the drawer open and close. Takes effect immediately.
     *
     * @see #open()
     * @see #close()
     * @see #animateClose()
     * @see #animateOpen()
     * @see #animateToggle()
     */
    public void toggle() {
        if (!mExpanded) {
            openDrawer();
        }
        else {
            closeDrawer();
        }
        invalidate();
        requestLayout();
    }

    /**
     * Toggles the drawer open and close with an animation.
     *
     * @see #open()
     * @see #close()
     * @see #animateClose()
     * @see #animateOpen()
     * @see #toggle()
     */
    public void animateToggle() {
        if (!mExpanded) {
            animateOpen();
        }
        else {
            animateClose();
        }
    }

    /**
     * Opens the drawer immediately.
     *
     * @see #toggle()
     * @see #close()
     * @see #animateOpen()
     */
    public void open() {
        openDrawer();
        invalidate();
        requestLayout();

        sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
    }

    /**
     * Closes the drawer immediately.
     *
     * @see #toggle()
     * @see #open()
     * @see #animateClose()
     */
    public void close() {
        closeDrawer();
        invalidate();
        requestLayout();
    }

    /**
     * Closes the drawer with an animation.
     *
     * @see #close()
     * @see #open()
     * @see #animateOpen()
     * @see #animateToggle()
     * @see #toggle()
     */
    public void animateClose() {
        prepareContent();
        final OnDrawerScrollListener scrollListener = mOnDrawerScrollListener;
        if (scrollListener != null) {
            scrollListener.onScrollStarted();
        }
        animateClose(getSide());

        if (scrollListener != null) {
            scrollListener.onScrollEnded();
        }
    }

    /**
     * Opens the drawer with an animation.
     *
     * @see #close()
     * @see #open()
     * @see #animateClose()
     * @see #animateToggle()
     * @see #toggle()
     */
    public void animateOpen() {
        prepareContent();
        final OnDrawerScrollListener scrollListener = mOnDrawerScrollListener;
        if (scrollListener != null) {
            scrollListener.onScrollStarted();
        }
        animateOpen(getSide());

        sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);

        if (scrollListener != null) {
            scrollListener.onScrollEnded();
        }
    }

    /**
     * Sets the listener that receives a notification when the drawer becomes open.
     *
     * @param onDrawerOpenListener The listener to be notified when the drawer is opened.
     */
    public void setOnDrawerOpenListener(OnDrawerOpenListener onDrawerOpenListener) {
        mOnDrawerOpenListener = onDrawerOpenListener;
    }

    /**
     * Sets the listener that receives a notification when the drawer becomes close.
     *
     * @param onDrawerCloseListener The listener to be notified when the drawer is closed.
     */
    public void setOnDrawerCloseListener(OnDrawerCloseListener onDrawerCloseListener) {
        mOnDrawerCloseListener = onDrawerCloseListener;
    }

    /**
     * Sets the listener that receives a notification when the drawer starts or ends
     * a scroll. A fling is considered as a scroll. A fling will also trigger a
     * drawer opened or drawer closed event.
     *
     * @param onDrawerScrollListener The listener to be notified when scrolling
     *                               starts or stops.
     */
    public void setOnDrawerScrollListener(OnDrawerScrollListener onDrawerScrollListener) {
        mOnDrawerScrollListener = onDrawerScrollListener;
    }

    /**
     * Returns the handle of the drawer.
     *
     * @return The View reprenseting the handle of the drawer, identified by
     * the "handle" id in XML.
     */
    public View getHandle() {
        return mHandle;
    }

    /**
     * Convenience method to get the size of the handle.
     * May not return a valid size until the view is layed out.
     *
     * @return Return the height if the component is vertical
     * otherwise returns the width for Horizontal orientation.
     */
    public int getHandleSize() {
        return mVertical ? mHandle.getHeight() : mHandle.getWidth();
    }

    /**
     * Returns the content of the drawer.
     *
     * @return The View reprenseting the content of the drawer, identified by
     * the "content" id in XML.
     */
    public View getContent() {
        return mContent;
    }

    /**
     * Unlocks the SlidingDrawer so that touch events are processed.
     *
     * @see #lock()
     */
    public void unlock() {
        mLocked = false;
    }

    /**
     * Locks the SlidingDrawer so that touch events are ignores.
     *
     * @see #unlock()
     */
    public void lock() {
        mLocked = true;
    }

    /**
     * Indicates whether the drawer is currently fully opened.
     *
     * @return True if the drawer is opened, false otherwise.
     */
    public boolean isOpened() {
        return mExpanded;
    }

    /**
     * Indicates whether the drawer is scrolling or flinging.
     *
     * @return True if the drawer is scroller or flinging, false otherwise.
     */
    public boolean isMoving() {
        return mTracking || mAnimating;
    }

    public int getBottomOffset() {
        return mBottomOffset;
    }

    public void setBottomOffset(int offset) {
        this.mBottomOffset = offset;
        invalidate();
    }

    public int getTopOffset() {
        return mTopOffset;
    }

    public void setTopOffset(int offset) {
        this.mTopOffset = offset;
        invalidate();
    }

    public boolean isAllowSingleTap() {
        return mAllowSingleTap;
    }

    public void setAllowSingleTap(boolean mAllowSingleTap) {
        this.mAllowSingleTap = mAllowSingleTap;
    }

    public boolean isAnimateOnClick() {
        return mAnimateOnClick;
    }

    public void setAnimateOnClick(boolean mAnimateOnClick) {
        this.mAnimateOnClick = mAnimateOnClick;
    }

    public enum Side {
        TOP(0), LEFT(1), BOTTOM(2), RIGHT(3), FRONT(4), BACK(5), CENTER(6);

        public final int value;

        private Side(int value) {
            this.value = value;
        }

        public static Side getByValue(int value) {
            for (Side s : Side.values()) {
                if (s.value == value) {
                    return s;
                }
            }
            throw new IllegalArgumentException("There is no 'Side' enum with this value");
        }
    }

    public enum Orientation {
        TOP(0), LEFT(1), BOTTOM(2), RIGHT(3);
        public final int value;

        private Orientation(int value) {
            this.value = value;
        }

        public static Orientation getByValue(int value) {
            for (Orientation s : Orientation.values()) {
                if (s.value == value) {
                    return s;
                }
            }
            throw new IllegalArgumentException("There is no 'Orientation' enum with this value");
        }
    }

    /**
     * Callback invoked when the drawer is opened.
     */
    public static interface OnDrawerOpenListener {
        /**
         * Invoked when the drawer becomes fully open.
         */
        public void onDrawerOpened();
    }

    /**
     * Callback invoked when the drawer is closed.
     */
    public static interface OnDrawerCloseListener {
        /**
         * Invoked when the drawer becomes fully closed.
         */
        public void onDrawerClosed();
    }

    /**
     * Callback invoked when the drawer is scrolled.
     */
    public static interface OnDrawerScrollListener {
        /**
         * Invoked when the user starts dragging/flinging the drawer's handle.
         */
        public void onScrollStarted();

        /**
         * Invoked when the user stops dragging/flinging the drawer's handle.
         */
        public void onScrollEnded();
    }

    /**
     * Drawer click listener.
     */
    private class DrawerToggler implements OnClickListener {
        public void onClick(View v) {
            if (mLocked) {
                return;
            }
            // mAllowSingleTap isn't relevant here; you're *always*
            // allowed to open/close the drawer by clicking with the
            // trackball.

            if (mAnimateOnClick) {
                animateToggle();
            }
            else {
                toggle();
            }
        }
    }

    private class SlidingHandler extends Handler {
        public void handleMessage(Message m) {
            switch (m.what) {
                case MSG_ANIMATE:
                    doAnimation();
                    break;
            }
        }
    }
}




Java Source Code List

com.nyubustracker.NYUBusTrackerApplication.java
com.nyubustracker.activities.MainActivity.java
com.nyubustracker.adapters.StopAdapter.java
com.nyubustracker.adapters.TimeAdapter.java
com.nyubustracker.helpers.BusDownloaderHelper.java
com.nyubustracker.helpers.BusManager.java
com.nyubustracker.helpers.DownloaderHelper.java
com.nyubustracker.helpers.Downloader.java
com.nyubustracker.helpers.MultipleOrientationSlidingDrawer.java
com.nyubustracker.helpers.RouteDownloaderHelper.java
com.nyubustracker.helpers.SegmentDownloaderHelper.java
com.nyubustracker.helpers.StopDownloaderHelper.java
com.nyubustracker.helpers.TimeDownloaderHelper.java
com.nyubustracker.helpers.VersionDownloaderHelper.java
com.nyubustracker.models.Bus.java
com.nyubustracker.models.Route.java
com.nyubustracker.models.Stop.java
com.nyubustracker.models.Time.java