DrawerLayout.java revision 1b8262b87426b2f766b40d6ab4eaeac296d5c2fe
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 final int widthMode = MeasureSpec.getMode(widthMeasureSpec); 586 final int heightMode = MeasureSpec.getMode(heightMeasureSpec); 587 final int widthSize = MeasureSpec.getSize(widthMeasureSpec); 588 final int heightSize = MeasureSpec.getSize(heightMeasureSpec); 589 590 if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) { 591 throw new IllegalArgumentException( 592 "DrawerLayout must be measured with MeasureSpec.EXACTLY."); 593 } 594 595 setMeasuredDimension(widthSize, heightSize); 596 597 // Gravity value for each drawer we've seen. Only one of each permitted. 598 int foundDrawers = 0; 599 final int childCount = getChildCount(); 600 for (int i = 0; i < childCount; i++) { 601 final View child = getChildAt(i); 602 603 if (child.getVisibility() == GONE) { 604 continue; 605 } 606 607 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 608 609 if (isContentView(child)) { 610 // Content views get measured at exactly the layout's size. 611 final int contentWidthSpec = MeasureSpec.makeMeasureSpec( 612 widthSize - lp.leftMargin - lp.rightMargin, MeasureSpec.EXACTLY); 613 final int contentHeightSpec = MeasureSpec.makeMeasureSpec( 614 heightSize - lp.topMargin - lp.bottomMargin, MeasureSpec.EXACTLY); 615 child.measure(contentWidthSpec, contentHeightSpec); 616 } else if (isDrawerView(child)) { 617 final int childGravity = 618 getDrawerViewGravity(child) & Gravity.HORIZONTAL_GRAVITY_MASK; 619 if ((foundDrawers & childGravity) != 0) { 620 throw new IllegalStateException("Child drawer has absolute gravity " + 621 gravityToString(childGravity) + " but this " + TAG + " already has a " + 622 "drawer view along that edge"); 623 } 624 final int drawerWidthSpec = getChildMeasureSpec(widthMeasureSpec, 625 mMinDrawerMargin + lp.leftMargin + lp.rightMargin, 626 lp.width); 627 final int drawerHeightSpec = getChildMeasureSpec(heightMeasureSpec, 628 lp.topMargin + lp.bottomMargin, 629 lp.height); 630 child.measure(drawerWidthSpec, drawerHeightSpec); 631 } else { 632 throw new IllegalStateException("Child " + child + " at index " + i + 633 " does not have a valid layout_gravity - must be Gravity.LEFT, " + 634 "Gravity.RIGHT or Gravity.NO_GRAVITY"); 635 } 636 } 637 } 638 639 @Override 640 protected void onLayout(boolean changed, int l, int t, int r, int b) { 641 mInLayout = true; 642 final int childCount = getChildCount(); 643 for (int i = 0; i < childCount; i++) { 644 final View child = getChildAt(i); 645 646 if (child.getVisibility() == GONE) { 647 continue; 648 } 649 650 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 651 652 if (isContentView(child)) { 653 child.layout(lp.leftMargin, lp.topMargin, 654 lp.leftMargin + child.getMeasuredWidth(), 655 lp.topMargin + child.getMeasuredHeight()); 656 } else { // Drawer, if it wasn't onMeasure would have thrown an exception. 657 final int childWidth = child.getMeasuredWidth(); 658 final int childHeight = child.getMeasuredHeight(); 659 int childLeft; 660 661 if (checkDrawerViewGravity(child, Gravity.LEFT)) { 662 childLeft = -childWidth + (int) (childWidth * lp.onScreen); 663 } else { // Right; onMeasure checked for us. 664 childLeft = r - l - (int) (childWidth * lp.onScreen); 665 } 666 667 final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK; 668 669 switch (vgrav) { 670 default: 671 case Gravity.TOP: { 672 child.layout(childLeft, lp.topMargin, childLeft + childWidth, childHeight); 673 break; 674 } 675 676 case Gravity.BOTTOM: { 677 final int height = b - t; 678 child.layout(childLeft, 679 height - lp.bottomMargin - child.getMeasuredHeight(), 680 childLeft + childWidth, 681 height - lp.bottomMargin); 682 break; 683 } 684 685 case Gravity.CENTER_VERTICAL: { 686 final int height = b - t; 687 int childTop = (height - childHeight) / 2; 688 689 // Offset for margins. If things don't fit right because of 690 // bad measurement before, oh well. 691 if (childTop < lp.topMargin) { 692 childTop = lp.topMargin; 693 } else if (childTop + childHeight > height - lp.bottomMargin) { 694 childTop = height - lp.bottomMargin - childHeight; 695 } 696 child.layout(childLeft, childTop, childLeft + childWidth, 697 childTop + childHeight); 698 break; 699 } 700 } 701 702 if (lp.onScreen == 0) { 703 child.setVisibility(INVISIBLE); 704 } 705 } 706 } 707 mInLayout = false; 708 mFirstLayout = false; 709 } 710 711 @Override 712 public void requestLayout() { 713 if (!mInLayout) { 714 super.requestLayout(); 715 } 716 } 717 718 @Override 719 public void computeScroll() { 720 final int childCount = getChildCount(); 721 float scrimOpacity = 0; 722 for (int i = 0; i < childCount; i++) { 723 final float onscreen = ((LayoutParams) getChildAt(i).getLayoutParams()).onScreen; 724 scrimOpacity = Math.max(scrimOpacity, onscreen); 725 } 726 mScrimOpacity = scrimOpacity; 727 728 // "|" used on purpose; both need to run. 729 if (mLeftDragger.continueSettling(true) | mRightDragger.continueSettling(true)) { 730 ViewCompat.postInvalidateOnAnimation(this); 731 } 732 } 733 734 private static boolean hasOpaqueBackground(View v) { 735 final Drawable bg = v.getBackground(); 736 if (bg != null) { 737 return bg.getOpacity() == PixelFormat.OPAQUE; 738 } 739 return false; 740 } 741 742 @Override 743 protected boolean drawChild(Canvas canvas, View child, long drawingTime) { 744 final int height = getHeight(); 745 final boolean drawingContent = isContentView(child); 746 int clipLeft = 0, clipRight = getWidth(); 747 748 final int restoreCount = canvas.save(); 749 if (drawingContent) { 750 final int childCount = getChildCount(); 751 for (int i = 0; i < childCount; i++) { 752 final View v = getChildAt(i); 753 if (v == child || v.getVisibility() != VISIBLE || 754 !hasOpaqueBackground(v) || !isDrawerView(v) || 755 v.getHeight() < height) { 756 continue; 757 } 758 759 if (checkDrawerViewGravity(v, Gravity.LEFT)) { 760 final int vright = v.getRight(); 761 if (vright > clipLeft) clipLeft = vright; 762 } else { 763 final int vleft = v.getLeft(); 764 if (vleft < clipRight) clipRight = vleft; 765 } 766 } 767 canvas.clipRect(clipLeft, 0, clipRight, getHeight()); 768 } 769 final boolean result = super.drawChild(canvas, child, drawingTime); 770 canvas.restoreToCount(restoreCount); 771 772 if (mScrimOpacity > 0 && drawingContent) { 773 final int baseAlpha = (mScrimColor & 0xff000000) >>> 24; 774 final int imag = (int) (baseAlpha * mScrimOpacity); 775 final int color = imag << 24 | (mScrimColor & 0xffffff); 776 mScrimPaint.setColor(color); 777 778 canvas.drawRect(clipLeft, 0, clipRight, getHeight(), mScrimPaint); 779 } else if (mShadowLeft != null && checkDrawerViewGravity(child, Gravity.LEFT)) { 780 final int shadowWidth = mShadowLeft.getIntrinsicWidth(); 781 final int childRight = child.getRight(); 782 final int drawerPeekDistance = mLeftDragger.getEdgeSize(); 783 final float alpha = 784 Math.max(0, Math.min((float) childRight / drawerPeekDistance, 1.f)); 785 mShadowLeft.setBounds(childRight, child.getTop(), 786 childRight + shadowWidth, child.getBottom()); 787 mShadowLeft.setAlpha((int) (0xff * alpha)); 788 mShadowLeft.draw(canvas); 789 } else if (mShadowRight != null && checkDrawerViewGravity(child, Gravity.RIGHT)) { 790 final int shadowWidth = mShadowRight.getIntrinsicWidth(); 791 final int childLeft = child.getLeft(); 792 final int showing = getWidth() - childLeft; 793 final int drawerPeekDistance = mRightDragger.getEdgeSize(); 794 final float alpha = 795 Math.max(0, Math.min((float) showing / drawerPeekDistance, 1.f)); 796 mShadowRight.setBounds(childLeft - shadowWidth, child.getTop(), 797 childLeft, child.getBottom()); 798 mShadowRight.setAlpha((int) (0xff * alpha)); 799 mShadowRight.draw(canvas); 800 } 801 return result; 802 } 803 804 boolean isContentView(View child) { 805 return ((LayoutParams) child.getLayoutParams()).gravity == Gravity.NO_GRAVITY; 806 } 807 808 boolean isDrawerView(View child) { 809 final int gravity = ((LayoutParams) child.getLayoutParams()).gravity; 810 final int absGravity = GravityCompat.getAbsoluteGravity(gravity, 811 ViewCompat.getLayoutDirection(child)); 812 return (absGravity & (Gravity.LEFT | Gravity.RIGHT)) != 0; 813 } 814 815 @Override 816 public boolean onInterceptTouchEvent(MotionEvent ev) { 817 final int action = MotionEventCompat.getActionMasked(ev); 818 819 // "|" used deliberately here; both methods should be invoked. 820 final boolean interceptForDrag = mLeftDragger.shouldInterceptTouchEvent(ev) | 821 mRightDragger.shouldInterceptTouchEvent(ev); 822 823 boolean interceptForTap = false; 824 825 switch (action) { 826 case MotionEvent.ACTION_DOWN: { 827 final float x = ev.getX(); 828 final float y = ev.getY(); 829 mInitialMotionX = x; 830 mInitialMotionY = y; 831 if (mScrimOpacity > 0 && 832 isContentView(mLeftDragger.findTopChildUnder((int) x, (int) y))) { 833 interceptForTap = true; 834 } 835 mDisallowInterceptRequested = false; 836 mChildrenCanceledTouch = false; 837 break; 838 } 839 840 case MotionEvent.ACTION_MOVE: { 841 // If we cross the touch slop, don't perform the delayed peek for an edge touch. 842 if (mLeftDragger.checkTouchSlop(ViewDragHelper.DIRECTION_ALL)) { 843 mLeftCallback.removeCallbacks(); 844 mRightCallback.removeCallbacks(); 845 } 846 break; 847 } 848 849 case MotionEvent.ACTION_CANCEL: 850 case MotionEvent.ACTION_UP: { 851 closeDrawers(true); 852 mDisallowInterceptRequested = false; 853 mChildrenCanceledTouch = false; 854 } 855 } 856 857 return interceptForDrag || interceptForTap || hasPeekingDrawer() || mChildrenCanceledTouch; 858 } 859 860 @Override 861 public boolean onTouchEvent(MotionEvent ev) { 862 mLeftDragger.processTouchEvent(ev); 863 mRightDragger.processTouchEvent(ev); 864 865 final int action = ev.getAction(); 866 boolean wantTouchEvents = true; 867 868 switch (action & MotionEventCompat.ACTION_MASK) { 869 case MotionEvent.ACTION_DOWN: { 870 final float x = ev.getX(); 871 final float y = ev.getY(); 872 mInitialMotionX = x; 873 mInitialMotionY = y; 874 mDisallowInterceptRequested = false; 875 mChildrenCanceledTouch = false; 876 break; 877 } 878 879 case MotionEvent.ACTION_UP: { 880 final float x = ev.getX(); 881 final float y = ev.getY(); 882 boolean peekingOnly = true; 883 final View touchedView = mLeftDragger.findTopChildUnder((int) x, (int) y); 884 if (touchedView != null && isContentView(touchedView)) { 885 final float dx = x - mInitialMotionX; 886 final float dy = y - mInitialMotionY; 887 final int slop = mLeftDragger.getTouchSlop(); 888 if (dx * dx + dy * dy < slop * slop) { 889 // Taps close a dimmed open drawer but only if it isn't locked open. 890 final View openDrawer = findOpenDrawer(); 891 if (openDrawer != null) { 892 peekingOnly = getDrawerLockMode(openDrawer) == LOCK_MODE_LOCKED_OPEN; 893 } 894 } 895 } 896 closeDrawers(peekingOnly); 897 mDisallowInterceptRequested = false; 898 break; 899 } 900 901 case MotionEvent.ACTION_CANCEL: { 902 closeDrawers(true); 903 mDisallowInterceptRequested = false; 904 mChildrenCanceledTouch = false; 905 break; 906 } 907 } 908 909 return wantTouchEvents; 910 } 911 912 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { 913 if (!mLeftDragger.isEdgeTouched(ViewDragHelper.EDGE_LEFT) && 914 !mRightDragger.isEdgeTouched(ViewDragHelper.EDGE_RIGHT)) { 915 // If we have an edge touch we want to skip this and track it for later instead. 916 super.requestDisallowInterceptTouchEvent(disallowIntercept); 917 } 918 mDisallowInterceptRequested = disallowIntercept; 919 if (disallowIntercept) { 920 closeDrawers(true); 921 } 922 } 923 924 /** 925 * Close all currently open drawer views by animating them out of view. 926 */ 927 public void closeDrawers() { 928 closeDrawers(false); 929 } 930 931 void closeDrawers(boolean peekingOnly) { 932 boolean needsInvalidate = false; 933 final int childCount = getChildCount(); 934 for (int i = 0; i < childCount; i++) { 935 final View child = getChildAt(i); 936 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 937 938 if (!isDrawerView(child) || (peekingOnly && !lp.isPeeking)) { 939 continue; 940 } 941 942 final int childWidth = child.getWidth(); 943 944 if (checkDrawerViewGravity(child, Gravity.LEFT)) { 945 needsInvalidate |= mLeftDragger.smoothSlideViewTo(child, 946 -childWidth, child.getTop()); 947 } else { 948 needsInvalidate |= mRightDragger.smoothSlideViewTo(child, 949 getWidth(), child.getTop()); 950 } 951 952 lp.isPeeking = false; 953 } 954 955 mLeftCallback.removeCallbacks(); 956 mRightCallback.removeCallbacks(); 957 958 if (needsInvalidate) { 959 invalidate(); 960 } 961 } 962 963 /** 964 * Open the specified drawer view by animating it into view. 965 * 966 * @param drawerView Drawer view to open 967 */ 968 public void openDrawer(View drawerView) { 969 if (!isDrawerView(drawerView)) { 970 throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer"); 971 } 972 973 if (mFirstLayout) { 974 final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); 975 lp.onScreen = 1.f; 976 lp.knownOpen = true; 977 } else { 978 if (checkDrawerViewGravity(drawerView, Gravity.LEFT)) { 979 mLeftDragger.smoothSlideViewTo(drawerView, 0, drawerView.getTop()); 980 } else { 981 mRightDragger.smoothSlideViewTo(drawerView, getWidth() - drawerView.getWidth(), 982 drawerView.getTop()); 983 } 984 } 985 invalidate(); 986 } 987 988 /** 989 * Open the specified drawer by animating it out of view. 990 * 991 * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right. 992 * GravityCompat.START or GravityCompat.END may also be used. 993 */ 994 public void openDrawer(int gravity) { 995 final int absGravity = GravityCompat.getAbsoluteGravity(gravity, 996 ViewCompat.getLayoutDirection(this)); 997 final View drawerView = findDrawerWithGravity(absGravity); 998 999 if (drawerView == null) { 1000 throw new IllegalArgumentException("No drawer view found with absolute gravity " + 1001 gravityToString(absGravity)); 1002 } 1003 openDrawer(drawerView); 1004 } 1005 1006 /** 1007 * Close the specified drawer view by animating it into view. 1008 * 1009 * @param drawerView Drawer view to close 1010 */ 1011 public void closeDrawer(View drawerView) { 1012 if (!isDrawerView(drawerView)) { 1013 throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer"); 1014 } 1015 1016 if (mFirstLayout) { 1017 final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); 1018 lp.onScreen = 0.f; 1019 lp.knownOpen = false; 1020 } else { 1021 if (checkDrawerViewGravity(drawerView, Gravity.LEFT)) { 1022 mLeftDragger.smoothSlideViewTo(drawerView, -drawerView.getWidth(), 1023 drawerView.getTop()); 1024 } else { 1025 mRightDragger.smoothSlideViewTo(drawerView, getWidth(), drawerView.getTop()); 1026 } 1027 } 1028 invalidate(); 1029 } 1030 1031 /** 1032 * Close the specified drawer by animating it out of view. 1033 * 1034 * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right. 1035 * GravityCompat.START or GravityCompat.END may also be used. 1036 */ 1037 public void closeDrawer(int gravity) { 1038 final int absGravity = GravityCompat.getAbsoluteGravity(gravity, 1039 ViewCompat.getLayoutDirection(this)); 1040 final View drawerView = findDrawerWithGravity(absGravity); 1041 1042 if (drawerView == null) { 1043 throw new IllegalArgumentException("No drawer view found with absolute gravity " + 1044 gravityToString(absGravity)); 1045 } 1046 closeDrawer(drawerView); 1047 } 1048 1049 /** 1050 * Check if the given drawer view is currently in an open state. 1051 * To be considered "open" the drawer must have settled into its fully 1052 * visible state. To check for partial visibility use 1053 * {@link #isDrawerVisible(android.view.View)}. 1054 * 1055 * @param drawer Drawer view to check 1056 * @return true if the given drawer view is in an open state 1057 * @see #isDrawerVisible(android.view.View) 1058 */ 1059 public boolean isDrawerOpen(View drawer) { 1060 if (!isDrawerView(drawer)) { 1061 throw new IllegalArgumentException("View " + drawer + " is not a drawer"); 1062 } 1063 return ((LayoutParams) drawer.getLayoutParams()).knownOpen; 1064 } 1065 1066 /** 1067 * Check if the given drawer view is currently in an open state. 1068 * To be considered "open" the drawer must have settled into its fully 1069 * visible state. If there is no drawer with the given gravity this method 1070 * will return false. 1071 * 1072 * @param drawerGravity Gravity of the drawer to check 1073 * @return true if the given drawer view is in an open state 1074 */ 1075 public boolean isDrawerOpen(int drawerGravity) { 1076 final View drawerView = findDrawerWithGravity(drawerGravity); 1077 if (drawerView != null) { 1078 return isDrawerOpen(drawerView); 1079 } 1080 return false; 1081 } 1082 1083 /** 1084 * Check if a given drawer view is currently visible on-screen. The drawer 1085 * may be only peeking onto the screen, fully extended, or anywhere inbetween. 1086 * 1087 * @param drawer Drawer view to check 1088 * @return true if the given drawer is visible on-screen 1089 * @see #isDrawerOpen(android.view.View) 1090 */ 1091 public boolean isDrawerVisible(View drawer) { 1092 if (!isDrawerView(drawer)) { 1093 throw new IllegalArgumentException("View " + drawer + " is not a drawer"); 1094 } 1095 return ((LayoutParams) drawer.getLayoutParams()).onScreen > 0; 1096 } 1097 1098 /** 1099 * Check if a given drawer view is currently visible on-screen. The drawer 1100 * may be only peeking onto the screen, fully extended, or anywhere inbetween. 1101 * If there is no drawer with the given gravity this method will return false. 1102 * 1103 * @param drawerGravity Gravity of the drawer to check 1104 * @return true if the given drawer is visible on-screen 1105 */ 1106 public boolean isDrawerVisible(int drawerGravity) { 1107 final View drawerView = findDrawerWithGravity(drawerGravity); 1108 if (drawerView != null) { 1109 return isDrawerVisible(drawerView); 1110 } 1111 return false; 1112 } 1113 1114 private boolean hasPeekingDrawer() { 1115 final int childCount = getChildCount(); 1116 for (int i = 0; i < childCount; i++) { 1117 final LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams(); 1118 if (lp.isPeeking) { 1119 return true; 1120 } 1121 } 1122 return false; 1123 } 1124 1125 @Override 1126 protected ViewGroup.LayoutParams generateDefaultLayoutParams() { 1127 return new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT); 1128 } 1129 1130 @Override 1131 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 1132 return p instanceof LayoutParams 1133 ? new LayoutParams((LayoutParams) p) 1134 : p instanceof ViewGroup.MarginLayoutParams 1135 ? new LayoutParams((MarginLayoutParams) p) 1136 : new LayoutParams(p); 1137 } 1138 1139 @Override 1140 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 1141 return p instanceof LayoutParams && super.checkLayoutParams(p); 1142 } 1143 1144 @Override 1145 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { 1146 return new LayoutParams(getContext(), attrs); 1147 } 1148 1149 private boolean hasVisibleDrawer() { 1150 return findVisibleDrawer() != null; 1151 } 1152 1153 private View findVisibleDrawer() { 1154 final int childCount = getChildCount(); 1155 for (int i = 0; i < childCount; i++) { 1156 final View child = getChildAt(i); 1157 if (isDrawerView(child) && isDrawerVisible(child)) { 1158 return child; 1159 } 1160 } 1161 return null; 1162 } 1163 1164 void cancelChildViewTouch() { 1165 // Cancel child touches 1166 if (!mChildrenCanceledTouch) { 1167 final long now = SystemClock.uptimeMillis(); 1168 final MotionEvent cancelEvent = MotionEvent.obtain(now, now, 1169 MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0); 1170 final int childCount = getChildCount(); 1171 for (int i = 0; i < childCount; i++) { 1172 getChildAt(i).dispatchTouchEvent(cancelEvent); 1173 } 1174 cancelEvent.recycle(); 1175 mChildrenCanceledTouch = true; 1176 } 1177 } 1178 1179 @Override 1180 public boolean onKeyDown(int keyCode, KeyEvent event) { 1181 if (keyCode == KeyEvent.KEYCODE_BACK && hasVisibleDrawer()) { 1182 KeyEventCompat.startTracking(event); 1183 return true; 1184 } 1185 return super.onKeyDown(keyCode, event); 1186 } 1187 1188 @Override 1189 public boolean onKeyUp(int keyCode, KeyEvent event) { 1190 if (keyCode == KeyEvent.KEYCODE_BACK) { 1191 final View visibleDrawer = findVisibleDrawer(); 1192 if (visibleDrawer != null && getDrawerLockMode(visibleDrawer) == LOCK_MODE_UNLOCKED) { 1193 closeDrawers(); 1194 } 1195 return visibleDrawer != null; 1196 } 1197 return super.onKeyUp(keyCode, event); 1198 } 1199 1200 @Override 1201 protected void onRestoreInstanceState(Parcelable state) { 1202 final SavedState ss = (SavedState) state; 1203 super.onRestoreInstanceState(ss.getSuperState()); 1204 1205 if (ss.openDrawerGravity != Gravity.NO_GRAVITY) { 1206 final View toOpen = findDrawerWithGravity(ss.openDrawerGravity); 1207 if (toOpen != null) { 1208 openDrawer(toOpen); 1209 } 1210 } 1211 1212 setDrawerLockMode(ss.lockModeLeft, Gravity.LEFT); 1213 setDrawerLockMode(ss.lockModeRight, Gravity.RIGHT); 1214 } 1215 1216 @Override 1217 protected Parcelable onSaveInstanceState() { 1218 final Parcelable superState = super.onSaveInstanceState(); 1219 1220 final SavedState ss = new SavedState(superState); 1221 1222 final int childCount = getChildCount(); 1223 for (int i = 0; i < childCount; i++) { 1224 final View child = getChildAt(i); 1225 if (!isDrawerView(child)) { 1226 continue; 1227 } 1228 1229 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 1230 if (lp.knownOpen) { 1231 ss.openDrawerGravity = lp.gravity; 1232 // Only one drawer can be open at a time. 1233 break; 1234 } 1235 } 1236 1237 ss.lockModeLeft = mLockModeLeft; 1238 ss.lockModeRight = mLockModeRight; 1239 1240 return ss; 1241 } 1242 1243 /** 1244 * State persisted across instances 1245 */ 1246 protected static class SavedState extends BaseSavedState { 1247 int openDrawerGravity = Gravity.NO_GRAVITY; 1248 int lockModeLeft = LOCK_MODE_UNLOCKED; 1249 int lockModeRight = LOCK_MODE_UNLOCKED; 1250 1251 public SavedState(Parcel in) { 1252 super(in); 1253 openDrawerGravity = in.readInt(); 1254 } 1255 1256 public SavedState(Parcelable superState) { 1257 super(superState); 1258 } 1259 1260 @Override 1261 public void writeToParcel(Parcel dest, int flags) { 1262 super.writeToParcel(dest, flags); 1263 dest.writeInt(openDrawerGravity); 1264 } 1265 1266 public static final Parcelable.Creator<SavedState> CREATOR = 1267 new Parcelable.Creator<SavedState>() { 1268 @Override 1269 public SavedState createFromParcel(Parcel source) { 1270 return new SavedState(source); 1271 } 1272 1273 @Override 1274 public SavedState[] newArray(int size) { 1275 return new SavedState[size]; 1276 } 1277 }; 1278 } 1279 1280 private class ViewDragCallback extends ViewDragHelper.Callback { 1281 private final int mGravity; 1282 private ViewDragHelper mDragger; 1283 1284 private final Runnable mPeekRunnable = new Runnable() { 1285 @Override public void run() { 1286 peekDrawer(); 1287 } 1288 }; 1289 1290 public ViewDragCallback(int gravity) { 1291 mGravity = gravity; 1292 } 1293 1294 public void setDragger(ViewDragHelper dragger) { 1295 mDragger = dragger; 1296 } 1297 1298 public void removeCallbacks() { 1299 DrawerLayout.this.removeCallbacks(mPeekRunnable); 1300 } 1301 1302 @Override 1303 public boolean tryCaptureView(View child, int pointerId) { 1304 // Only capture views where the gravity matches what we're looking for. 1305 // This lets us use two ViewDragHelpers, one for each side drawer. 1306 return isDrawerView(child) && checkDrawerViewGravity(child, mGravity) && 1307 getDrawerLockMode(child) == LOCK_MODE_UNLOCKED; 1308 } 1309 1310 @Override 1311 public void onViewDragStateChanged(int state) { 1312 updateDrawerState(mGravity, state, mDragger.getCapturedView()); 1313 } 1314 1315 @Override 1316 public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { 1317 float offset; 1318 final int childWidth = changedView.getWidth(); 1319 1320 // This reverses the positioning shown in onLayout. 1321 if (checkDrawerViewGravity(changedView, Gravity.LEFT)) { 1322 offset = (float) (childWidth + left) / childWidth; 1323 } else { 1324 final int width = getWidth(); 1325 offset = (float) (width - left) / childWidth; 1326 } 1327 setDrawerViewOffset(changedView, offset); 1328 changedView.setVisibility(offset == 0 ? INVISIBLE : VISIBLE); 1329 invalidate(); 1330 } 1331 1332 @Override 1333 public void onViewCaptured(View capturedChild, int activePointerId) { 1334 final LayoutParams lp = (LayoutParams) capturedChild.getLayoutParams(); 1335 lp.isPeeking = false; 1336 1337 closeOtherDrawer(); 1338 } 1339 1340 private void closeOtherDrawer() { 1341 final int otherGrav = mGravity == Gravity.LEFT ? Gravity.RIGHT : Gravity.LEFT; 1342 final View toClose = findDrawerWithGravity(otherGrav); 1343 if (toClose != null) { 1344 closeDrawer(toClose); 1345 } 1346 } 1347 1348 @Override 1349 public void onViewReleased(View releasedChild, float xvel, float yvel) { 1350 // Offset is how open the drawer is, therefore left/right values 1351 // are reversed from one another. 1352 final float offset = getDrawerViewOffset(releasedChild); 1353 final int childWidth = releasedChild.getWidth(); 1354 1355 int left; 1356 if (checkDrawerViewGravity(releasedChild, Gravity.LEFT)) { 1357 left = xvel > 0 || xvel == 0 && offset > 0.5f ? 0 : -childWidth; 1358 } else { 1359 final int width = getWidth(); 1360 left = xvel < 0 || xvel == 0 && offset < 0.5f ? width - childWidth : width; 1361 } 1362 1363 mDragger.settleCapturedViewAt(left, releasedChild.getTop()); 1364 invalidate(); 1365 } 1366 1367 @Override 1368 public void onEdgeTouched(int edgeFlags, int pointerId) { 1369 postDelayed(mPeekRunnable, PEEK_DELAY); 1370 } 1371 1372 private void peekDrawer() { 1373 final View toCapture; 1374 final int childLeft; 1375 final int peekDistance = mDragger.getEdgeSize(); 1376 final boolean leftEdge = mGravity == Gravity.LEFT; 1377 if (leftEdge) { 1378 toCapture = findDrawerWithGravity(Gravity.LEFT); 1379 childLeft = (toCapture != null ? -toCapture.getWidth() : 0) + peekDistance; 1380 } else { 1381 toCapture = findDrawerWithGravity(Gravity.RIGHT); 1382 childLeft = getWidth() - peekDistance; 1383 } 1384 // Only peek if it would mean making the drawer more visible and the drawer isn't locked 1385 if (toCapture != null && ((leftEdge && toCapture.getLeft() < childLeft) || 1386 (!leftEdge && toCapture.getLeft() > childLeft)) && 1387 getDrawerLockMode(toCapture) == LOCK_MODE_UNLOCKED) { 1388 final LayoutParams lp = (LayoutParams) toCapture.getLayoutParams(); 1389 mDragger.smoothSlideViewTo(toCapture, childLeft, toCapture.getTop()); 1390 lp.isPeeking = true; 1391 invalidate(); 1392 1393 closeOtherDrawer(); 1394 1395 cancelChildViewTouch(); 1396 } 1397 } 1398 1399 @Override 1400 public boolean onEdgeLock(int edgeFlags) { 1401 if (ALLOW_EDGE_LOCK) { 1402 final View drawer = findDrawerWithGravity(mGravity); 1403 if (drawer != null && !isDrawerOpen(drawer)) { 1404 closeDrawer(drawer); 1405 } 1406 return true; 1407 } 1408 return false; 1409 } 1410 1411 @Override 1412 public void onEdgeDragStarted(int edgeFlags, int pointerId) { 1413 final View toCapture; 1414 if ((edgeFlags & ViewDragHelper.EDGE_LEFT) == ViewDragHelper.EDGE_LEFT) { 1415 toCapture = findDrawerWithGravity(Gravity.LEFT); 1416 } else { 1417 toCapture = findDrawerWithGravity(Gravity.RIGHT); 1418 } 1419 1420 if (toCapture != null && getDrawerLockMode(toCapture) == LOCK_MODE_UNLOCKED) { 1421 mDragger.captureChildView(toCapture, pointerId); 1422 } 1423 } 1424 1425 @Override 1426 public int getViewHorizontalDragRange(View child) { 1427 return child.getWidth(); 1428 } 1429 1430 @Override 1431 public int clampViewPositionHorizontal(View child, int left, int dx) { 1432 if (checkDrawerViewGravity(child, Gravity.LEFT)) { 1433 return Math.max(-child.getWidth(), Math.min(left, 0)); 1434 } else { 1435 final int width = getWidth(); 1436 return Math.max(width - child.getWidth(), Math.min(left, width)); 1437 } 1438 } 1439 1440 @Override 1441 public int clampViewPositionVertical(View child, int top, int dy) { 1442 return child.getTop(); 1443 } 1444 } 1445 1446 public static class LayoutParams extends ViewGroup.MarginLayoutParams { 1447 1448 public int gravity = Gravity.NO_GRAVITY; 1449 float onScreen; 1450 boolean isPeeking; 1451 boolean knownOpen; 1452 1453 public LayoutParams(Context c, AttributeSet attrs) { 1454 super(c, attrs); 1455 1456 final TypedArray a = c.obtainStyledAttributes(attrs, LAYOUT_ATTRS); 1457 this.gravity = a.getInt(0, Gravity.NO_GRAVITY); 1458 a.recycle(); 1459 } 1460 1461 public LayoutParams(int width, int height) { 1462 super(width, height); 1463 } 1464 1465 public LayoutParams(int width, int height, int gravity) { 1466 this(width, height); 1467 this.gravity = gravity; 1468 } 1469 1470 public LayoutParams(LayoutParams source) { 1471 super(source); 1472 this.gravity = source.gravity; 1473 } 1474 1475 public LayoutParams(ViewGroup.LayoutParams source) { 1476 super(source); 1477 } 1478 1479 public LayoutParams(ViewGroup.MarginLayoutParams source) { 1480 super(source); 1481 } 1482 } 1483 1484 class AccessibilityDelegate extends AccessibilityDelegateCompat { 1485 private final Rect mTmpRect = new Rect(); 1486 1487 @Override 1488 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) { 1489 final AccessibilityNodeInfoCompat superNode = AccessibilityNodeInfoCompat.obtain(info); 1490 super.onInitializeAccessibilityNodeInfo(host, superNode); 1491 1492 info.setSource(host); 1493 final ViewParent parent = ViewCompat.getParentForAccessibility(host); 1494 if (parent instanceof View) { 1495 info.setParent((View) parent); 1496 } 1497 copyNodeInfoNoChildren(info, superNode); 1498 1499 superNode.recycle(); 1500 1501 final int childCount = getChildCount(); 1502 for (int i = 0; i < childCount; i++) { 1503 final View child = getChildAt(i); 1504 if (!filter(child)) { 1505 info.addChild(child); 1506 } 1507 } 1508 } 1509 1510 @Override 1511 public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child, 1512 AccessibilityEvent event) { 1513 if (!filter(child)) { 1514 return super.onRequestSendAccessibilityEvent(host, child, event); 1515 } 1516 return false; 1517 } 1518 1519 public boolean filter(View child) { 1520 final View openDrawer = findOpenDrawer(); 1521 return openDrawer != null && openDrawer != child; 1522 } 1523 1524 /** 1525 * This should really be in AccessibilityNodeInfoCompat, but there unfortunately 1526 * seem to be a few elements that are not easily cloneable using the underlying API. 1527 * Leave it private here as it's not general-purpose useful. 1528 */ 1529 private void copyNodeInfoNoChildren(AccessibilityNodeInfoCompat dest, 1530 AccessibilityNodeInfoCompat src) { 1531 final Rect rect = mTmpRect; 1532 1533 src.getBoundsInParent(rect); 1534 dest.setBoundsInParent(rect); 1535 1536 src.getBoundsInScreen(rect); 1537 dest.setBoundsInScreen(rect); 1538 1539 dest.setVisibleToUser(src.isVisibleToUser()); 1540 dest.setPackageName(src.getPackageName()); 1541 dest.setClassName(src.getClassName()); 1542 dest.setContentDescription(src.getContentDescription()); 1543 1544 dest.setEnabled(src.isEnabled()); 1545 dest.setClickable(src.isClickable()); 1546 dest.setFocusable(src.isFocusable()); 1547 dest.setFocused(src.isFocused()); 1548 dest.setAccessibilityFocused(src.isAccessibilityFocused()); 1549 dest.setSelected(src.isSelected()); 1550 dest.setLongClickable(src.isLongClickable()); 1551 1552 dest.addAction(src.getActions()); 1553 } 1554 } 1555} 1556