DrawerLayout.java revision 1d26501f0c8e9f3577f651938a03f6b3a1a672c7
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.support.v4.view.GravityCompat; 25import android.support.v4.view.MotionEventCompat; 26import android.support.v4.view.ViewCompat; 27import android.util.AttributeSet; 28import android.util.Log; 29import android.view.Gravity; 30import android.view.MotionEvent; 31import android.view.View; 32import android.view.ViewGroup; 33 34/** 35 * DrawerLayout acts as a top-level container for window content that allows for 36 * interactive "drawer" views to be pulled out from the edge of the window. 37 * 38 * <p>Drawer positioning and layout is controlled using the <code>android:layout_gravity</code> 39 * attribute on child views corresponding to </p> 40 * 41 * <p>As per the Android Design guide, any drawers positioned to the left/start should 42 * always contain content for navigating around the application, whereas any drawers 43 * positioned to the right/end should always contain actions to take on the current content. 44 * This preserves the same navigation left, actions right structure present in the Action Bar 45 * and elsewhere.</p> 46 */ 47public class DrawerLayout extends ViewGroup { 48 private static final String TAG = "DrawerLayout"; 49 50 private static final int INVALID_POINTER = -1; 51 52 /** 53 * Indicates that any drawers are in an idle, settled state. No animation is in progress. 54 */ 55 public static final int STATE_IDLE = ViewDragHelper.STATE_IDLE; 56 57 /** 58 * Indicates that a drawer is currently being dragged by the user. 59 */ 60 public static final int STATE_DRAGGING = ViewDragHelper.STATE_DRAGGING; 61 62 /** 63 * Indicates that a drawer is in the process of settling to a final position. 64 */ 65 public static final int STATE_SETTLING = ViewDragHelper.STATE_SETTLING; 66 67 private static final int MIN_DRAWER_MARGIN = 64; // dp 68 69 private static final int DRAWER_PEEK_DISTANCE = 16; // dp 70 71 private static final int DEFAULT_SCRIM_COLOR = 0x99000000; 72 73 private static final int[] LAYOUT_ATTRS = new int[] { 74 android.R.attr.layout_gravity 75 }; 76 77 private int mMinDrawerMargin; 78 private int mDrawerPeekDistance; 79 80 private int mScrimColor = DEFAULT_SCRIM_COLOR; 81 private float mScrimOpacity; 82 private Paint mScrimPaint = new Paint(); 83 84 private final ViewDragHelper mLeftDragger; 85 private final ViewDragHelper mRightDragger; 86 private int mDrawerState; 87 88 private DrawerListener mListener; 89 90 private float mInitialMotionX; 91 private float mInitialMotionY; 92 93 /** 94 * Listener for monitoring events about drawers. 95 */ 96 public interface DrawerListener { 97 /** 98 * Called when a drawer's position changes. 99 * @param drawerView The child view that was moved 100 * @param slideOffset The new offset of this drawer within its range, from 0-1 101 */ 102 public void onDrawerSlide(View drawerView, float slideOffset); 103 104 /** 105 * Called when a drawer has settled in a completely open state. 106 * The drawer is interactive at this point. 107 * 108 * @param drawerView Drawer view that is now open 109 */ 110 public void onDrawerOpened(View drawerView); 111 112 /** 113 * Called when a drawer has settled in a completely closed state. 114 * 115 * @param drawerView Drawer view that is now closed 116 */ 117 public void onDrawerClosed(View drawerView); 118 119 /** 120 * Called when the drawer motion state changes. The new state will 121 * be one of {@link #STATE_IDLE}, {@link #STATE_DRAGGING} or {@link #STATE_SETTLING}. 122 * 123 * @param newState The new drawer motion state 124 */ 125 public void onDrawerStateChanged(int newState); 126 } 127 128 public DrawerLayout(Context context) { 129 this(context, null); 130 } 131 132 public DrawerLayout(Context context, AttributeSet attrs) { 133 this(context, attrs, 0); 134 } 135 136 public DrawerLayout(Context context, AttributeSet attrs, int defStyle) { 137 super(context, attrs, defStyle); 138 139 final float density = getResources().getDisplayMetrics().density; 140 mMinDrawerMargin = (int) (MIN_DRAWER_MARGIN * density + 0.5f); 141 mDrawerPeekDistance = (int) (DRAWER_PEEK_DISTANCE * density + 0.5f); 142 143 final ViewDragCallback leftCallback = new ViewDragCallback(Gravity.LEFT); 144 final ViewDragCallback rightCallback = new ViewDragCallback(Gravity.RIGHT); 145 146 mLeftDragger = ViewDragHelper.create(this, 0.5f, leftCallback); 147 mLeftDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT); 148 leftCallback.setDragger(mLeftDragger); 149 150 mRightDragger = ViewDragHelper.create(this, 0.5f, rightCallback); 151 mRightDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_RIGHT); 152 rightCallback.setDragger(mRightDragger); 153 } 154 155 /** 156 * Set a listener to be notified of drawer events. 157 * 158 * @param listener Listener to notify when drawer events occur 159 * @see DrawerListener 160 */ 161 public void setDrawerListener(DrawerListener listener) { 162 mListener = listener; 163 } 164 165 /** 166 * Resolve the shared state of all drawers from the component ViewDragHelpers. 167 * Should be called whenever a ViewDragHelper's state changes. 168 */ 169 void updateDrawerState(int forGravity) { 170 final int leftState = mLeftDragger.getViewDragState(); 171 final int rightState = mRightDragger.getViewDragState(); 172 173 final int state; 174 if (leftState == STATE_DRAGGING || rightState == STATE_DRAGGING) { 175 state = STATE_DRAGGING; 176 } else if (leftState == STATE_SETTLING || rightState == STATE_SETTLING) { 177 state = STATE_SETTLING; 178 } else { 179 state = STATE_IDLE; 180 } 181 182 if (state != mDrawerState) { 183 mDrawerState = state; 184 final View activeDrawer = findDrawerWithGravity(forGravity); 185 if (state == STATE_IDLE && activeDrawer != null) { 186 final LayoutParams lp = (LayoutParams) activeDrawer.getLayoutParams(); 187 if (lp.onscreen == 0) { 188 dispatchOnDrawerClosed(activeDrawer); 189 } else if (lp.onscreen == 1) { 190 dispatchOnDrawerOpened(activeDrawer); 191 } 192 } 193 if (mListener != null) { 194 mListener.onDrawerStateChanged(state); 195 } 196 } 197 } 198 199 void dispatchOnDrawerClosed(View drawerView) { 200 final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); 201 if (lp.knownOpen) { 202 lp.knownOpen = false; 203 if (mListener != null) { 204 mListener.onDrawerClosed(drawerView); 205 } 206 } 207 } 208 209 void dispatchOnDrawerOpened(View drawerView) { 210 final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); 211 if (!lp.knownOpen) { 212 lp.knownOpen = true; 213 if (mListener != null) { 214 mListener.onDrawerOpened(drawerView); 215 } 216 } 217 } 218 219 void dispatchOnDrawerSlide(View drawerView, float slideOffset) { 220 if (mListener != null) { 221 mListener.onDrawerSlide(drawerView, slideOffset); 222 } 223 } 224 225 void setDrawerViewOffset(View drawerView, float slideOffset) { 226 final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); 227 if (slideOffset == lp.onscreen) { 228 return; 229 } 230 231 lp.onscreen = slideOffset; 232 dispatchOnDrawerSlide(drawerView, slideOffset); 233 } 234 235 float getDrawerViewOffset(View drawerView) { 236 return ((LayoutParams) drawerView.getLayoutParams()).onscreen; 237 } 238 239 int getDrawerViewGravity(View drawerView) { 240 final int gravity = ((LayoutParams) drawerView.getLayoutParams()).gravity; 241 return GravityCompat.getAbsoluteGravity(gravity, ViewCompat.getLayoutDirection(drawerView)); 242 } 243 244 boolean checkDrawerViewGravity(View drawerView, int checkFor) { 245 final int absGrav = getDrawerViewGravity(drawerView); 246 return (absGrav & checkFor) == checkFor; 247 } 248 249 void moveDrawerToOffset(View drawerView, float slideOffset) { 250 final float oldOffset = getDrawerViewOffset(drawerView); 251 final int width = drawerView.getWidth(); 252 final int oldPos = (int) (width * oldOffset); 253 final int newPos = (int) (width * slideOffset); 254 final int dx = newPos - oldPos; 255 256 drawerView.offsetLeftAndRight(checkDrawerViewGravity(drawerView, Gravity.LEFT) ? dx : -dx); 257 setDrawerViewOffset(drawerView, slideOffset); 258 } 259 260 View findDrawerWithGravity(int gravity) { 261 final int childCount = getChildCount(); 262 for (int i = 0; i < childCount; i++) { 263 final View child = getChildAt(i); 264 final int childGravity = getDrawerViewGravity(child); 265 if ((childGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 266 (gravity & Gravity.HORIZONTAL_GRAVITY_MASK)) { 267 return child; 268 } 269 } 270 return null; 271 } 272 273 /** 274 * Simple gravity to string - only supports LEFT and RIGHT for debugging output. 275 * 276 * @param gravity Absolute gravity value 277 * @return LEFT or RIGHT as appropriate, or a hex string 278 */ 279 static String gravityToString(int gravity) { 280 if ((gravity & Gravity.LEFT) == Gravity.LEFT) { 281 return "LEFT"; 282 } 283 if ((gravity & Gravity.RIGHT) == Gravity.RIGHT) { 284 return "RIGHT"; 285 } 286 return Integer.toHexString(gravity); 287 } 288 289 @Override 290 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 291 final int widthMode = MeasureSpec.getMode(widthMeasureSpec); 292 final int heightMode = MeasureSpec.getMode(heightMeasureSpec); 293 final int widthSize = MeasureSpec.getSize(widthMeasureSpec); 294 final int heightSize = MeasureSpec.getSize(heightMeasureSpec); 295 296 if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) { 297 throw new IllegalArgumentException( 298 "DrawerLayout must be measured with MeasureSpec.EXACTLY."); 299 } 300 301 setMeasuredDimension(widthSize, heightSize); 302 303 // Gravity value for each drawer we've seen. Only one of each permitted. 304 int foundDrawers = 0; 305 final int childCount = getChildCount(); 306 for (int i = 0; i < childCount; i++) { 307 final View child = getChildAt(i); 308 309 if (child.getVisibility() == GONE) { 310 continue; 311 } 312 313 if (isContentView(child)) { 314 // Content views get measured at exactly the layout's size. 315 child.measure(widthMeasureSpec, heightMeasureSpec); 316 } else if (isDrawerView(child)) { 317 final int childGravity = 318 getDrawerViewGravity(child) & Gravity.HORIZONTAL_GRAVITY_MASK; 319 if ((foundDrawers & childGravity) != 0) { 320 throw new IllegalStateException("Child drawer has absolute gravity " + 321 gravityToString(childGravity) + " but this " + TAG + " already has a " + 322 "drawer view along that edge"); 323 } 324 final int drawerWidthSpec = getChildMeasureSpec(widthMeasureSpec, mMinDrawerMargin, 325 child.getLayoutParams().width); 326 child.measure(drawerWidthSpec, heightMeasureSpec); 327 } else { 328 throw new IllegalStateException("Child " + child + " at index " + i + 329 " does not have a valid layout_gravity - must be Gravity.LEFT, " + 330 "Gravity.RIGHT or Gravity.NO_GRAVITY"); 331 } 332 } 333 } 334 335 @Override 336 protected void onLayout(boolean changed, int l, int t, int r, int b) { 337 final int childCount = getChildCount(); 338 for (int i = 0; i < childCount; i++) { 339 final View child = getChildAt(i); 340 341 if (child.getVisibility() == GONE) { 342 continue; 343 } 344 345 if (isContentView(child)) { 346 child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight()); 347 } else { // Drawer, if it wasn't onMeasure would have thrown an exception. 348 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 349 350 final int childWidth = child.getMeasuredWidth(); 351 int childLeft; 352 353 if (checkDrawerViewGravity(child, Gravity.LEFT)) { 354 childLeft = -childWidth + (int) (childWidth * lp.onscreen); 355 } else { // Right; onMeasure checked for us. 356 childLeft = r - l - (int) (childWidth * lp.onscreen); 357 } 358 359 child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight()); 360 } 361 } 362 } 363 364 @Override 365 public void computeScroll() { 366 final int childCount = getChildCount(); 367 float scrimOpacity = 0; 368 for (int i = 0; i < childCount; i++) { 369 final float onscreen = ((LayoutParams) getChildAt(i).getLayoutParams()).onscreen; 370 scrimOpacity = Math.max(scrimOpacity, onscreen); 371 } 372 mScrimOpacity = scrimOpacity; 373 374 // "|" used on purpose; both need to run. 375 if (mLeftDragger.continueSettling(true) | mRightDragger.continueSettling(true)) { 376 ViewCompat.postInvalidateOnAnimation(this); 377 } 378 } 379 380 @Override 381 protected boolean drawChild(Canvas canvas, View child, long drawingTime) { 382 final int restoreCount = canvas.save(); 383 final boolean result = super.drawChild(canvas, child, drawingTime); 384 canvas.restoreToCount(restoreCount); 385 if (mScrimOpacity > 0 && isContentView(child)) { 386 final int baseAlpha = (mScrimColor & 0xff000000) >>> 24; 387 final int imag = (int) (baseAlpha * mScrimOpacity); 388 final int color = imag << 24 | (mScrimColor & 0xffffff); 389 mScrimPaint.setColor(color); 390 391 canvas.drawRect(0, 0, getWidth(), getHeight(), mScrimPaint); 392 } 393 return result; 394 } 395 396 boolean isContentView(View child) { 397 return ((LayoutParams) child.getLayoutParams()).gravity == Gravity.NO_GRAVITY; 398 } 399 400 boolean isDrawerView(View child) { 401 final int gravity = ((LayoutParams) child.getLayoutParams()).gravity; 402 final int absGravity = GravityCompat.getAbsoluteGravity(gravity, 403 ViewCompat.getLayoutDirection(child)); 404 return (absGravity & (Gravity.LEFT | Gravity.RIGHT)) != 0; 405 } 406 407 @Override 408 public boolean onInterceptTouchEvent(MotionEvent ev) { 409 final int action = MotionEventCompat.getActionMasked(ev); 410 411 // "|" used deliberately here; both methods should be invoked. 412 final boolean interceptForDrag = mLeftDragger.shouldInterceptTouchEvent(ev) | 413 mRightDragger.shouldInterceptTouchEvent(ev); 414 415 boolean interceptForTap = false; 416 417 switch (action) { 418 case MotionEvent.ACTION_DOWN: { 419 final float x = ev.getX(); 420 final float y = ev.getY(); 421 mInitialMotionX = x; 422 mInitialMotionY = y; 423 if (mScrimOpacity > 0 && 424 isContentView(mLeftDragger.findTopChildUnder((int) x, (int) y))) { 425 interceptForTap = true; 426 } 427 break; 428 } 429 430 case MotionEvent.ACTION_CANCEL: 431 case MotionEvent.ACTION_UP: { 432 closeDrawers(true); 433 } 434 } 435 return interceptForDrag || interceptForTap; 436 } 437 438 @Override 439 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { 440 super.requestDisallowInterceptTouchEvent(disallowIntercept); 441 if (disallowIntercept) { 442 closeDrawers(true); 443 } 444 } 445 446 @Override 447 public boolean onTouchEvent(MotionEvent ev) { 448 mLeftDragger.processTouchEvent(ev); 449 mRightDragger.processTouchEvent(ev); 450 451 final int action = ev.getAction(); 452 boolean wantTouchEvents = true; 453 454 switch (action & MotionEventCompat.ACTION_MASK) { 455 case MotionEvent.ACTION_DOWN: { 456 final float x = ev.getX(); 457 final float y = ev.getY(); 458 mInitialMotionX = x; 459 mInitialMotionY = y; 460 break; 461 } 462 463 case MotionEvent.ACTION_UP: { 464 final float x = ev.getX(); 465 final float y = ev.getY(); 466 boolean peekingOnly = true; 467 if (isContentView(mLeftDragger.findTopChildUnder((int) x, (int) y))) { 468 final float dx = x - mInitialMotionX; 469 final float dy = y - mInitialMotionY; 470 final int slop = mLeftDragger.getTouchSlop(); 471 if (dx * dx + dy * dy < slop * slop) { 472 // Taps close a dimmed open pane. 473 peekingOnly = false; 474 } 475 } 476 closeDrawers(peekingOnly); 477 break; 478 } 479 480 case MotionEvent.ACTION_CANCEL: { 481 closeDrawers(true); 482 break; 483 } 484 } 485 486 return wantTouchEvents; 487 } 488 489 /** 490 * Close all currently open drawer views by animating them out of view. 491 */ 492 public void closeDrawers() { 493 closeDrawers(false); 494 } 495 496 void closeDrawers(boolean peekingOnly) { 497 boolean needsInvalidate = false; 498 final int childCount = getChildCount(); 499 for (int i = 0; i < childCount; i++) { 500 final View child = getChildAt(i); 501 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 502 503 if (!isDrawerView(child) || (peekingOnly && !lp.isPeeking)) { 504 continue; 505 } 506 507 final int childWidth = child.getWidth(); 508 509 if (checkDrawerViewGravity(child, Gravity.LEFT)) { 510 needsInvalidate |= mLeftDragger.smoothSlideViewTo(child, 511 -childWidth, child.getTop()); 512 } else { 513 needsInvalidate |= mRightDragger.smoothSlideViewTo(child, 514 getWidth(), child.getTop()); 515 } 516 517 lp.isPeeking = false; 518 } 519 520 if (needsInvalidate) { 521 invalidate(); 522 } 523 } 524 525 /** 526 * Open the specified drawer view by animating it into view. 527 * 528 * @param drawerView Drawer view to open 529 */ 530 public void openDrawer(View drawerView) { 531 if (!isDrawerView(drawerView)) { 532 throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer"); 533 } 534 535 if (checkDrawerViewGravity(drawerView, Gravity.LEFT)) { 536 mLeftDragger.smoothSlideViewTo(drawerView, 0, drawerView.getTop()); 537 } else { 538 mRightDragger.smoothSlideViewTo(drawerView, getWidth() - drawerView.getWidth(), 539 drawerView.getTop()); 540 } 541 invalidate(); 542 } 543 544 /** 545 * Open the specified drawer by animating it out of view. 546 * 547 * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right. 548 * GravityCompat.START or GravityCompat.END may also be used. 549 */ 550 public void openDrawer(int gravity) { 551 final int absGravity = GravityCompat.getAbsoluteGravity(gravity, 552 ViewCompat.getLayoutDirection(this)); 553 final View drawerView = findDrawerWithGravity(absGravity); 554 555 if (drawerView == null) { 556 throw new IllegalArgumentException("No drawer view found with absolute gravity " + 557 gravityToString(absGravity)); 558 } 559 openDrawer(drawerView); 560 } 561 562 /** 563 * Close the specified drawer view by animating it into view. 564 * 565 * @param drawerView Drawer view to close 566 */ 567 public void closeDrawer(View drawerView) { 568 if (!isDrawerView(drawerView)) { 569 throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer"); 570 } 571 572 if (checkDrawerViewGravity(drawerView, Gravity.LEFT)) { 573 mLeftDragger.smoothSlideViewTo(drawerView, -drawerView.getWidth(), drawerView.getTop()); 574 } else { 575 mRightDragger.smoothSlideViewTo(drawerView, getWidth(), drawerView.getTop()); 576 } 577 invalidate(); 578 } 579 580 /** 581 * Close the specified drawer by animating it out of view. 582 * 583 * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right. 584 * GravityCompat.START or GravityCompat.END may also be used. 585 */ 586 public void closeDrawer(int gravity) { 587 final int absGravity = GravityCompat.getAbsoluteGravity(gravity, 588 ViewCompat.getLayoutDirection(this)); 589 final View drawerView = findDrawerWithGravity(absGravity); 590 591 if (drawerView == null) { 592 throw new IllegalArgumentException("No drawer view found with absolute gravity " + 593 gravityToString(absGravity)); 594 } 595 closeDrawer(drawerView); 596 } 597 598 @Override 599 protected ViewGroup.LayoutParams generateDefaultLayoutParams() { 600 return new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT); 601 } 602 603 @Override 604 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 605 return p instanceof LayoutParams 606 ? new LayoutParams((LayoutParams) p) 607 : new LayoutParams(p); 608 } 609 610 @Override 611 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 612 return p instanceof LayoutParams && super.checkLayoutParams(p); 613 } 614 615 @Override 616 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { 617 return new LayoutParams(getContext(), attrs); 618 } 619 620 private class ViewDragCallback extends ViewDragHelper.Callback { 621 622 private final int mGravity; 623 private ViewDragHelper mDragger; 624 625 public ViewDragCallback(int gravity) { 626 mGravity = gravity; 627 } 628 629 public void setDragger(ViewDragHelper dragger) { 630 mDragger = dragger; 631 } 632 633 @Override 634 public boolean tryCaptureView(View child, int pointerId) { 635 // Only capture views where the gravity matches what we're looking for. 636 // This lets us use two ViewDragHelpers, one for each side drawer. 637 return isDrawerView(child) && checkDrawerViewGravity(child, mGravity); 638 } 639 640 @Override 641 public void onViewDragStateChanged(int state) { 642 updateDrawerState(mGravity); 643 } 644 645 @Override 646 public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { 647 float offset; 648 final int childWidth = changedView.getWidth(); 649 650 // This reverses the positioning shown in onLayout. 651 if (checkDrawerViewGravity(changedView, Gravity.LEFT)) { 652 offset = (float) (childWidth + left) / childWidth; 653 } else { 654 final int width = getWidth(); 655 offset = (float) (width - left) / childWidth; 656 } 657 setDrawerViewOffset(changedView, offset); 658 invalidate(); 659 } 660 661 @Override 662 public void onViewCaptured(View capturedChild, int activePointerId) { 663 final LayoutParams lp = (LayoutParams) capturedChild.getLayoutParams(); 664 lp.isPeeking = false; 665 666 closeOtherDrawer(); 667 } 668 669 private void closeOtherDrawer() { 670 final int otherGrav = mGravity == Gravity.LEFT ? Gravity.RIGHT : Gravity.LEFT; 671 final View toClose = findDrawerWithGravity(otherGrav); 672 if (toClose != null) { 673 closeDrawer(toClose); 674 } 675 } 676 677 @Override 678 public void onViewReleased(View releasedChild, float xvel, float yvel) { 679 // Offset is how open the drawer is, therefore left/right values 680 // are reversed from one another. 681 final float offset = getDrawerViewOffset(releasedChild); 682 final int childWidth = releasedChild.getWidth(); 683 684 int left; 685 if (checkDrawerViewGravity(releasedChild, Gravity.LEFT)) { 686 left = xvel > 0 || xvel == 0 && offset > 0.5f ? 0 : -childWidth; 687 } else { 688 final int width = getWidth(); 689 left = xvel < 0 || xvel == 0 && offset < 0.5f ? width - childWidth : width; 690 } 691 692 mDragger.settleCapturedViewAt(left, releasedChild.getTop()); 693 invalidate(); 694 } 695 696 @Override 697 public void onEdgeTouched(int edgeFlags, int pointerId) { 698 final View toCapture; 699 final int childLeft; 700 if ((edgeFlags & ViewDragHelper.EDGE_LEFT) == ViewDragHelper.EDGE_LEFT) { 701 toCapture = findDrawerWithGravity(Gravity.LEFT); 702 childLeft = -toCapture.getWidth() + mDrawerPeekDistance; 703 } else { 704 toCapture = findDrawerWithGravity(Gravity.RIGHT); 705 childLeft = getWidth() - mDrawerPeekDistance; 706 } 707 708 if (toCapture != null) { 709 mDragger.smoothSlideViewTo(toCapture, childLeft, toCapture.getTop()); 710 ((LayoutParams) toCapture.getLayoutParams()).isPeeking = true; 711 invalidate(); 712 713 closeOtherDrawer(); 714 } 715 } 716 717 @Override 718 public void onEdgeDragStarted(int edgeFlags, int pointerId) { 719 final View toCapture; 720 if ((edgeFlags & ViewDragHelper.EDGE_LEFT) == ViewDragHelper.EDGE_LEFT) { 721 toCapture = findDrawerWithGravity(Gravity.LEFT); 722 } else { 723 toCapture = findDrawerWithGravity(Gravity.RIGHT); 724 } 725 726 if (toCapture != null) { 727 mDragger.captureChildView(toCapture, pointerId); 728 } 729 } 730 731 @Override 732 public int getViewHorizontalDragRange(View child) { 733 return child.getWidth(); 734 } 735 736 @Override 737 public int clampViewPositionHorizontal(View child, int left, int dx) { 738 if (checkDrawerViewGravity(child, Gravity.LEFT)) { 739 return Math.max(-child.getWidth(), Math.min(left, 0)); 740 } else { 741 final int width = getWidth(); 742 return Math.max(width - child.getWidth(), Math.min(left, width)); 743 } 744 } 745 } 746 747 public static class LayoutParams extends ViewGroup.LayoutParams { 748 749 public int gravity = Gravity.NO_GRAVITY; 750 float onscreen; 751 boolean isPeeking; 752 boolean knownOpen; 753 754 public LayoutParams(Context c, AttributeSet attrs) { 755 super(c, attrs); 756 757 final TypedArray a = c.obtainStyledAttributes(attrs, LAYOUT_ATTRS); 758 this.gravity = a.getInt(0, Gravity.NO_GRAVITY); 759 a.recycle(); 760 } 761 762 public LayoutParams(int width, int height) { 763 super(width, height); 764 } 765 766 public LayoutParams(int width, int height, int gravity) { 767 this(width, height); 768 this.gravity = gravity; 769 } 770 771 public LayoutParams(LayoutParams source) { 772 super(source); 773 this.gravity = source.gravity; 774 } 775 776 public LayoutParams(ViewGroup.LayoutParams source) { 777 super(source); 778 } 779 } 780} 781