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