Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.widget;
     18 
     19 import android.R;
     20 import android.content.Context;
     21 import android.content.res.TypedArray;
     22 import android.graphics.Bitmap;
     23 import android.graphics.Canvas;
     24 import android.graphics.Rect;
     25 import android.os.Handler;
     26 import android.os.Message;
     27 import android.os.SystemClock;
     28 import android.util.AttributeSet;
     29 import android.view.MotionEvent;
     30 import android.view.SoundEffectConstants;
     31 import android.view.VelocityTracker;
     32 import android.view.View;
     33 import android.view.ViewGroup;
     34 import android.view.accessibility.AccessibilityEvent;
     35 import android.view.accessibility.AccessibilityNodeInfo;
     36 
     37 /**
     38  * SlidingDrawer hides content out of the screen and allows the user to drag a handle
     39  * to bring the content on screen. SlidingDrawer can be used vertically or horizontally.
     40  *
     41  * A special widget composed of two children views: the handle, that the users drags,
     42  * and the content, attached to the handle and dragged with it.
     43  *
     44  * SlidingDrawer should be used as an overlay inside layouts. This means SlidingDrawer
     45  * should only be used inside of a FrameLayout or a RelativeLayout for instance. The
     46  * size of the SlidingDrawer defines how much space the content will occupy once slid
     47  * out so SlidingDrawer should usually use match_parent for both its dimensions.
     48  *
     49  * Inside an XML layout, SlidingDrawer must define the id of the handle and of the
     50  * content:
     51  *
     52  * <pre class="prettyprint">
     53  * &lt;SlidingDrawer
     54  *     android:id="@+id/drawer"
     55  *     android:layout_width="match_parent"
     56  *     android:layout_height="match_parent"
     57  *
     58  *     android:handle="@+id/handle"
     59  *     android:content="@+id/content"&gt;
     60  *
     61  *     &lt;ImageView
     62  *         android:id="@id/handle"
     63  *         android:layout_width="88dip"
     64  *         android:layout_height="44dip" /&gt;
     65  *
     66  *     &lt;GridView
     67  *         android:id="@id/content"
     68  *         android:layout_width="match_parent"
     69  *         android:layout_height="match_parent" /&gt;
     70  *
     71  * &lt;/SlidingDrawer&gt;
     72  * </pre>
     73  *
     74  * @attr ref android.R.styleable#SlidingDrawer_content
     75  * @attr ref android.R.styleable#SlidingDrawer_handle
     76  * @attr ref android.R.styleable#SlidingDrawer_topOffset
     77  * @attr ref android.R.styleable#SlidingDrawer_bottomOffset
     78  * @attr ref android.R.styleable#SlidingDrawer_orientation
     79  * @attr ref android.R.styleable#SlidingDrawer_allowSingleTap
     80  * @attr ref android.R.styleable#SlidingDrawer_animateOnClick
     81  *
     82  * @deprecated This class is not supported anymore. It is recommended you
     83  * base your own implementation on the source code for the Android Open
     84  * Source Project if you must use it in your application.
     85  */
     86 @Deprecated
     87 public class SlidingDrawer extends ViewGroup {
     88     public static final int ORIENTATION_HORIZONTAL = 0;
     89     public static final int ORIENTATION_VERTICAL = 1;
     90 
     91     private static final int TAP_THRESHOLD = 6;
     92     private static final float MAXIMUM_TAP_VELOCITY = 100.0f;
     93     private static final float MAXIMUM_MINOR_VELOCITY = 150.0f;
     94     private static final float MAXIMUM_MAJOR_VELOCITY = 200.0f;
     95     private static final float MAXIMUM_ACCELERATION = 2000.0f;
     96     private static final int VELOCITY_UNITS = 1000;
     97     private static final int MSG_ANIMATE = 1000;
     98     private static final int ANIMATION_FRAME_DURATION = 1000 / 60;
     99 
    100     private static final int EXPANDED_FULL_OPEN = -10001;
    101     private static final int COLLAPSED_FULL_CLOSED = -10002;
    102 
    103     private final int mHandleId;
    104     private final int mContentId;
    105 
    106     private View mHandle;
    107     private View mContent;
    108 
    109     private final Rect mFrame = new Rect();
    110     private final Rect mInvalidate = new Rect();
    111     private boolean mTracking;
    112     private boolean mLocked;
    113 
    114     private VelocityTracker mVelocityTracker;
    115 
    116     private boolean mVertical;
    117     private boolean mExpanded;
    118     private int mBottomOffset;
    119     private int mTopOffset;
    120     private int mHandleHeight;
    121     private int mHandleWidth;
    122 
    123     private OnDrawerOpenListener mOnDrawerOpenListener;
    124     private OnDrawerCloseListener mOnDrawerCloseListener;
    125     private OnDrawerScrollListener mOnDrawerScrollListener;
    126 
    127     private final Handler mHandler = new SlidingHandler();
    128     private float mAnimatedAcceleration;
    129     private float mAnimatedVelocity;
    130     private float mAnimationPosition;
    131     private long mAnimationLastTime;
    132     private long mCurrentAnimationTime;
    133     private int mTouchDelta;
    134     private boolean mAnimating;
    135     private boolean mAllowSingleTap;
    136     private boolean mAnimateOnClick;
    137 
    138     private final int mTapThreshold;
    139     private final int mMaximumTapVelocity;
    140     private final int mMaximumMinorVelocity;
    141     private final int mMaximumMajorVelocity;
    142     private final int mMaximumAcceleration;
    143     private final int mVelocityUnits;
    144 
    145     /**
    146      * Callback invoked when the drawer is opened.
    147      */
    148     public static interface OnDrawerOpenListener {
    149         /**
    150          * Invoked when the drawer becomes fully open.
    151          */
    152         public void onDrawerOpened();
    153     }
    154 
    155     /**
    156      * Callback invoked when the drawer is closed.
    157      */
    158     public static interface OnDrawerCloseListener {
    159         /**
    160          * Invoked when the drawer becomes fully closed.
    161          */
    162         public void onDrawerClosed();
    163     }
    164 
    165     /**
    166      * Callback invoked when the drawer is scrolled.
    167      */
    168     public static interface OnDrawerScrollListener {
    169         /**
    170          * Invoked when the user starts dragging/flinging the drawer's handle.
    171          */
    172         public void onScrollStarted();
    173 
    174         /**
    175          * Invoked when the user stops dragging/flinging the drawer's handle.
    176          */
    177         public void onScrollEnded();
    178     }
    179 
    180     /**
    181      * Creates a new SlidingDrawer from a specified set of attributes defined in XML.
    182      *
    183      * @param context The application's environment.
    184      * @param attrs The attributes defined in XML.
    185      */
    186     public SlidingDrawer(Context context, AttributeSet attrs) {
    187         this(context, attrs, 0);
    188     }
    189 
    190     /**
    191      * Creates a new SlidingDrawer from a specified set of attributes defined in XML.
    192      *
    193      * @param context The application's environment.
    194      * @param attrs The attributes defined in XML.
    195      * @param defStyle The style to apply to this widget.
    196      */
    197     public SlidingDrawer(Context context, AttributeSet attrs, int defStyle) {
    198         super(context, attrs, defStyle);
    199         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SlidingDrawer, defStyle, 0);
    200 
    201         int orientation = a.getInt(R.styleable.SlidingDrawer_orientation, ORIENTATION_VERTICAL);
    202         mVertical = orientation == ORIENTATION_VERTICAL;
    203         mBottomOffset = (int) a.getDimension(R.styleable.SlidingDrawer_bottomOffset, 0.0f);
    204         mTopOffset = (int) a.getDimension(R.styleable.SlidingDrawer_topOffset, 0.0f);
    205         mAllowSingleTap = a.getBoolean(R.styleable.SlidingDrawer_allowSingleTap, true);
    206         mAnimateOnClick = a.getBoolean(R.styleable.SlidingDrawer_animateOnClick, true);
    207 
    208         int handleId = a.getResourceId(R.styleable.SlidingDrawer_handle, 0);
    209         if (handleId == 0) {
    210             throw new IllegalArgumentException("The handle attribute is required and must refer "
    211                     + "to a valid child.");
    212         }
    213 
    214         int contentId = a.getResourceId(R.styleable.SlidingDrawer_content, 0);
    215         if (contentId == 0) {
    216             throw new IllegalArgumentException("The content attribute is required and must refer "
    217                     + "to a valid child.");
    218         }
    219 
    220         if (handleId == contentId) {
    221             throw new IllegalArgumentException("The content and handle attributes must refer "
    222                     + "to different children.");
    223         }
    224 
    225         mHandleId = handleId;
    226         mContentId = contentId;
    227 
    228         final float density = getResources().getDisplayMetrics().density;
    229         mTapThreshold = (int) (TAP_THRESHOLD * density + 0.5f);
    230         mMaximumTapVelocity = (int) (MAXIMUM_TAP_VELOCITY * density + 0.5f);
    231         mMaximumMinorVelocity = (int) (MAXIMUM_MINOR_VELOCITY * density + 0.5f);
    232         mMaximumMajorVelocity = (int) (MAXIMUM_MAJOR_VELOCITY * density + 0.5f);
    233         mMaximumAcceleration = (int) (MAXIMUM_ACCELERATION * density + 0.5f);
    234         mVelocityUnits = (int) (VELOCITY_UNITS * density + 0.5f);
    235 
    236         a.recycle();
    237 
    238         setAlwaysDrawnWithCacheEnabled(false);
    239     }
    240 
    241     @Override
    242     protected void onFinishInflate() {
    243         mHandle = findViewById(mHandleId);
    244         if (mHandle == null) {
    245             throw new IllegalArgumentException("The handle attribute is must refer to an"
    246                     + " existing child.");
    247         }
    248         mHandle.setOnClickListener(new DrawerToggler());
    249 
    250         mContent = findViewById(mContentId);
    251         if (mContent == null) {
    252             throw new IllegalArgumentException("The content attribute is must refer to an"
    253                     + " existing child.");
    254         }
    255         mContent.setVisibility(View.GONE);
    256     }
    257 
    258     @Override
    259     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    260         int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
    261         int widthSpecSize =  MeasureSpec.getSize(widthMeasureSpec);
    262 
    263         int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
    264         int heightSpecSize =  MeasureSpec.getSize(heightMeasureSpec);
    265 
    266         if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
    267             throw new RuntimeException("SlidingDrawer cannot have UNSPECIFIED dimensions");
    268         }
    269 
    270         final View handle = mHandle;
    271         measureChild(handle, widthMeasureSpec, heightMeasureSpec);
    272 
    273         if (mVertical) {
    274             int height = heightSpecSize - handle.getMeasuredHeight() - mTopOffset;
    275             mContent.measure(MeasureSpec.makeMeasureSpec(widthSpecSize, MeasureSpec.EXACTLY),
    276                     MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
    277         } else {
    278             int width = widthSpecSize - handle.getMeasuredWidth() - mTopOffset;
    279             mContent.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
    280                     MeasureSpec.makeMeasureSpec(heightSpecSize, MeasureSpec.EXACTLY));
    281         }
    282 
    283         setMeasuredDimension(widthSpecSize, heightSpecSize);
    284     }
    285 
    286     @Override
    287     protected void dispatchDraw(Canvas canvas) {
    288         final long drawingTime = getDrawingTime();
    289         final View handle = mHandle;
    290         final boolean isVertical = mVertical;
    291 
    292         drawChild(canvas, handle, drawingTime);
    293 
    294         if (mTracking || mAnimating) {
    295             final Bitmap cache = mContent.getDrawingCache();
    296             if (cache != null) {
    297                 if (isVertical) {
    298                     canvas.drawBitmap(cache, 0, handle.getBottom(), null);
    299                 } else {
    300                     canvas.drawBitmap(cache, handle.getRight(), 0, null);
    301                 }
    302             } else {
    303                 canvas.save();
    304                 canvas.translate(isVertical ? 0 : handle.getLeft() - mTopOffset,
    305                         isVertical ? handle.getTop() - mTopOffset : 0);
    306                 drawChild(canvas, mContent, drawingTime);
    307                 canvas.restore();
    308             }
    309         } else if (mExpanded) {
    310             drawChild(canvas, mContent, drawingTime);
    311         }
    312     }
    313 
    314     @Override
    315     protected void onLayout(boolean changed, int l, int t, int r, int b) {
    316         if (mTracking) {
    317             return;
    318         }
    319 
    320         final int width = r - l;
    321         final int height = b - t;
    322 
    323         final View handle = mHandle;
    324 
    325         int childWidth = handle.getMeasuredWidth();
    326         int childHeight = handle.getMeasuredHeight();
    327 
    328         int childLeft;
    329         int childTop;
    330 
    331         final View content = mContent;
    332 
    333         if (mVertical) {
    334             childLeft = (width - childWidth) / 2;
    335             childTop = mExpanded ? mTopOffset : height - childHeight + mBottomOffset;
    336 
    337             content.layout(0, mTopOffset + childHeight, content.getMeasuredWidth(),
    338                     mTopOffset + childHeight + content.getMeasuredHeight());
    339         } else {
    340             childLeft = mExpanded ? mTopOffset : width - childWidth + mBottomOffset;
    341             childTop = (height - childHeight) / 2;
    342 
    343             content.layout(mTopOffset + childWidth, 0,
    344                     mTopOffset + childWidth + content.getMeasuredWidth(),
    345                     content.getMeasuredHeight());
    346         }
    347 
    348         handle.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
    349         mHandleHeight = handle.getHeight();
    350         mHandleWidth = handle.getWidth();
    351     }
    352 
    353     @Override
    354     public boolean onInterceptTouchEvent(MotionEvent event) {
    355         if (mLocked) {
    356             return false;
    357         }
    358 
    359         final int action = event.getAction();
    360 
    361         float x = event.getX();
    362         float y = event.getY();
    363 
    364         final Rect frame = mFrame;
    365         final View handle = mHandle;
    366 
    367         handle.getHitRect(frame);
    368         if (!mTracking && !frame.contains((int) x, (int) y)) {
    369             return false;
    370         }
    371 
    372         if (action == MotionEvent.ACTION_DOWN) {
    373             mTracking = true;
    374 
    375             handle.setPressed(true);
    376             // Must be called before prepareTracking()
    377             prepareContent();
    378 
    379             // Must be called after prepareContent()
    380             if (mOnDrawerScrollListener != null) {
    381                 mOnDrawerScrollListener.onScrollStarted();
    382             }
    383 
    384             if (mVertical) {
    385                 final int top = mHandle.getTop();
    386                 mTouchDelta = (int) y - top;
    387                 prepareTracking(top);
    388             } else {
    389                 final int left = mHandle.getLeft();
    390                 mTouchDelta = (int) x - left;
    391                 prepareTracking(left);
    392             }
    393             mVelocityTracker.addMovement(event);
    394         }
    395 
    396         return true;
    397     }
    398 
    399     @Override
    400     public boolean onTouchEvent(MotionEvent event) {
    401         if (mLocked) {
    402             return true;
    403         }
    404 
    405         if (mTracking) {
    406             mVelocityTracker.addMovement(event);
    407             final int action = event.getAction();
    408             switch (action) {
    409                 case MotionEvent.ACTION_MOVE:
    410                     moveHandle((int) (mVertical ? event.getY() : event.getX()) - mTouchDelta);
    411                     break;
    412                 case MotionEvent.ACTION_UP:
    413                 case MotionEvent.ACTION_CANCEL: {
    414                     final VelocityTracker velocityTracker = mVelocityTracker;
    415                     velocityTracker.computeCurrentVelocity(mVelocityUnits);
    416 
    417                     float yVelocity = velocityTracker.getYVelocity();
    418                     float xVelocity = velocityTracker.getXVelocity();
    419                     boolean negative;
    420 
    421                     final boolean vertical = mVertical;
    422                     if (vertical) {
    423                         negative = yVelocity < 0;
    424                         if (xVelocity < 0) {
    425                             xVelocity = -xVelocity;
    426                         }
    427                         if (xVelocity > mMaximumMinorVelocity) {
    428                             xVelocity = mMaximumMinorVelocity;
    429                         }
    430                     } else {
    431                         negative = xVelocity < 0;
    432                         if (yVelocity < 0) {
    433                             yVelocity = -yVelocity;
    434                         }
    435                         if (yVelocity > mMaximumMinorVelocity) {
    436                             yVelocity = mMaximumMinorVelocity;
    437                         }
    438                     }
    439 
    440                     float velocity = (float) Math.hypot(xVelocity, yVelocity);
    441                     if (negative) {
    442                         velocity = -velocity;
    443                     }
    444 
    445                     final int top = mHandle.getTop();
    446                     final int left = mHandle.getLeft();
    447 
    448                     if (Math.abs(velocity) < mMaximumTapVelocity) {
    449                         if (vertical ? (mExpanded && top < mTapThreshold + mTopOffset) ||
    450                                 (!mExpanded && top > mBottomOffset + mBottom - mTop -
    451                                         mHandleHeight - mTapThreshold) :
    452                                 (mExpanded && left < mTapThreshold + mTopOffset) ||
    453                                 (!mExpanded && left > mBottomOffset + mRight - mLeft -
    454                                         mHandleWidth - mTapThreshold)) {
    455 
    456                             if (mAllowSingleTap) {
    457                                 playSoundEffect(SoundEffectConstants.CLICK);
    458 
    459                                 if (mExpanded) {
    460                                     animateClose(vertical ? top : left);
    461                                 } else {
    462                                     animateOpen(vertical ? top : left);
    463                                 }
    464                             } else {
    465                                 performFling(vertical ? top : left, velocity, false);
    466                             }
    467 
    468                         } else {
    469                             performFling(vertical ? top : left, velocity, false);
    470                         }
    471                     } else {
    472                         performFling(vertical ? top : left, velocity, false);
    473                     }
    474                 }
    475                 break;
    476             }
    477         }
    478 
    479         return mTracking || mAnimating || super.onTouchEvent(event);
    480     }
    481 
    482     private void animateClose(int position) {
    483         prepareTracking(position);
    484         performFling(position, mMaximumAcceleration, true);
    485     }
    486 
    487     private void animateOpen(int position) {
    488         prepareTracking(position);
    489         performFling(position, -mMaximumAcceleration, true);
    490     }
    491 
    492     private void performFling(int position, float velocity, boolean always) {
    493         mAnimationPosition = position;
    494         mAnimatedVelocity = velocity;
    495 
    496         if (mExpanded) {
    497             if (always || (velocity > mMaximumMajorVelocity ||
    498                     (position > mTopOffset + (mVertical ? mHandleHeight : mHandleWidth) &&
    499                             velocity > -mMaximumMajorVelocity))) {
    500                 // We are expanded, but they didn't move sufficiently to cause
    501                 // us to retract.  Animate back to the expanded position.
    502                 mAnimatedAcceleration = mMaximumAcceleration;
    503                 if (velocity < 0) {
    504                     mAnimatedVelocity = 0;
    505                 }
    506             } else {
    507                 // We are expanded and are now going to animate away.
    508                 mAnimatedAcceleration = -mMaximumAcceleration;
    509                 if (velocity > 0) {
    510                     mAnimatedVelocity = 0;
    511                 }
    512             }
    513         } else {
    514             if (!always && (velocity > mMaximumMajorVelocity ||
    515                     (position > (mVertical ? getHeight() : getWidth()) / 2 &&
    516                             velocity > -mMaximumMajorVelocity))) {
    517                 // We are collapsed, and they moved enough to allow us to expand.
    518                 mAnimatedAcceleration = mMaximumAcceleration;
    519                 if (velocity < 0) {
    520                     mAnimatedVelocity = 0;
    521                 }
    522             } else {
    523                 // We are collapsed, but they didn't move sufficiently to cause
    524                 // us to retract.  Animate back to the collapsed position.
    525                 mAnimatedAcceleration = -mMaximumAcceleration;
    526                 if (velocity > 0) {
    527                     mAnimatedVelocity = 0;
    528                 }
    529             }
    530         }
    531 
    532         long now = SystemClock.uptimeMillis();
    533         mAnimationLastTime = now;
    534         mCurrentAnimationTime = now + ANIMATION_FRAME_DURATION;
    535         mAnimating = true;
    536         mHandler.removeMessages(MSG_ANIMATE);
    537         mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE), mCurrentAnimationTime);
    538         stopTracking();
    539     }
    540 
    541     private void prepareTracking(int position) {
    542         mTracking = true;
    543         mVelocityTracker = VelocityTracker.obtain();
    544         boolean opening = !mExpanded;
    545         if (opening) {
    546             mAnimatedAcceleration = mMaximumAcceleration;
    547             mAnimatedVelocity = mMaximumMajorVelocity;
    548             mAnimationPosition = mBottomOffset +
    549                     (mVertical ? getHeight() - mHandleHeight : getWidth() - mHandleWidth);
    550             moveHandle((int) mAnimationPosition);
    551             mAnimating = true;
    552             mHandler.removeMessages(MSG_ANIMATE);
    553             long now = SystemClock.uptimeMillis();
    554             mAnimationLastTime = now;
    555             mCurrentAnimationTime = now + ANIMATION_FRAME_DURATION;
    556             mAnimating = true;
    557         } else {
    558             if (mAnimating) {
    559                 mAnimating = false;
    560                 mHandler.removeMessages(MSG_ANIMATE);
    561             }
    562             moveHandle(position);
    563         }
    564     }
    565 
    566     private void moveHandle(int position) {
    567         final View handle = mHandle;
    568 
    569         if (mVertical) {
    570             if (position == EXPANDED_FULL_OPEN) {
    571                 handle.offsetTopAndBottom(mTopOffset - handle.getTop());
    572                 invalidate();
    573             } else if (position == COLLAPSED_FULL_CLOSED) {
    574                 handle.offsetTopAndBottom(mBottomOffset + mBottom - mTop -
    575                         mHandleHeight - handle.getTop());
    576                 invalidate();
    577             } else {
    578                 final int top = handle.getTop();
    579                 int deltaY = position - top;
    580                 if (position < mTopOffset) {
    581                     deltaY = mTopOffset - top;
    582                 } else if (deltaY > mBottomOffset + mBottom - mTop - mHandleHeight - top) {
    583                     deltaY = mBottomOffset + mBottom - mTop - mHandleHeight - top;
    584                 }
    585                 handle.offsetTopAndBottom(deltaY);
    586 
    587                 final Rect frame = mFrame;
    588                 final Rect region = mInvalidate;
    589 
    590                 handle.getHitRect(frame);
    591                 region.set(frame);
    592 
    593                 region.union(frame.left, frame.top - deltaY, frame.right, frame.bottom - deltaY);
    594                 region.union(0, frame.bottom - deltaY, getWidth(),
    595                         frame.bottom - deltaY + mContent.getHeight());
    596 
    597                 invalidate(region);
    598             }
    599         } else {
    600             if (position == EXPANDED_FULL_OPEN) {
    601                 handle.offsetLeftAndRight(mTopOffset - handle.getLeft());
    602                 invalidate();
    603             } else if (position == COLLAPSED_FULL_CLOSED) {
    604                 handle.offsetLeftAndRight(mBottomOffset + mRight - mLeft -
    605                         mHandleWidth - handle.getLeft());
    606                 invalidate();
    607             } else {
    608                 final int left = handle.getLeft();
    609                 int deltaX = position - left;
    610                 if (position < mTopOffset) {
    611                     deltaX = mTopOffset - left;
    612                 } else if (deltaX > mBottomOffset + mRight - mLeft - mHandleWidth - left) {
    613                     deltaX = mBottomOffset + mRight - mLeft - mHandleWidth - left;
    614                 }
    615                 handle.offsetLeftAndRight(deltaX);
    616 
    617                 final Rect frame = mFrame;
    618                 final Rect region = mInvalidate;
    619 
    620                 handle.getHitRect(frame);
    621                 region.set(frame);
    622 
    623                 region.union(frame.left - deltaX, frame.top, frame.right - deltaX, frame.bottom);
    624                 region.union(frame.right - deltaX, 0,
    625                         frame.right - deltaX + mContent.getWidth(), getHeight());
    626 
    627                 invalidate(region);
    628             }
    629         }
    630     }
    631 
    632     private void prepareContent() {
    633         if (mAnimating) {
    634             return;
    635         }
    636 
    637         // Something changed in the content, we need to honor the layout request
    638         // before creating the cached bitmap
    639         final View content = mContent;
    640         if (content.isLayoutRequested()) {
    641             if (mVertical) {
    642                 final int childHeight = mHandleHeight;
    643                 int height = mBottom - mTop - childHeight - mTopOffset;
    644                 content.measure(MeasureSpec.makeMeasureSpec(mRight - mLeft, MeasureSpec.EXACTLY),
    645                         MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
    646                 content.layout(0, mTopOffset + childHeight, content.getMeasuredWidth(),
    647                         mTopOffset + childHeight + content.getMeasuredHeight());
    648             } else {
    649                 final int childWidth = mHandle.getWidth();
    650                 int width = mRight - mLeft - childWidth - mTopOffset;
    651                 content.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
    652                         MeasureSpec.makeMeasureSpec(mBottom - mTop, MeasureSpec.EXACTLY));
    653                 content.layout(childWidth + mTopOffset, 0,
    654                         mTopOffset + childWidth + content.getMeasuredWidth(),
    655                         content.getMeasuredHeight());
    656             }
    657         }
    658         // Try only once... we should really loop but it's not a big deal
    659         // if the draw was cancelled, it will only be temporary anyway
    660         content.getViewTreeObserver().dispatchOnPreDraw();
    661         if (!content.isHardwareAccelerated()) content.buildDrawingCache();
    662 
    663         content.setVisibility(View.GONE);
    664     }
    665 
    666     private void stopTracking() {
    667         mHandle.setPressed(false);
    668         mTracking = false;
    669 
    670         if (mOnDrawerScrollListener != null) {
    671             mOnDrawerScrollListener.onScrollEnded();
    672         }
    673 
    674         if (mVelocityTracker != null) {
    675             mVelocityTracker.recycle();
    676             mVelocityTracker = null;
    677         }
    678     }
    679 
    680     private void doAnimation() {
    681         if (mAnimating) {
    682             incrementAnimation();
    683             if (mAnimationPosition >= mBottomOffset + (mVertical ? getHeight() : getWidth()) - 1) {
    684                 mAnimating = false;
    685                 closeDrawer();
    686             } else if (mAnimationPosition < mTopOffset) {
    687                 mAnimating = false;
    688                 openDrawer();
    689             } else {
    690                 moveHandle((int) mAnimationPosition);
    691                 mCurrentAnimationTime += ANIMATION_FRAME_DURATION;
    692                 mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE),
    693                         mCurrentAnimationTime);
    694             }
    695         }
    696     }
    697 
    698     private void incrementAnimation() {
    699         long now = SystemClock.uptimeMillis();
    700         float t = (now - mAnimationLastTime) / 1000.0f;                   // ms -> s
    701         final float position = mAnimationPosition;
    702         final float v = mAnimatedVelocity;                                // px/s
    703         final float a = mAnimatedAcceleration;                            // px/s/s
    704         mAnimationPosition = position + (v * t) + (0.5f * a * t * t);     // px
    705         mAnimatedVelocity = v + (a * t);                                  // px/s
    706         mAnimationLastTime = now;                                         // ms
    707     }
    708 
    709     /**
    710      * Toggles the drawer open and close. Takes effect immediately.
    711      *
    712      * @see #open()
    713      * @see #close()
    714      * @see #animateClose()
    715      * @see #animateOpen()
    716      * @see #animateToggle()
    717      */
    718     public void toggle() {
    719         if (!mExpanded) {
    720             openDrawer();
    721         } else {
    722             closeDrawer();
    723         }
    724         invalidate();
    725         requestLayout();
    726     }
    727 
    728     /**
    729      * Toggles the drawer open and close with an animation.
    730      *
    731      * @see #open()
    732      * @see #close()
    733      * @see #animateClose()
    734      * @see #animateOpen()
    735      * @see #toggle()
    736      */
    737     public void animateToggle() {
    738         if (!mExpanded) {
    739             animateOpen();
    740         } else {
    741             animateClose();
    742         }
    743     }
    744 
    745     /**
    746      * Opens the drawer immediately.
    747      *
    748      * @see #toggle()
    749      * @see #close()
    750      * @see #animateOpen()
    751      */
    752     public void open() {
    753         openDrawer();
    754         invalidate();
    755         requestLayout();
    756 
    757         sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
    758     }
    759 
    760     /**
    761      * Closes the drawer immediately.
    762      *
    763      * @see #toggle()
    764      * @see #open()
    765      * @see #animateClose()
    766      */
    767     public void close() {
    768         closeDrawer();
    769         invalidate();
    770         requestLayout();
    771     }
    772 
    773     /**
    774      * Closes the drawer with an animation.
    775      *
    776      * @see #close()
    777      * @see #open()
    778      * @see #animateOpen()
    779      * @see #animateToggle()
    780      * @see #toggle()
    781      */
    782     public void animateClose() {
    783         prepareContent();
    784         final OnDrawerScrollListener scrollListener = mOnDrawerScrollListener;
    785         if (scrollListener != null) {
    786             scrollListener.onScrollStarted();
    787         }
    788         animateClose(mVertical ? mHandle.getTop() : mHandle.getLeft());
    789 
    790         if (scrollListener != null) {
    791             scrollListener.onScrollEnded();
    792         }
    793     }
    794 
    795     /**
    796      * Opens the drawer with an animation.
    797      *
    798      * @see #close()
    799      * @see #open()
    800      * @see #animateClose()
    801      * @see #animateToggle()
    802      * @see #toggle()
    803      */
    804     public void animateOpen() {
    805         prepareContent();
    806         final OnDrawerScrollListener scrollListener = mOnDrawerScrollListener;
    807         if (scrollListener != null) {
    808             scrollListener.onScrollStarted();
    809         }
    810         animateOpen(mVertical ? mHandle.getTop() : mHandle.getLeft());
    811 
    812         sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
    813 
    814         if (scrollListener != null) {
    815             scrollListener.onScrollEnded();
    816         }
    817     }
    818 
    819     @Override
    820     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
    821         super.onInitializeAccessibilityEvent(event);
    822         event.setClassName(SlidingDrawer.class.getName());
    823     }
    824 
    825     @Override
    826     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
    827         super.onInitializeAccessibilityNodeInfo(info);
    828         info.setClassName(SlidingDrawer.class.getName());
    829     }
    830 
    831     private void closeDrawer() {
    832         moveHandle(COLLAPSED_FULL_CLOSED);
    833         mContent.setVisibility(View.GONE);
    834         mContent.destroyDrawingCache();
    835 
    836         if (!mExpanded) {
    837             return;
    838         }
    839 
    840         mExpanded = false;
    841         if (mOnDrawerCloseListener != null) {
    842             mOnDrawerCloseListener.onDrawerClosed();
    843         }
    844     }
    845 
    846     private void openDrawer() {
    847         moveHandle(EXPANDED_FULL_OPEN);
    848         mContent.setVisibility(View.VISIBLE);
    849 
    850         if (mExpanded) {
    851             return;
    852         }
    853 
    854         mExpanded = true;
    855 
    856         if (mOnDrawerOpenListener != null) {
    857             mOnDrawerOpenListener.onDrawerOpened();
    858         }
    859     }
    860 
    861     /**
    862      * Sets the listener that receives a notification when the drawer becomes open.
    863      *
    864      * @param onDrawerOpenListener The listener to be notified when the drawer is opened.
    865      */
    866     public void setOnDrawerOpenListener(OnDrawerOpenListener onDrawerOpenListener) {
    867         mOnDrawerOpenListener = onDrawerOpenListener;
    868     }
    869 
    870     /**
    871      * Sets the listener that receives a notification when the drawer becomes close.
    872      *
    873      * @param onDrawerCloseListener The listener to be notified when the drawer is closed.
    874      */
    875     public void setOnDrawerCloseListener(OnDrawerCloseListener onDrawerCloseListener) {
    876         mOnDrawerCloseListener = onDrawerCloseListener;
    877     }
    878 
    879     /**
    880      * Sets the listener that receives a notification when the drawer starts or ends
    881      * a scroll. A fling is considered as a scroll. A fling will also trigger a
    882      * drawer opened or drawer closed event.
    883      *
    884      * @param onDrawerScrollListener The listener to be notified when scrolling
    885      *        starts or stops.
    886      */
    887     public void setOnDrawerScrollListener(OnDrawerScrollListener onDrawerScrollListener) {
    888         mOnDrawerScrollListener = onDrawerScrollListener;
    889     }
    890 
    891     /**
    892      * Returns the handle of the drawer.
    893      *
    894      * @return The View reprenseting the handle of the drawer, identified by
    895      *         the "handle" id in XML.
    896      */
    897     public View getHandle() {
    898         return mHandle;
    899     }
    900 
    901     /**
    902      * Returns the content of the drawer.
    903      *
    904      * @return The View reprenseting the content of the drawer, identified by
    905      *         the "content" id in XML.
    906      */
    907     public View getContent() {
    908         return mContent;
    909     }
    910 
    911     /**
    912      * Unlocks the SlidingDrawer so that touch events are processed.
    913      *
    914      * @see #lock()
    915      */
    916     public void unlock() {
    917         mLocked = false;
    918     }
    919 
    920     /**
    921      * Locks the SlidingDrawer so that touch events are ignores.
    922      *
    923      * @see #unlock()
    924      */
    925     public void lock() {
    926         mLocked = true;
    927     }
    928 
    929     /**
    930      * Indicates whether the drawer is currently fully opened.
    931      *
    932      * @return True if the drawer is opened, false otherwise.
    933      */
    934     public boolean isOpened() {
    935         return mExpanded;
    936     }
    937 
    938     /**
    939      * Indicates whether the drawer is scrolling or flinging.
    940      *
    941      * @return True if the drawer is scroller or flinging, false otherwise.
    942      */
    943     public boolean isMoving() {
    944         return mTracking || mAnimating;
    945     }
    946 
    947     private class DrawerToggler implements OnClickListener {
    948         public void onClick(View v) {
    949             if (mLocked) {
    950                 return;
    951             }
    952             // mAllowSingleTap isn't relevant here; you're *always*
    953             // allowed to open/close the drawer by clicking with the
    954             // trackball.
    955 
    956             if (mAnimateOnClick) {
    957                 animateToggle();
    958             } else {
    959                 toggle();
    960             }
    961         }
    962     }
    963 
    964     private class SlidingHandler extends Handler {
    965         public void handleMessage(Message m) {
    966             switch (m.what) {
    967                 case MSG_ANIMATE:
    968                     doAnimation();
    969                     break;
    970             }
    971         }
    972     }
    973 }
    974