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