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