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