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