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