DrawerLayout.java revision 3cf6141d516a0a6170823b1bda5c0b6d7500e76c
1/* 2 * Copyright (C) 2013 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 18package android.support.v4.widget; 19 20import android.content.Context; 21import android.content.res.TypedArray; 22import android.graphics.Canvas; 23import android.graphics.Paint; 24import android.graphics.PixelFormat; 25import android.graphics.Rect; 26import android.graphics.drawable.Drawable; 27import android.os.Parcel; 28import android.os.Parcelable; 29import android.os.SystemClock; 30import android.support.v4.view.AccessibilityDelegateCompat; 31import android.support.v4.view.GravityCompat; 32import android.support.v4.view.KeyEventCompat; 33import android.support.v4.view.MotionEventCompat; 34import android.support.v4.view.ViewCompat; 35import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; 36import android.util.AttributeSet; 37import android.view.Gravity; 38import android.view.KeyEvent; 39import android.view.MotionEvent; 40import android.view.View; 41import android.view.ViewGroup; 42import android.view.ViewParent; 43import android.view.accessibility.AccessibilityEvent; 44 45/** 46 * DrawerLayout acts as a top-level container for window content that allows for 47 * interactive "drawer" views to be pulled out from the edge of the window. 48 * 49 * <p>Drawer positioning and layout is controlled using the <code>android:layout_gravity</code> 50 * attribute on child views corresponding to which side of the view you want the drawer 51 * to emerge from: left or right. (Or start/end on platform versions that support layout direction.) 52 * </p> 53 * 54 * <p>To use a DrawerLayout, position your primary content view as the first child with 55 * a width and height of <code>match_parent</code>. Add drawers as child views after the main 56 * content view and set the <code>layout_gravity</code> appropriately. Drawers commonly use 57 * <code>match_parent</code> for height with a fixed width.</p> 58 * 59 * <p>{@link DrawerListener} can be used to monitor the state and motion of drawer views. 60 * Avoid performing expensive operations such as layout during animation as it can cause 61 * stuttering; try to perform expensive operations during the {@link #STATE_IDLE} state. 62 * {@link SimpleDrawerListener} offers default/no-op implementations of each callback method.</p> 63 * 64 * <p>As per the Android Design guide, any drawers positioned to the left/start should 65 * always contain content for navigating around the application, whereas any drawers 66 * positioned to the right/end should always contain actions to take on the current content. 67 * This preserves the same navigation left, actions right structure present in the Action Bar 68 * and elsewhere.</p> 69 */ 70public class DrawerLayout extends ViewGroup { 71 private static final String TAG = "DrawerLayout"; 72 73 /** 74 * Indicates that any drawers are in an idle, settled state. No animation is in progress. 75 */ 76 public static final int STATE_IDLE = ViewDragHelper.STATE_IDLE; 77 78 /** 79 * Indicates that a drawer is currently being dragged by the user. 80 */ 81 public static final int STATE_DRAGGING = ViewDragHelper.STATE_DRAGGING; 82 83 /** 84 * Indicates that a drawer is in the process of settling to a final position. 85 */ 86 public static final int STATE_SETTLING = ViewDragHelper.STATE_SETTLING; 87 88 /** 89 * The drawer is unlocked. 90 */ 91 public static final int LOCK_MODE_UNLOCKED = 0; 92 93 /** 94 * The drawer is locked closed. The user may not open it, though 95 * the app may open it programmatically. 96 */ 97 public static final int LOCK_MODE_LOCKED_CLOSED = 1; 98 99 /** 100 * The drawer is locked open. The user may not close it, though the app 101 * may close it programmatically. 102 */ 103 public static final int LOCK_MODE_LOCKED_OPEN = 2; 104 105 private static final int MIN_DRAWER_MARGIN = 64; // dp 106 107 private static final int DEFAULT_SCRIM_COLOR = 0x99000000; 108 109 /** 110 * Length of time to delay before peeking the drawer. 111 */ 112 private static final int PEEK_DELAY = 160; // ms 113 114 /** 115 * Experimental feature. 116 */ 117 private static final boolean ALLOW_EDGE_LOCK = false; 118 119 private static final int[] LAYOUT_ATTRS = new int[] { 120 android.R.attr.layout_gravity 121 }; 122 123 private int mMinDrawerMargin; 124 125 private int mScrimColor = DEFAULT_SCRIM_COLOR; 126 private float mScrimOpacity; 127 private Paint mScrimPaint = new Paint(); 128 129 private final ViewDragHelper mLeftDragger; 130 private final ViewDragHelper mRightDragger; 131 private final ViewDragCallback mLeftCallback; 132 private final ViewDragCallback mRightCallback; 133 private int mDrawerState; 134 private boolean mInLayout; 135 private boolean mFirstLayout = true; 136 private int mLockModeLeft; 137 private int mLockModeRight; 138 private boolean mDisallowInterceptRequested; 139 private boolean mChildrenCanceledTouch; 140 141 private DrawerListener mListener; 142 143 private float mInitialMotionX; 144 private float mInitialMotionY; 145 146 private Drawable mShadowLeft; 147 private Drawable mShadowRight; 148 149 /** 150 * Listener for monitoring events about drawers. 151 */ 152 public interface DrawerListener { 153 /** 154 * Called when a drawer's position changes. 155 * @param drawerView The child view that was moved 156 * @param slideOffset The new offset of this drawer within its range, from 0-1 157 */ 158 public void onDrawerSlide(View drawerView, float slideOffset); 159 160 /** 161 * Called when a drawer has settled in a completely open state. 162 * The drawer is interactive at this point. 163 * 164 * @param drawerView Drawer view that is now open 165 */ 166 public void onDrawerOpened(View drawerView); 167 168 /** 169 * Called when a drawer has settled in a completely closed state. 170 * 171 * @param drawerView Drawer view that is now closed 172 */ 173 public void onDrawerClosed(View drawerView); 174 175 /** 176 * Called when the drawer motion state changes. The new state will 177 * be one of {@link #STATE_IDLE}, {@link #STATE_DRAGGING} or {@link #STATE_SETTLING}. 178 * 179 * @param newState The new drawer motion state 180 */ 181 public void onDrawerStateChanged(int newState); 182 } 183 184 /** 185 * Stub/no-op implementations of all methods of {@link DrawerListener}. 186 * Override this if you only care about a few of the available callback methods. 187 */ 188 public static abstract class SimpleDrawerListener implements DrawerListener { 189 @Override 190 public void onDrawerSlide(View drawerView, float slideOffset) { 191 } 192 193 @Override 194 public void onDrawerOpened(View drawerView) { 195 } 196 197 @Override 198 public void onDrawerClosed(View drawerView) { 199 } 200 201 @Override 202 public void onDrawerStateChanged(int newState) { 203 } 204 } 205 206 public DrawerLayout(Context context) { 207 this(context, null); 208 } 209 210 public DrawerLayout(Context context, AttributeSet attrs) { 211 this(context, attrs, 0); 212 } 213 214 public DrawerLayout(Context context, AttributeSet attrs, int defStyle) { 215 super(context, attrs, defStyle); 216 217 final float density = getResources().getDisplayMetrics().density; 218 mMinDrawerMargin = (int) (MIN_DRAWER_MARGIN * density + 0.5f); 219 220 mLeftCallback = new ViewDragCallback(Gravity.LEFT); 221 mRightCallback = new ViewDragCallback(Gravity.RIGHT); 222 223 mLeftDragger = ViewDragHelper.create(this, 0.5f, mLeftCallback); 224 mLeftDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT); 225 mLeftCallback.setDragger(mLeftDragger); 226 227 mRightDragger = ViewDragHelper.create(this, 0.5f, mRightCallback); 228 mRightDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_RIGHT); 229 mRightCallback.setDragger(mRightDragger); 230 231 // So that we can catch the back button 232 setFocusableInTouchMode(true); 233 234 ViewCompat.setAccessibilityDelegate(this, new AccessibilityDelegate()); 235 } 236 237 /** 238 * Set a simple drawable used for the left or right shadow. 239 * The drawable provided must have a nonzero intrinsic width. 240 * 241 * @param shadowDrawable Shadow drawable to use at the edge of a drawer 242 * @param gravity Which drawer the shadow should apply to 243 */ 244 public void setDrawerShadow(Drawable shadowDrawable, int gravity) { 245 /* 246 * TODO Someone someday might want to set more complex drawables here. 247 * They're probably nuts, but we might want to consider registering callbacks, 248 * setting states, etc. properly. 249 */ 250 251 final int absGravity = GravityCompat.getAbsoluteGravity(gravity, 252 ViewCompat.getLayoutDirection(this)); 253 if ((absGravity & Gravity.LEFT) == Gravity.LEFT) { 254 mShadowLeft = shadowDrawable; 255 invalidate(); 256 } 257 if ((absGravity & Gravity.RIGHT) == Gravity.RIGHT) { 258 mShadowRight = shadowDrawable; 259 invalidate(); 260 } 261 } 262 263 /** 264 * Set a simple drawable used for the left or right shadow. 265 * The drawable provided must have a nonzero intrinsic width. 266 * 267 * @param resId Resource id of a shadow drawable to use at the edge of a drawer 268 * @param gravity Which drawer the shadow should apply to 269 */ 270 public void setDrawerShadow(int resId, int gravity) { 271 setDrawerShadow(getResources().getDrawable(resId), gravity); 272 } 273 274 /** 275 * Set a color to use for the scrim that obscures primary content while a drawer is open. 276 * 277 * @param color Color to use in 0xAARRGGBB format. 278 */ 279 public void setScrimColor(int color) { 280 mScrimColor = color; 281 invalidate(); 282 } 283 284 /** 285 * Set a listener to be notified of drawer events. 286 * 287 * @param listener Listener to notify when drawer events occur 288 * @see DrawerListener 289 */ 290 public void setDrawerListener(DrawerListener listener) { 291 mListener = listener; 292 } 293 294 /** 295 * Enable or disable interaction with all drawers. 296 * 297 * <p>This allows the application to restrict the user's ability to open or close 298 * any drawer within this layout. DrawerLayout will still respond to calls to 299 * {@link #openDrawer(int)}, {@link #closeDrawer(int)} and friends if a drawer is locked.</p> 300 * 301 * <p>Locking drawers open or closed will implicitly open or close 302 * any drawers as appropriate.</p> 303 * 304 * @param lockMode The new lock mode for the given drawer. One of {@link #LOCK_MODE_UNLOCKED}, 305 * {@link #LOCK_MODE_LOCKED_CLOSED} or {@link #LOCK_MODE_LOCKED_OPEN}. 306 */ 307 public void setDrawerLockMode(int lockMode) { 308 setDrawerLockMode(lockMode, Gravity.LEFT); 309 setDrawerLockMode(lockMode, Gravity.RIGHT); 310 } 311 312 /** 313 * Enable or disable interaction with the given drawer. 314 * 315 * <p>This allows the application to restrict the user's ability to open or close 316 * the given drawer. DrawerLayout will still respond to calls to {@link #openDrawer(int)}, 317 * {@link #closeDrawer(int)} and friends if a drawer is locked.</p> 318 * 319 * <p>Locking a drawer open or closed will implicitly open or close 320 * that drawer as appropriate.</p> 321 * 322 * @param lockMode The new lock mode for the given drawer. One of {@link #LOCK_MODE_UNLOCKED}, 323 * {@link #LOCK_MODE_LOCKED_CLOSED} or {@link #LOCK_MODE_LOCKED_OPEN}. 324 * @param edgeGravity Gravity.LEFT, RIGHT, START or END. 325 * Expresses which drawer to change the mode for. 326 * 327 * @see #LOCK_MODE_UNLOCKED 328 * @see #LOCK_MODE_LOCKED_CLOSED 329 * @see #LOCK_MODE_LOCKED_OPEN 330 */ 331 public void setDrawerLockMode(int lockMode, int edgeGravity) { 332 final int absGrav = GravityCompat.getAbsoluteGravity(edgeGravity, 333 ViewCompat.getLayoutDirection(this)); 334 if (absGrav == Gravity.LEFT) { 335 mLockModeLeft = lockMode; 336 } else if (absGrav == Gravity.RIGHT) { 337 mLockModeRight = lockMode; 338 } 339 if (lockMode != LOCK_MODE_UNLOCKED) { 340 // Cancel interaction in progress 341 final ViewDragHelper helper = absGrav == Gravity.LEFT ? mLeftDragger : mRightDragger; 342 helper.cancel(); 343 } 344 switch (lockMode) { 345 case LOCK_MODE_LOCKED_OPEN: 346 final View toOpen = findDrawerWithGravity(absGrav); 347 if (toOpen != null) { 348 openDrawer(toOpen); 349 } 350 break; 351 case LOCK_MODE_LOCKED_CLOSED: 352 final View toClose = findDrawerWithGravity(absGrav); 353 if (toClose != null) { 354 closeDrawer(toClose); 355 } 356 break; 357 // default: do nothing 358 } 359 } 360 361 /** 362 * Enable or disable interaction with the given drawer. 363 * 364 * <p>This allows the application to restrict the user's ability to open or close 365 * the given drawer. DrawerLayout will still respond to calls to {@link #openDrawer(int)}, 366 * {@link #closeDrawer(int)} and friends if a drawer is locked.</p> 367 * 368 * <p>Locking a drawer open or closed will implicitly open or close 369 * that drawer as appropriate.</p> 370 * 371 * @param lockMode The new lock mode for the given drawer. One of {@link #LOCK_MODE_UNLOCKED}, 372 * {@link #LOCK_MODE_LOCKED_CLOSED} or {@link #LOCK_MODE_LOCKED_OPEN}. 373 * @param drawerView The drawer view to change the lock mode for 374 * 375 * @see #LOCK_MODE_UNLOCKED 376 * @see #LOCK_MODE_LOCKED_CLOSED 377 * @see #LOCK_MODE_LOCKED_OPEN 378 */ 379 public void setDrawerLockMode(int lockMode, View drawerView) { 380 if (!isDrawerView(drawerView)) { 381 throw new IllegalArgumentException("View " + drawerView + " is not a " + 382 "drawer with appropriate layout_gravity"); 383 } 384 setDrawerLockMode(lockMode, getDrawerViewGravity(drawerView)); 385 } 386 387 /** 388 * Check the lock mode of the drawer with the given gravity. 389 * 390 * @param edgeGravity Gravity of the drawer to check 391 * @return one of {@link #LOCK_MODE_UNLOCKED}, {@link #LOCK_MODE_LOCKED_CLOSED} or 392 * {@link #LOCK_MODE_LOCKED_OPEN}. 393 */ 394 public int getDrawerLockMode(int edgeGravity) { 395 final int absGrav = GravityCompat.getAbsoluteGravity(edgeGravity, 396 ViewCompat.getLayoutDirection(this)); 397 if (absGrav == Gravity.LEFT) { 398 return mLockModeLeft; 399 } else if (absGrav == Gravity.RIGHT) { 400 return mLockModeRight; 401 } 402 return LOCK_MODE_UNLOCKED; 403 } 404 405 /** 406 * Check the lock mode of the given drawer view. 407 * 408 * @param drawerView Drawer view to check lock mode 409 * @return one of {@link #LOCK_MODE_UNLOCKED}, {@link #LOCK_MODE_LOCKED_CLOSED} or 410 * {@link #LOCK_MODE_LOCKED_OPEN}. 411 */ 412 public int getDrawerLockMode(View drawerView) { 413 final int gravity = getDrawerViewGravity(drawerView); 414 if (gravity == Gravity.LEFT) { 415 return mLockModeLeft; 416 } else if (gravity == Gravity.RIGHT) { 417 return mLockModeRight; 418 } 419 420 return LOCK_MODE_UNLOCKED; 421 } 422 423 /** 424 * Resolve the shared state of all drawers from the component ViewDragHelpers. 425 * Should be called whenever a ViewDragHelper's state changes. 426 */ 427 void updateDrawerState(int forGravity, int activeState, View activeDrawer) { 428 final int leftState = mLeftDragger.getViewDragState(); 429 final int rightState = mRightDragger.getViewDragState(); 430 431 final int state; 432 if (leftState == STATE_DRAGGING || rightState == STATE_DRAGGING) { 433 state = STATE_DRAGGING; 434 } else if (leftState == STATE_SETTLING || rightState == STATE_SETTLING) { 435 state = STATE_SETTLING; 436 } else { 437 state = STATE_IDLE; 438 } 439 440 if (activeDrawer != null && activeState == STATE_IDLE) { 441 final LayoutParams lp = (LayoutParams) activeDrawer.getLayoutParams(); 442 if (lp.onScreen == 0) { 443 dispatchOnDrawerClosed(activeDrawer); 444 } else if (lp.onScreen == 1) { 445 dispatchOnDrawerOpened(activeDrawer); 446 } 447 } 448 449 if (state != mDrawerState) { 450 mDrawerState = state; 451 452 if (mListener != null) { 453 mListener.onDrawerStateChanged(state); 454 } 455 } 456 } 457 458 void dispatchOnDrawerClosed(View drawerView) { 459 final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); 460 if (lp.knownOpen) { 461 lp.knownOpen = false; 462 if (mListener != null) { 463 mListener.onDrawerClosed(drawerView); 464 } 465 sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); 466 } 467 } 468 469 void dispatchOnDrawerOpened(View drawerView) { 470 final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); 471 if (!lp.knownOpen) { 472 lp.knownOpen = true; 473 if (mListener != null) { 474 mListener.onDrawerOpened(drawerView); 475 } 476 drawerView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); 477 } 478 } 479 480 void dispatchOnDrawerSlide(View drawerView, float slideOffset) { 481 if (mListener != null) { 482 mListener.onDrawerSlide(drawerView, slideOffset); 483 } 484 } 485 486 void setDrawerViewOffset(View drawerView, float slideOffset) { 487 final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); 488 if (slideOffset == lp.onScreen) { 489 return; 490 } 491 492 lp.onScreen = slideOffset; 493 dispatchOnDrawerSlide(drawerView, slideOffset); 494 } 495 496 float getDrawerViewOffset(View drawerView) { 497 return ((LayoutParams) drawerView.getLayoutParams()).onScreen; 498 } 499 500 int getDrawerViewGravity(View drawerView) { 501 final int gravity = ((LayoutParams) drawerView.getLayoutParams()).gravity; 502 return GravityCompat.getAbsoluteGravity(gravity, ViewCompat.getLayoutDirection(drawerView)); 503 } 504 505 boolean checkDrawerViewGravity(View drawerView, int checkFor) { 506 final int absGrav = getDrawerViewGravity(drawerView); 507 return (absGrav & checkFor) == checkFor; 508 } 509 510 View findOpenDrawer() { 511 final int childCount = getChildCount(); 512 for (int i = 0; i < childCount; i++) { 513 final View child = getChildAt(i); 514 if (((LayoutParams) child.getLayoutParams()).knownOpen) { 515 return child; 516 } 517 } 518 return null; 519 } 520 521 void moveDrawerToOffset(View drawerView, float slideOffset) { 522 final float oldOffset = getDrawerViewOffset(drawerView); 523 final int width = drawerView.getWidth(); 524 final int oldPos = (int) (width * oldOffset); 525 final int newPos = (int) (width * slideOffset); 526 final int dx = newPos - oldPos; 527 528 drawerView.offsetLeftAndRight(checkDrawerViewGravity(drawerView, Gravity.LEFT) ? dx : -dx); 529 setDrawerViewOffset(drawerView, slideOffset); 530 } 531 532 View findDrawerWithGravity(int gravity) { 533 final int childCount = getChildCount(); 534 for (int i = 0; i < childCount; i++) { 535 final View child = getChildAt(i); 536 final int childGravity = getDrawerViewGravity(child); 537 if ((childGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 538 (gravity & Gravity.HORIZONTAL_GRAVITY_MASK)) { 539 return child; 540 } 541 } 542 return null; 543 } 544 545 /** 546 * Simple gravity to string - only supports LEFT and RIGHT for debugging output. 547 * 548 * @param gravity Absolute gravity value 549 * @return LEFT or RIGHT as appropriate, or a hex string 550 */ 551 static String gravityToString(int gravity) { 552 if ((gravity & Gravity.LEFT) == Gravity.LEFT) { 553 return "LEFT"; 554 } 555 if ((gravity & Gravity.RIGHT) == Gravity.RIGHT) { 556 return "RIGHT"; 557 } 558 return Integer.toHexString(gravity); 559 } 560 561 @Override 562 protected void onDetachedFromWindow() { 563 super.onDetachedFromWindow(); 564 mFirstLayout = true; 565 } 566 567 @Override 568 protected void onAttachedToWindow() { 569 super.onAttachedToWindow(); 570 mFirstLayout = true; 571 } 572 573 @Override 574 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 575 final int widthMode = MeasureSpec.getMode(widthMeasureSpec); 576 final int heightMode = MeasureSpec.getMode(heightMeasureSpec); 577 final int widthSize = MeasureSpec.getSize(widthMeasureSpec); 578 final int heightSize = MeasureSpec.getSize(heightMeasureSpec); 579 580 if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) { 581 throw new IllegalArgumentException( 582 "DrawerLayout must be measured with MeasureSpec.EXACTLY."); 583 } 584 585 setMeasuredDimension(widthSize, heightSize); 586 587 // Gravity value for each drawer we've seen. Only one of each permitted. 588 int foundDrawers = 0; 589 final int childCount = getChildCount(); 590 for (int i = 0; i < childCount; i++) { 591 final View child = getChildAt(i); 592 593 if (child.getVisibility() == GONE) { 594 continue; 595 } 596 597 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 598 599 if (isContentView(child)) { 600 // Content views get measured at exactly the layout's size. 601 final int contentWidthSpec = MeasureSpec.makeMeasureSpec( 602 widthSize - lp.leftMargin - lp.rightMargin, MeasureSpec.EXACTLY); 603 final int contentHeightSpec = MeasureSpec.makeMeasureSpec( 604 heightSize - lp.topMargin - lp.bottomMargin, MeasureSpec.EXACTLY); 605 child.measure(contentWidthSpec, contentHeightSpec); 606 } else if (isDrawerView(child)) { 607 final int childGravity = 608 getDrawerViewGravity(child) & Gravity.HORIZONTAL_GRAVITY_MASK; 609 if ((foundDrawers & childGravity) != 0) { 610 throw new IllegalStateException("Child drawer has absolute gravity " + 611 gravityToString(childGravity) + " but this " + TAG + " already has a " + 612 "drawer view along that edge"); 613 } 614 final int drawerWidthSpec = getChildMeasureSpec(widthMeasureSpec, 615 mMinDrawerMargin + lp.leftMargin + lp.rightMargin, 616 lp.width); 617 final int drawerHeightSpec = getChildMeasureSpec(heightMeasureSpec, 618 lp.topMargin + lp.bottomMargin, 619 lp.height); 620 child.measure(drawerWidthSpec, drawerHeightSpec); 621 } else { 622 throw new IllegalStateException("Child " + child + " at index " + i + 623 " does not have a valid layout_gravity - must be Gravity.LEFT, " + 624 "Gravity.RIGHT or Gravity.NO_GRAVITY"); 625 } 626 } 627 } 628 629 @Override 630 protected void onLayout(boolean changed, int l, int t, int r, int b) { 631 mInLayout = true; 632 final int childCount = getChildCount(); 633 for (int i = 0; i < childCount; i++) { 634 final View child = getChildAt(i); 635 636 if (child.getVisibility() == GONE) { 637 continue; 638 } 639 640 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 641 642 if (isContentView(child)) { 643 child.layout(lp.leftMargin, lp.topMargin, 644 lp.leftMargin + child.getMeasuredWidth(), 645 lp.topMargin + child.getMeasuredHeight()); 646 } else { // Drawer, if it wasn't onMeasure would have thrown an exception. 647 final int childWidth = child.getMeasuredWidth(); 648 final int childHeight = child.getMeasuredHeight(); 649 int childLeft; 650 651 if (checkDrawerViewGravity(child, Gravity.LEFT)) { 652 childLeft = -childWidth + (int) (childWidth * lp.onScreen); 653 } else { // Right; onMeasure checked for us. 654 childLeft = r - l - (int) (childWidth * lp.onScreen); 655 } 656 657 final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK; 658 659 switch (vgrav) { 660 default: 661 case Gravity.TOP: { 662 child.layout(childLeft, lp.topMargin, childLeft + childWidth, childHeight); 663 break; 664 } 665 666 case Gravity.BOTTOM: { 667 final int height = b - t; 668 child.layout(childLeft, 669 height - lp.bottomMargin - child.getMeasuredHeight(), 670 childLeft + childWidth, 671 height - lp.bottomMargin); 672 break; 673 } 674 675 case Gravity.CENTER_VERTICAL: { 676 final int height = b - t; 677 int childTop = (height - childHeight) / 2; 678 679 // Offset for margins. If things don't fit right because of 680 // bad measurement before, oh well. 681 if (childTop < lp.topMargin) { 682 childTop = lp.topMargin; 683 } else if (childTop + childHeight > height - lp.bottomMargin) { 684 childTop = height - lp.bottomMargin - childHeight; 685 } 686 child.layout(childLeft, childTop, childLeft + childWidth, 687 childTop + childHeight); 688 break; 689 } 690 } 691 692 if (lp.onScreen == 0) { 693 child.setVisibility(INVISIBLE); 694 } 695 } 696 } 697 mInLayout = false; 698 mFirstLayout = false; 699 } 700 701 @Override 702 public void requestLayout() { 703 if (!mInLayout) { 704 super.requestLayout(); 705 } 706 } 707 708 @Override 709 public void computeScroll() { 710 final int childCount = getChildCount(); 711 float scrimOpacity = 0; 712 for (int i = 0; i < childCount; i++) { 713 final float onscreen = ((LayoutParams) getChildAt(i).getLayoutParams()).onScreen; 714 scrimOpacity = Math.max(scrimOpacity, onscreen); 715 } 716 mScrimOpacity = scrimOpacity; 717 718 // "|" used on purpose; both need to run. 719 if (mLeftDragger.continueSettling(true) | mRightDragger.continueSettling(true)) { 720 ViewCompat.postInvalidateOnAnimation(this); 721 } 722 } 723 724 private static boolean hasOpaqueBackground(View v) { 725 final Drawable bg = v.getBackground(); 726 if (bg != null) { 727 return bg.getOpacity() == PixelFormat.OPAQUE; 728 } 729 return false; 730 } 731 732 @Override 733 protected boolean drawChild(Canvas canvas, View child, long drawingTime) { 734 final int height = getHeight(); 735 final boolean drawingContent = isContentView(child); 736 int clipLeft = 0, clipRight = getWidth(); 737 738 final int restoreCount = canvas.save(); 739 if (drawingContent) { 740 final int childCount = getChildCount(); 741 for (int i = 0; i < childCount; i++) { 742 final View v = getChildAt(i); 743 if (v == child || v.getVisibility() != VISIBLE || 744 !hasOpaqueBackground(v) || !isDrawerView(v) || 745 v.getHeight() < height) { 746 continue; 747 } 748 749 if (checkDrawerViewGravity(v, Gravity.LEFT)) { 750 final int vright = v.getRight(); 751 if (vright > clipLeft) clipLeft = vright; 752 } else { 753 final int vleft = v.getLeft(); 754 if (vleft < clipRight) clipRight = vleft; 755 } 756 } 757 canvas.clipRect(clipLeft, 0, clipRight, getHeight()); 758 } 759 final boolean result = super.drawChild(canvas, child, drawingTime); 760 canvas.restoreToCount(restoreCount); 761 762 if (mScrimOpacity > 0 && drawingContent) { 763 final int baseAlpha = (mScrimColor & 0xff000000) >>> 24; 764 final int imag = (int) (baseAlpha * mScrimOpacity); 765 final int color = imag << 24 | (mScrimColor & 0xffffff); 766 mScrimPaint.setColor(color); 767 768 canvas.drawRect(clipLeft, 0, clipRight, getHeight(), mScrimPaint); 769 } else if (mShadowLeft != null && checkDrawerViewGravity(child, Gravity.LEFT)) { 770 final int shadowWidth = mShadowLeft.getIntrinsicWidth(); 771 final int childRight = child.getRight(); 772 final int drawerPeekDistance = mLeftDragger.getEdgeSize(); 773 final float alpha = 774 Math.max(0, Math.min((float) childRight / drawerPeekDistance, 1.f)); 775 mShadowLeft.setBounds(childRight, child.getTop(), 776 childRight + shadowWidth, child.getBottom()); 777 mShadowLeft.setAlpha((int) (0xff * alpha)); 778 mShadowLeft.draw(canvas); 779 } else if (mShadowRight != null && checkDrawerViewGravity(child, Gravity.RIGHT)) { 780 final int shadowWidth = mShadowRight.getIntrinsicWidth(); 781 final int childLeft = child.getLeft(); 782 final int showing = getWidth() - childLeft; 783 final int drawerPeekDistance = mRightDragger.getEdgeSize(); 784 final float alpha = 785 Math.max(0, Math.min((float) showing / drawerPeekDistance, 1.f)); 786 mShadowRight.setBounds(childLeft - shadowWidth, child.getTop(), 787 childLeft, child.getBottom()); 788 mShadowRight.setAlpha((int) (0xff * alpha)); 789 mShadowRight.draw(canvas); 790 } 791 return result; 792 } 793 794 boolean isContentView(View child) { 795 return ((LayoutParams) child.getLayoutParams()).gravity == Gravity.NO_GRAVITY; 796 } 797 798 boolean isDrawerView(View child) { 799 final int gravity = ((LayoutParams) child.getLayoutParams()).gravity; 800 final int absGravity = GravityCompat.getAbsoluteGravity(gravity, 801 ViewCompat.getLayoutDirection(child)); 802 return (absGravity & (Gravity.LEFT | Gravity.RIGHT)) != 0; 803 } 804 805 @Override 806 public boolean onInterceptTouchEvent(MotionEvent ev) { 807 final int action = MotionEventCompat.getActionMasked(ev); 808 809 // "|" used deliberately here; both methods should be invoked. 810 final boolean interceptForDrag = mLeftDragger.shouldInterceptTouchEvent(ev) | 811 mRightDragger.shouldInterceptTouchEvent(ev); 812 813 boolean interceptForTap = false; 814 815 switch (action) { 816 case MotionEvent.ACTION_DOWN: { 817 final float x = ev.getX(); 818 final float y = ev.getY(); 819 mInitialMotionX = x; 820 mInitialMotionY = y; 821 if (mScrimOpacity > 0 && 822 isContentView(mLeftDragger.findTopChildUnder((int) x, (int) y))) { 823 interceptForTap = true; 824 } 825 mDisallowInterceptRequested = false; 826 mChildrenCanceledTouch = false; 827 break; 828 } 829 830 case MotionEvent.ACTION_MOVE: { 831 // If we cross the touch slop, don't perform the delayed peek for an edge touch. 832 if (mLeftDragger.checkTouchSlop(ViewDragHelper.DIRECTION_ALL)) { 833 mLeftCallback.removeCallbacks(); 834 mRightCallback.removeCallbacks(); 835 } 836 break; 837 } 838 839 case MotionEvent.ACTION_CANCEL: 840 case MotionEvent.ACTION_UP: { 841 closeDrawers(true); 842 mDisallowInterceptRequested = false; 843 mChildrenCanceledTouch = false; 844 } 845 } 846 847 return interceptForDrag || interceptForTap || hasPeekingDrawer() || mChildrenCanceledTouch; 848 } 849 850 @Override 851 public boolean onTouchEvent(MotionEvent ev) { 852 mLeftDragger.processTouchEvent(ev); 853 mRightDragger.processTouchEvent(ev); 854 855 final int action = ev.getAction(); 856 boolean wantTouchEvents = true; 857 858 switch (action & MotionEventCompat.ACTION_MASK) { 859 case MotionEvent.ACTION_DOWN: { 860 final float x = ev.getX(); 861 final float y = ev.getY(); 862 mInitialMotionX = x; 863 mInitialMotionY = y; 864 mDisallowInterceptRequested = false; 865 mChildrenCanceledTouch = false; 866 break; 867 } 868 869 case MotionEvent.ACTION_UP: { 870 final float x = ev.getX(); 871 final float y = ev.getY(); 872 boolean peekingOnly = true; 873 final View touchedView = mLeftDragger.findTopChildUnder((int) x, (int) y); 874 if (touchedView != null && isContentView(touchedView)) { 875 final float dx = x - mInitialMotionX; 876 final float dy = y - mInitialMotionY; 877 final int slop = mLeftDragger.getTouchSlop(); 878 if (dx * dx + dy * dy < slop * slop) { 879 // Taps close a dimmed open drawer but only if it isn't locked open. 880 final View openDrawer = findOpenDrawer(); 881 if (openDrawer != null) { 882 peekingOnly = getDrawerLockMode(openDrawer) == LOCK_MODE_LOCKED_OPEN; 883 } 884 } 885 } 886 closeDrawers(peekingOnly); 887 mDisallowInterceptRequested = false; 888 break; 889 } 890 891 case MotionEvent.ACTION_CANCEL: { 892 closeDrawers(true); 893 mDisallowInterceptRequested = false; 894 mChildrenCanceledTouch = false; 895 break; 896 } 897 } 898 899 return wantTouchEvents; 900 } 901 902 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { 903 if (!mLeftDragger.isEdgeTouched(ViewDragHelper.EDGE_LEFT) && 904 !mRightDragger.isEdgeTouched(ViewDragHelper.EDGE_RIGHT)) { 905 // If we have an edge touch we want to skip this and track it for later instead. 906 super.requestDisallowInterceptTouchEvent(disallowIntercept); 907 } 908 mDisallowInterceptRequested = disallowIntercept; 909 if (disallowIntercept) { 910 closeDrawers(true); 911 } 912 } 913 914 /** 915 * Close all currently open drawer views by animating them out of view. 916 */ 917 public void closeDrawers() { 918 closeDrawers(false); 919 } 920 921 void closeDrawers(boolean peekingOnly) { 922 boolean needsInvalidate = false; 923 final int childCount = getChildCount(); 924 for (int i = 0; i < childCount; i++) { 925 final View child = getChildAt(i); 926 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 927 928 if (!isDrawerView(child) || (peekingOnly && !lp.isPeeking)) { 929 continue; 930 } 931 932 final int childWidth = child.getWidth(); 933 934 if (checkDrawerViewGravity(child, Gravity.LEFT)) { 935 needsInvalidate |= mLeftDragger.smoothSlideViewTo(child, 936 -childWidth, child.getTop()); 937 } else { 938 needsInvalidate |= mRightDragger.smoothSlideViewTo(child, 939 getWidth(), child.getTop()); 940 } 941 942 lp.isPeeking = false; 943 } 944 945 mLeftCallback.removeCallbacks(); 946 mRightCallback.removeCallbacks(); 947 948 if (needsInvalidate) { 949 invalidate(); 950 } 951 } 952 953 /** 954 * Open the specified drawer view by animating it into view. 955 * 956 * @param drawerView Drawer view to open 957 */ 958 public void openDrawer(View drawerView) { 959 if (!isDrawerView(drawerView)) { 960 throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer"); 961 } 962 963 if (mFirstLayout) { 964 final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); 965 lp.onScreen = 1.f; 966 lp.knownOpen = true; 967 } else { 968 if (checkDrawerViewGravity(drawerView, Gravity.LEFT)) { 969 mLeftDragger.smoothSlideViewTo(drawerView, 0, drawerView.getTop()); 970 } else { 971 mRightDragger.smoothSlideViewTo(drawerView, getWidth() - drawerView.getWidth(), 972 drawerView.getTop()); 973 } 974 } 975 invalidate(); 976 } 977 978 /** 979 * Open the specified drawer by animating it out of view. 980 * 981 * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right. 982 * GravityCompat.START or GravityCompat.END may also be used. 983 */ 984 public void openDrawer(int gravity) { 985 final int absGravity = GravityCompat.getAbsoluteGravity(gravity, 986 ViewCompat.getLayoutDirection(this)); 987 final View drawerView = findDrawerWithGravity(absGravity); 988 989 if (drawerView == null) { 990 throw new IllegalArgumentException("No drawer view found with absolute gravity " + 991 gravityToString(absGravity)); 992 } 993 openDrawer(drawerView); 994 } 995 996 /** 997 * Close the specified drawer view by animating it into view. 998 * 999 * @param drawerView Drawer view to close 1000 */ 1001 public void closeDrawer(View drawerView) { 1002 if (!isDrawerView(drawerView)) { 1003 throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer"); 1004 } 1005 1006 if (mFirstLayout) { 1007 final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); 1008 lp.onScreen = 0.f; 1009 lp.knownOpen = false; 1010 } else { 1011 if (checkDrawerViewGravity(drawerView, Gravity.LEFT)) { 1012 mLeftDragger.smoothSlideViewTo(drawerView, -drawerView.getWidth(), 1013 drawerView.getTop()); 1014 } else { 1015 mRightDragger.smoothSlideViewTo(drawerView, getWidth(), drawerView.getTop()); 1016 } 1017 } 1018 invalidate(); 1019 } 1020 1021 /** 1022 * Close the specified drawer by animating it out of view. 1023 * 1024 * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right. 1025 * GravityCompat.START or GravityCompat.END may also be used. 1026 */ 1027 public void closeDrawer(int gravity) { 1028 final int absGravity = GravityCompat.getAbsoluteGravity(gravity, 1029 ViewCompat.getLayoutDirection(this)); 1030 final View drawerView = findDrawerWithGravity(absGravity); 1031 1032 if (drawerView == null) { 1033 throw new IllegalArgumentException("No drawer view found with absolute gravity " + 1034 gravityToString(absGravity)); 1035 } 1036 closeDrawer(drawerView); 1037 } 1038 1039 /** 1040 * Check if the given drawer view is currently in an open state. 1041 * To be considered "open" the drawer must have settled into its fully 1042 * visible state. To check for partial visibility use 1043 * {@link #isDrawerVisible(android.view.View)}. 1044 * 1045 * @param drawer Drawer view to check 1046 * @return true if the given drawer view is in an open state 1047 * @see #isDrawerVisible(android.view.View) 1048 */ 1049 public boolean isDrawerOpen(View drawer) { 1050 if (!isDrawerView(drawer)) { 1051 throw new IllegalArgumentException("View " + drawer + " is not a drawer"); 1052 } 1053 return ((LayoutParams) drawer.getLayoutParams()).knownOpen; 1054 } 1055 1056 /** 1057 * Check if a given drawer view is currently visible on-screen. The drawer 1058 * may be only peeking onto the screen, fully extended, or anywhere inbetween. 1059 * 1060 * @param drawer Drawer view to check 1061 * @return true if the given drawer is visible on-screen 1062 * @see #isDrawerOpen(android.view.View) 1063 */ 1064 public boolean isDrawerVisible(View drawer) { 1065 if (!isDrawerView(drawer)) { 1066 throw new IllegalArgumentException("View " + drawer + " is not a drawer"); 1067 } 1068 return ((LayoutParams) drawer.getLayoutParams()).onScreen > 0; 1069 } 1070 1071 private boolean hasPeekingDrawer() { 1072 final int childCount = getChildCount(); 1073 for (int i = 0; i < childCount; i++) { 1074 final LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams(); 1075 if (lp.isPeeking) { 1076 return true; 1077 } 1078 } 1079 return false; 1080 } 1081 1082 @Override 1083 protected ViewGroup.LayoutParams generateDefaultLayoutParams() { 1084 return new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT); 1085 } 1086 1087 @Override 1088 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 1089 return p instanceof LayoutParams 1090 ? new LayoutParams((LayoutParams) p) 1091 : p instanceof ViewGroup.MarginLayoutParams 1092 ? new LayoutParams((MarginLayoutParams) p) 1093 : new LayoutParams(p); 1094 } 1095 1096 @Override 1097 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 1098 return p instanceof LayoutParams && super.checkLayoutParams(p); 1099 } 1100 1101 @Override 1102 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { 1103 return new LayoutParams(getContext(), attrs); 1104 } 1105 1106 private boolean hasVisibleDrawer() { 1107 return findVisibleDrawer() != null; 1108 } 1109 1110 private View findVisibleDrawer() { 1111 final int childCount = getChildCount(); 1112 for (int i = 0; i < childCount; i++) { 1113 final View child = getChildAt(i); 1114 if (isDrawerView(child) && isDrawerVisible(child)) { 1115 return child; 1116 } 1117 } 1118 return null; 1119 } 1120 1121 void cancelChildViewTouch() { 1122 // Cancel child touches 1123 if (!mChildrenCanceledTouch) { 1124 final long now = SystemClock.uptimeMillis(); 1125 final MotionEvent cancelEvent = MotionEvent.obtain(now, now, 1126 MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0); 1127 final int childCount = getChildCount(); 1128 for (int i = 0; i < childCount; i++) { 1129 getChildAt(i).dispatchTouchEvent(cancelEvent); 1130 } 1131 cancelEvent.recycle(); 1132 mChildrenCanceledTouch = true; 1133 } 1134 } 1135 1136 @Override 1137 public boolean onKeyDown(int keyCode, KeyEvent event) { 1138 if (keyCode == KeyEvent.KEYCODE_BACK && hasVisibleDrawer()) { 1139 KeyEventCompat.startTracking(event); 1140 return true; 1141 } 1142 return super.onKeyDown(keyCode, event); 1143 } 1144 1145 @Override 1146 public boolean onKeyUp(int keyCode, KeyEvent event) { 1147 if (keyCode == KeyEvent.KEYCODE_BACK) { 1148 final View visibleDrawer = findVisibleDrawer(); 1149 if (visibleDrawer != null && getDrawerLockMode(visibleDrawer) == LOCK_MODE_UNLOCKED) { 1150 closeDrawers(); 1151 } 1152 return visibleDrawer != null; 1153 } 1154 return super.onKeyUp(keyCode, event); 1155 } 1156 1157 @Override 1158 protected void onRestoreInstanceState(Parcelable state) { 1159 final SavedState ss = (SavedState) state; 1160 super.onRestoreInstanceState(ss.getSuperState()); 1161 1162 if (ss.openDrawerGravity != Gravity.NO_GRAVITY) { 1163 final View toOpen = findDrawerWithGravity(ss.openDrawerGravity); 1164 if (toOpen != null) { 1165 openDrawer(toOpen); 1166 } 1167 } 1168 1169 setDrawerLockMode(ss.lockModeLeft, Gravity.LEFT); 1170 setDrawerLockMode(ss.lockModeRight, Gravity.RIGHT); 1171 } 1172 1173 @Override 1174 protected Parcelable onSaveInstanceState() { 1175 final Parcelable superState = super.onSaveInstanceState(); 1176 1177 final SavedState ss = new SavedState(superState); 1178 1179 final int childCount = getChildCount(); 1180 for (int i = 0; i < childCount; i++) { 1181 final View child = getChildAt(i); 1182 if (!isDrawerView(child)) { 1183 continue; 1184 } 1185 1186 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 1187 if (lp.knownOpen) { 1188 ss.openDrawerGravity = lp.gravity; 1189 // Only one drawer can be open at a time. 1190 break; 1191 } 1192 } 1193 1194 ss.lockModeLeft = mLockModeLeft; 1195 ss.lockModeRight = mLockModeRight; 1196 1197 return ss; 1198 } 1199 1200 /** 1201 * State persisted across instances 1202 */ 1203 protected static class SavedState extends BaseSavedState { 1204 int openDrawerGravity = Gravity.NO_GRAVITY; 1205 int lockModeLeft = LOCK_MODE_UNLOCKED; 1206 int lockModeRight = LOCK_MODE_UNLOCKED; 1207 1208 public SavedState(Parcel in) { 1209 super(in); 1210 openDrawerGravity = in.readInt(); 1211 } 1212 1213 public SavedState(Parcelable superState) { 1214 super(superState); 1215 } 1216 1217 @Override 1218 public void writeToParcel(Parcel dest, int flags) { 1219 super.writeToParcel(dest, flags); 1220 dest.writeInt(openDrawerGravity); 1221 } 1222 1223 public static final Parcelable.Creator<SavedState> CREATOR = 1224 new Parcelable.Creator<SavedState>() { 1225 @Override 1226 public SavedState createFromParcel(Parcel source) { 1227 return new SavedState(source); 1228 } 1229 1230 @Override 1231 public SavedState[] newArray(int size) { 1232 return new SavedState[size]; 1233 } 1234 }; 1235 } 1236 1237 private class ViewDragCallback extends ViewDragHelper.Callback { 1238 private final int mGravity; 1239 private ViewDragHelper mDragger; 1240 1241 private final Runnable mPeekRunnable = new Runnable() { 1242 @Override public void run() { 1243 peekDrawer(); 1244 } 1245 }; 1246 1247 public ViewDragCallback(int gravity) { 1248 mGravity = gravity; 1249 } 1250 1251 public void setDragger(ViewDragHelper dragger) { 1252 mDragger = dragger; 1253 } 1254 1255 public void removeCallbacks() { 1256 DrawerLayout.this.removeCallbacks(mPeekRunnable); 1257 } 1258 1259 @Override 1260 public boolean tryCaptureView(View child, int pointerId) { 1261 // Only capture views where the gravity matches what we're looking for. 1262 // This lets us use two ViewDragHelpers, one for each side drawer. 1263 return isDrawerView(child) && checkDrawerViewGravity(child, mGravity) && 1264 getDrawerLockMode(child) == LOCK_MODE_UNLOCKED; 1265 } 1266 1267 @Override 1268 public void onViewDragStateChanged(int state) { 1269 updateDrawerState(mGravity, state, mDragger.getCapturedView()); 1270 } 1271 1272 @Override 1273 public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { 1274 float offset; 1275 final int childWidth = changedView.getWidth(); 1276 1277 // This reverses the positioning shown in onLayout. 1278 if (checkDrawerViewGravity(changedView, Gravity.LEFT)) { 1279 offset = (float) (childWidth + left) / childWidth; 1280 } else { 1281 final int width = getWidth(); 1282 offset = (float) (width - left) / childWidth; 1283 } 1284 setDrawerViewOffset(changedView, offset); 1285 changedView.setVisibility(offset == 0 ? INVISIBLE : VISIBLE); 1286 invalidate(); 1287 } 1288 1289 @Override 1290 public void onViewCaptured(View capturedChild, int activePointerId) { 1291 final LayoutParams lp = (LayoutParams) capturedChild.getLayoutParams(); 1292 lp.isPeeking = false; 1293 1294 closeOtherDrawer(); 1295 } 1296 1297 private void closeOtherDrawer() { 1298 final int otherGrav = mGravity == Gravity.LEFT ? Gravity.RIGHT : Gravity.LEFT; 1299 final View toClose = findDrawerWithGravity(otherGrav); 1300 if (toClose != null) { 1301 closeDrawer(toClose); 1302 } 1303 } 1304 1305 @Override 1306 public void onViewReleased(View releasedChild, float xvel, float yvel) { 1307 // Offset is how open the drawer is, therefore left/right values 1308 // are reversed from one another. 1309 final float offset = getDrawerViewOffset(releasedChild); 1310 final int childWidth = releasedChild.getWidth(); 1311 1312 int left; 1313 if (checkDrawerViewGravity(releasedChild, Gravity.LEFT)) { 1314 left = xvel > 0 || xvel == 0 && offset > 0.5f ? 0 : -childWidth; 1315 } else { 1316 final int width = getWidth(); 1317 left = xvel < 0 || xvel == 0 && offset < 0.5f ? width - childWidth : width; 1318 } 1319 1320 mDragger.settleCapturedViewAt(left, releasedChild.getTop()); 1321 invalidate(); 1322 } 1323 1324 @Override 1325 public void onEdgeTouched(int edgeFlags, int pointerId) { 1326 postDelayed(mPeekRunnable, PEEK_DELAY); 1327 } 1328 1329 private void peekDrawer() { 1330 final View toCapture; 1331 final int childLeft; 1332 final int peekDistance = mDragger.getEdgeSize(); 1333 final boolean leftEdge = mGravity == Gravity.LEFT; 1334 if (leftEdge) { 1335 toCapture = findDrawerWithGravity(Gravity.LEFT); 1336 childLeft = (toCapture != null ? -toCapture.getWidth() : 0) + peekDistance; 1337 } else { 1338 toCapture = findDrawerWithGravity(Gravity.RIGHT); 1339 childLeft = getWidth() - peekDistance; 1340 } 1341 // Only peek if it would mean making the drawer more visible and the drawer isn't locked 1342 if (toCapture != null && ((leftEdge && toCapture.getLeft() < childLeft) || 1343 (!leftEdge && toCapture.getLeft() > childLeft)) && 1344 getDrawerLockMode(toCapture) == LOCK_MODE_UNLOCKED) { 1345 final LayoutParams lp = (LayoutParams) toCapture.getLayoutParams(); 1346 mDragger.smoothSlideViewTo(toCapture, childLeft, toCapture.getTop()); 1347 lp.isPeeking = true; 1348 invalidate(); 1349 1350 closeOtherDrawer(); 1351 1352 cancelChildViewTouch(); 1353 } 1354 } 1355 1356 @Override 1357 public boolean onEdgeLock(int edgeFlags) { 1358 if (ALLOW_EDGE_LOCK) { 1359 final View drawer = findDrawerWithGravity(mGravity); 1360 if (drawer != null && !isDrawerOpen(drawer)) { 1361 closeDrawer(drawer); 1362 } 1363 return true; 1364 } 1365 return false; 1366 } 1367 1368 @Override 1369 public void onEdgeDragStarted(int edgeFlags, int pointerId) { 1370 final View toCapture; 1371 if ((edgeFlags & ViewDragHelper.EDGE_LEFT) == ViewDragHelper.EDGE_LEFT) { 1372 toCapture = findDrawerWithGravity(Gravity.LEFT); 1373 } else { 1374 toCapture = findDrawerWithGravity(Gravity.RIGHT); 1375 } 1376 1377 if (toCapture != null && getDrawerLockMode(toCapture) == LOCK_MODE_UNLOCKED) { 1378 mDragger.captureChildView(toCapture, pointerId); 1379 } 1380 } 1381 1382 @Override 1383 public int getViewHorizontalDragRange(View child) { 1384 return child.getWidth(); 1385 } 1386 1387 @Override 1388 public int clampViewPositionHorizontal(View child, int left, int dx) { 1389 if (checkDrawerViewGravity(child, Gravity.LEFT)) { 1390 return Math.max(-child.getWidth(), Math.min(left, 0)); 1391 } else { 1392 final int width = getWidth(); 1393 return Math.max(width - child.getWidth(), Math.min(left, width)); 1394 } 1395 } 1396 1397 @Override 1398 public int clampViewPositionVertical(View child, int top, int dy) { 1399 return child.getTop(); 1400 } 1401 } 1402 1403 public static class LayoutParams extends ViewGroup.MarginLayoutParams { 1404 1405 public int gravity = Gravity.NO_GRAVITY; 1406 float onScreen; 1407 boolean isPeeking; 1408 boolean knownOpen; 1409 1410 public LayoutParams(Context c, AttributeSet attrs) { 1411 super(c, attrs); 1412 1413 final TypedArray a = c.obtainStyledAttributes(attrs, LAYOUT_ATTRS); 1414 this.gravity = a.getInt(0, Gravity.NO_GRAVITY); 1415 a.recycle(); 1416 } 1417 1418 public LayoutParams(int width, int height) { 1419 super(width, height); 1420 } 1421 1422 public LayoutParams(int width, int height, int gravity) { 1423 this(width, height); 1424 this.gravity = gravity; 1425 } 1426 1427 public LayoutParams(LayoutParams source) { 1428 super(source); 1429 this.gravity = source.gravity; 1430 } 1431 1432 public LayoutParams(ViewGroup.LayoutParams source) { 1433 super(source); 1434 } 1435 1436 public LayoutParams(ViewGroup.MarginLayoutParams source) { 1437 super(source); 1438 } 1439 } 1440 1441 class AccessibilityDelegate extends AccessibilityDelegateCompat { 1442 private final Rect mTmpRect = new Rect(); 1443 1444 @Override 1445 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) { 1446 final AccessibilityNodeInfoCompat superNode = AccessibilityNodeInfoCompat.obtain(info); 1447 super.onInitializeAccessibilityNodeInfo(host, superNode); 1448 1449 info.setSource(host); 1450 final ViewParent parent = ViewCompat.getParentForAccessibility(host); 1451 if (parent instanceof View) { 1452 info.setParent((View) parent); 1453 } 1454 copyNodeInfoNoChildren(info, superNode); 1455 1456 superNode.recycle(); 1457 1458 final int childCount = getChildCount(); 1459 for (int i = 0; i < childCount; i++) { 1460 final View child = getChildAt(i); 1461 if (!filter(child)) { 1462 info.addChild(child); 1463 } 1464 } 1465 } 1466 1467 @Override 1468 public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child, 1469 AccessibilityEvent event) { 1470 if (!filter(child)) { 1471 return super.onRequestSendAccessibilityEvent(host, child, event); 1472 } 1473 return false; 1474 } 1475 1476 public boolean filter(View child) { 1477 final View openDrawer = findOpenDrawer(); 1478 return openDrawer != null && openDrawer != child; 1479 } 1480 1481 /** 1482 * This should really be in AccessibilityNodeInfoCompat, but there unfortunately 1483 * seem to be a few elements that are not easily cloneable using the underlying API. 1484 * Leave it private here as it's not general-purpose useful. 1485 */ 1486 private void copyNodeInfoNoChildren(AccessibilityNodeInfoCompat dest, 1487 AccessibilityNodeInfoCompat src) { 1488 final Rect rect = mTmpRect; 1489 1490 src.getBoundsInParent(rect); 1491 dest.setBoundsInParent(rect); 1492 1493 src.getBoundsInScreen(rect); 1494 dest.setBoundsInScreen(rect); 1495 1496 dest.setViewIdResourceName(src.getViewIdResourceName()); 1497 dest.setVisibleToUser(src.isVisibleToUser()); 1498 dest.setPackageName(src.getPackageName()); 1499 dest.setClassName(src.getClassName()); 1500 dest.setContentDescription(src.getContentDescription()); 1501 1502 dest.setEnabled(src.isEnabled()); 1503 dest.setClickable(src.isClickable()); 1504 dest.setFocusable(src.isFocusable()); 1505 dest.setFocused(src.isFocused()); 1506 dest.setAccessibilityFocused(src.isAccessibilityFocused()); 1507 dest.setSelected(src.isSelected()); 1508 dest.setLongClickable(src.isLongClickable()); 1509 1510 dest.addAction(src.getActions()); 1511 } 1512 } 1513} 1514