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