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