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