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