1 /* 2 * Copyright 2018 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 18 package androidx.drawerlayout.widget; 19 20 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; 21 22 import android.annotation.SuppressLint; 23 import android.annotation.TargetApi; 24 import android.content.Context; 25 import android.content.res.TypedArray; 26 import android.graphics.Canvas; 27 import android.graphics.Matrix; 28 import android.graphics.Paint; 29 import android.graphics.PixelFormat; 30 import android.graphics.Rect; 31 import android.graphics.drawable.ColorDrawable; 32 import android.graphics.drawable.Drawable; 33 import android.os.Build; 34 import android.os.Parcel; 35 import android.os.Parcelable; 36 import android.os.SystemClock; 37 import android.util.AttributeSet; 38 import android.view.Gravity; 39 import android.view.InputDevice; 40 import android.view.KeyEvent; 41 import android.view.MotionEvent; 42 import android.view.View; 43 import android.view.ViewGroup; 44 import android.view.ViewParent; 45 import android.view.WindowInsets; 46 import android.view.accessibility.AccessibilityEvent; 47 48 import androidx.annotation.ColorInt; 49 import androidx.annotation.DrawableRes; 50 import androidx.annotation.IntDef; 51 import androidx.annotation.NonNull; 52 import androidx.annotation.Nullable; 53 import androidx.annotation.RestrictTo; 54 import androidx.core.content.ContextCompat; 55 import androidx.core.graphics.drawable.DrawableCompat; 56 import androidx.core.view.AccessibilityDelegateCompat; 57 import androidx.core.view.GravityCompat; 58 import androidx.core.view.ViewCompat; 59 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; 60 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat; 61 import androidx.customview.view.AbsSavedState; 62 import androidx.customview.widget.ViewDragHelper; 63 64 import java.lang.annotation.Retention; 65 import java.lang.annotation.RetentionPolicy; 66 import java.util.ArrayList; 67 import java.util.List; 68 69 /** 70 * DrawerLayout acts as a top-level container for window content that allows for 71 * interactive "drawer" views to be pulled out from one or both vertical edges of the window. 72 * 73 * <p>Drawer positioning and layout is controlled using the <code>android:layout_gravity</code> 74 * attribute on child views corresponding to which side of the view you want the drawer 75 * to emerge from: left or right (or start/end on platform versions that support layout direction.) 76 * Note that you can only have one drawer view for each vertical edge of the window. If your 77 * layout configures more than one drawer view per vertical edge of the window, an exception will 78 * be thrown at runtime. 79 * </p> 80 * 81 * <p>To use a DrawerLayout, position your primary content view as the first child with 82 * width and height of <code>match_parent</code> and no <code>layout_gravity></code>. 83 * Add drawers as child views after the main content view and set the <code>layout_gravity</code> 84 * appropriately. Drawers commonly use <code>match_parent</code> for height with a fixed width.</p> 85 * 86 * <p>{@link DrawerListener} can be used to monitor the state and motion of drawer views. 87 * Avoid performing expensive operations such as layout during animation as it can cause 88 * stuttering; try to perform expensive operations during the {@link #STATE_IDLE} state. 89 * {@link SimpleDrawerListener} offers default/no-op implementations of each callback method.</p> 90 * 91 * <p>As per the <a href="{@docRoot}design/patterns/navigation-drawer.html">Android Design 92 * guide</a>, any drawers positioned to the left/start should 93 * always contain content for navigating around the application, whereas any drawers 94 * positioned to the right/end should always contain actions to take on the current content. 95 * This preserves the same navigation left, actions right structure present in the Action Bar 96 * and elsewhere.</p> 97 * 98 * <p>For more information about how to use DrawerLayout, read <a 99 * href="{@docRoot}training/implementing-navigation/nav-drawer.html">Creating a Navigation 100 * Drawer</a>.</p> 101 */ 102 public class DrawerLayout extends ViewGroup { 103 private static final String TAG = "DrawerLayout"; 104 105 private static final int[] THEME_ATTRS = { 106 android.R.attr.colorPrimaryDark 107 }; 108 109 @IntDef({STATE_IDLE, STATE_DRAGGING, STATE_SETTLING}) 110 @Retention(RetentionPolicy.SOURCE) 111 private @interface State {} 112 113 /** 114 * Indicates that any drawers are in an idle, settled state. No animation is in progress. 115 */ 116 public static final int STATE_IDLE = ViewDragHelper.STATE_IDLE; 117 118 /** 119 * Indicates that a drawer is currently being dragged by the user. 120 */ 121 public static final int STATE_DRAGGING = ViewDragHelper.STATE_DRAGGING; 122 123 /** 124 * Indicates that a drawer is in the process of settling to a final position. 125 */ 126 public static final int STATE_SETTLING = ViewDragHelper.STATE_SETTLING; 127 128 @IntDef({LOCK_MODE_UNLOCKED, LOCK_MODE_LOCKED_CLOSED, LOCK_MODE_LOCKED_OPEN, 129 LOCK_MODE_UNDEFINED}) 130 @Retention(RetentionPolicy.SOURCE) 131 private @interface LockMode {} 132 133 /** 134 * The drawer is unlocked. 135 */ 136 public static final int LOCK_MODE_UNLOCKED = 0; 137 138 /** 139 * The drawer is locked closed. The user may not open it, though 140 * the app may open it programmatically. 141 */ 142 public static final int LOCK_MODE_LOCKED_CLOSED = 1; 143 144 /** 145 * The drawer is locked open. The user may not close it, though the app 146 * may close it programmatically. 147 */ 148 public static final int LOCK_MODE_LOCKED_OPEN = 2; 149 150 /** 151 * The drawer's lock state is reset to default. 152 */ 153 public static final int LOCK_MODE_UNDEFINED = 3; 154 155 @IntDef(value = {Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END}, 156 flag = true) 157 @Retention(RetentionPolicy.SOURCE) 158 private @interface EdgeGravity {} 159 160 161 private static final int MIN_DRAWER_MARGIN = 64; // dp 162 private static final int DRAWER_ELEVATION = 10; //dp 163 164 private static final int DEFAULT_SCRIM_COLOR = 0x99000000; 165 166 /** 167 * Length of time to delay before peeking the drawer. 168 */ 169 private static final int PEEK_DELAY = 160; // ms 170 171 /** 172 * Minimum velocity that will be detected as a fling 173 */ 174 private static final int MIN_FLING_VELOCITY = 400; // dips per second 175 176 /** 177 * Experimental feature. 178 */ 179 private static final boolean ALLOW_EDGE_LOCK = false; 180 181 private static final boolean CHILDREN_DISALLOW_INTERCEPT = true; 182 183 private static final float TOUCH_SLOP_SENSITIVITY = 1.f; 184 185 static final int[] LAYOUT_ATTRS = new int[] { 186 android.R.attr.layout_gravity 187 }; 188 189 /** Whether we can use NO_HIDE_DESCENDANTS accessibility importance. */ 190 static final boolean CAN_HIDE_DESCENDANTS = Build.VERSION.SDK_INT >= 19; 191 192 /** Whether the drawer shadow comes from setting elevation on the drawer. */ 193 private static final boolean SET_DRAWER_SHADOW_FROM_ELEVATION = 194 Build.VERSION.SDK_INT >= 21; 195 196 private final ChildAccessibilityDelegate mChildAccessibilityDelegate = 197 new ChildAccessibilityDelegate(); 198 private float mDrawerElevation; 199 200 private int mMinDrawerMargin; 201 202 private int mScrimColor = DEFAULT_SCRIM_COLOR; 203 private float mScrimOpacity; 204 private Paint mScrimPaint = new Paint(); 205 206 private final ViewDragHelper mLeftDragger; 207 private final ViewDragHelper mRightDragger; 208 private final ViewDragCallback mLeftCallback; 209 private final ViewDragCallback mRightCallback; 210 private int mDrawerState; 211 private boolean mInLayout; 212 private boolean mFirstLayout = true; 213 214 private @LockMode int mLockModeLeft = LOCK_MODE_UNDEFINED; 215 private @LockMode int mLockModeRight = LOCK_MODE_UNDEFINED; 216 private @LockMode int mLockModeStart = LOCK_MODE_UNDEFINED; 217 private @LockMode int mLockModeEnd = LOCK_MODE_UNDEFINED; 218 219 private boolean mDisallowInterceptRequested; 220 private boolean mChildrenCanceledTouch; 221 222 private @Nullable DrawerListener mListener; 223 private List<DrawerListener> mListeners; 224 225 private float mInitialMotionX; 226 private float mInitialMotionY; 227 228 private Drawable mStatusBarBackground; 229 private Drawable mShadowLeftResolved; 230 private Drawable mShadowRightResolved; 231 232 private CharSequence mTitleLeft; 233 private CharSequence mTitleRight; 234 235 private Object mLastInsets; 236 private boolean mDrawStatusBarBackground; 237 238 /** Shadow drawables for different gravity */ 239 private Drawable mShadowStart = null; 240 private Drawable mShadowEnd = null; 241 private Drawable mShadowLeft = null; 242 private Drawable mShadowRight = null; 243 244 private final ArrayList<View> mNonDrawerViews; 245 246 private Rect mChildHitRect; 247 private Matrix mChildInvertedMatrix; 248 249 /** 250 * Listener for monitoring events about drawers. 251 */ 252 public interface DrawerListener { 253 /** 254 * Called when a drawer's position changes. 255 * @param drawerView The child view that was moved 256 * @param slideOffset The new offset of this drawer within its range, from 0-1 257 */ onDrawerSlide(@onNull View drawerView, float slideOffset)258 void onDrawerSlide(@NonNull View drawerView, float slideOffset); 259 260 /** 261 * Called when a drawer has settled in a completely open state. 262 * The drawer is interactive at this point. 263 * 264 * @param drawerView Drawer view that is now open 265 */ onDrawerOpened(@onNull View drawerView)266 void onDrawerOpened(@NonNull View drawerView); 267 268 /** 269 * Called when a drawer has settled in a completely closed state. 270 * 271 * @param drawerView Drawer view that is now closed 272 */ onDrawerClosed(@onNull View drawerView)273 void onDrawerClosed(@NonNull View drawerView); 274 275 /** 276 * Called when the drawer motion state changes. The new state will 277 * be one of {@link #STATE_IDLE}, {@link #STATE_DRAGGING} or {@link #STATE_SETTLING}. 278 * 279 * @param newState The new drawer motion state 280 */ onDrawerStateChanged(@tate int newState)281 void onDrawerStateChanged(@State int newState); 282 } 283 284 /** 285 * Stub/no-op implementations of all methods of {@link DrawerListener}. 286 * Override this if you only care about a few of the available callback methods. 287 */ 288 public abstract static class SimpleDrawerListener implements DrawerListener { 289 @Override onDrawerSlide(View drawerView, float slideOffset)290 public void onDrawerSlide(View drawerView, float slideOffset) { 291 } 292 293 @Override onDrawerOpened(View drawerView)294 public void onDrawerOpened(View drawerView) { 295 } 296 297 @Override onDrawerClosed(View drawerView)298 public void onDrawerClosed(View drawerView) { 299 } 300 301 @Override onDrawerStateChanged(int newState)302 public void onDrawerStateChanged(int newState) { 303 } 304 } 305 DrawerLayout(@onNull Context context)306 public DrawerLayout(@NonNull Context context) { 307 this(context, null); 308 } 309 DrawerLayout(@onNull Context context, @Nullable AttributeSet attrs)310 public DrawerLayout(@NonNull Context context, @Nullable AttributeSet attrs) { 311 this(context, attrs, 0); 312 } 313 DrawerLayout(@onNull Context context, @Nullable AttributeSet attrs, int defStyle)314 public DrawerLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) { 315 super(context, attrs, defStyle); 316 setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); 317 final float density = getResources().getDisplayMetrics().density; 318 mMinDrawerMargin = (int) (MIN_DRAWER_MARGIN * density + 0.5f); 319 final float minVel = MIN_FLING_VELOCITY * density; 320 321 mLeftCallback = new ViewDragCallback(Gravity.LEFT); 322 mRightCallback = new ViewDragCallback(Gravity.RIGHT); 323 324 mLeftDragger = ViewDragHelper.create(this, TOUCH_SLOP_SENSITIVITY, mLeftCallback); 325 mLeftDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT); 326 mLeftDragger.setMinVelocity(minVel); 327 mLeftCallback.setDragger(mLeftDragger); 328 329 mRightDragger = ViewDragHelper.create(this, TOUCH_SLOP_SENSITIVITY, mRightCallback); 330 mRightDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_RIGHT); 331 mRightDragger.setMinVelocity(minVel); 332 mRightCallback.setDragger(mRightDragger); 333 334 // So that we can catch the back button 335 setFocusableInTouchMode(true); 336 337 ViewCompat.setImportantForAccessibility(this, 338 ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES); 339 340 ViewCompat.setAccessibilityDelegate(this, new AccessibilityDelegate()); 341 setMotionEventSplittingEnabled(false); 342 if (ViewCompat.getFitsSystemWindows(this)) { 343 if (Build.VERSION.SDK_INT >= 21) { 344 setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() { 345 @TargetApi(21) 346 @Override 347 public WindowInsets onApplyWindowInsets(View view, WindowInsets insets) { 348 final DrawerLayout drawerLayout = (DrawerLayout) view; 349 drawerLayout.setChildInsets(insets, insets.getSystemWindowInsetTop() > 0); 350 return insets.consumeSystemWindowInsets(); 351 } 352 }); 353 setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE 354 | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); 355 final TypedArray a = context.obtainStyledAttributes(THEME_ATTRS); 356 try { 357 mStatusBarBackground = a.getDrawable(0); 358 } finally { 359 a.recycle(); 360 } 361 } else { 362 mStatusBarBackground = null; 363 } 364 } 365 366 mDrawerElevation = DRAWER_ELEVATION * density; 367 368 mNonDrawerViews = new ArrayList<View>(); 369 } 370 371 /** 372 * Sets the base elevation of the drawer(s) relative to the parent, in pixels. Note that the 373 * elevation change is only supported in API 21 and above. 374 * 375 * @param elevation The base depth position of the view, in pixels. 376 */ setDrawerElevation(float elevation)377 public void setDrawerElevation(float elevation) { 378 mDrawerElevation = elevation; 379 for (int i = 0; i < getChildCount(); i++) { 380 View child = getChildAt(i); 381 if (isDrawerView(child)) { 382 ViewCompat.setElevation(child, mDrawerElevation); 383 } 384 } 385 } 386 387 /** 388 * The base elevation of the drawer(s) relative to the parent, in pixels. Note that the 389 * elevation change is only supported in API 21 and above. For unsupported API levels, 0 will 390 * be returned as the elevation. 391 * 392 * @return The base depth position of the view, in pixels. 393 */ getDrawerElevation()394 public float getDrawerElevation() { 395 if (SET_DRAWER_SHADOW_FROM_ELEVATION) { 396 return mDrawerElevation; 397 } 398 return 0f; 399 } 400 401 /** 402 * @hide Internal use only; called to apply window insets when configured 403 * with fitsSystemWindows="true" 404 */ 405 @RestrictTo(LIBRARY_GROUP) setChildInsets(Object insets, boolean draw)406 public void setChildInsets(Object insets, boolean draw) { 407 mLastInsets = insets; 408 mDrawStatusBarBackground = draw; 409 setWillNotDraw(!draw && getBackground() == null); 410 requestLayout(); 411 } 412 413 /** 414 * Set a simple drawable used for the left or right shadow. The drawable provided must have a 415 * nonzero intrinsic width. For API 21 and above, an elevation will be set on the drawer 416 * instead of using the provided shadow drawable. 417 * 418 * <p>Note that for better support for both left-to-right and right-to-left layout 419 * directions, a drawable for RTL layout (in additional to the one in LTR layout) can be 420 * defined with a resource qualifier "ldrtl" for API 17 and above with the gravity 421 * {@link GravityCompat#START}. Alternatively, for API 23 and above, the drawable can 422 * auto-mirrored such that the drawable will be mirrored in RTL layout.</p> 423 * 424 * @param shadowDrawable Shadow drawable to use at the edge of a drawer 425 * @param gravity Which drawer the shadow should apply to 426 */ setDrawerShadow(Drawable shadowDrawable, @EdgeGravity int gravity)427 public void setDrawerShadow(Drawable shadowDrawable, @EdgeGravity int gravity) { 428 /* 429 * TODO Someone someday might want to set more complex drawables here. 430 * They're probably nuts, but we might want to consider registering callbacks, 431 * setting states, etc. properly. 432 */ 433 if (SET_DRAWER_SHADOW_FROM_ELEVATION) { 434 // No op. Drawer shadow will come from setting an elevation on the drawer. 435 return; 436 } 437 if ((gravity & GravityCompat.START) == GravityCompat.START) { 438 mShadowStart = shadowDrawable; 439 } else if ((gravity & GravityCompat.END) == GravityCompat.END) { 440 mShadowEnd = shadowDrawable; 441 } else if ((gravity & Gravity.LEFT) == Gravity.LEFT) { 442 mShadowLeft = shadowDrawable; 443 } else if ((gravity & Gravity.RIGHT) == Gravity.RIGHT) { 444 mShadowRight = shadowDrawable; 445 } else { 446 return; 447 } 448 resolveShadowDrawables(); 449 invalidate(); 450 } 451 452 /** 453 * Set a simple drawable used for the left or right shadow. The drawable provided must have a 454 * nonzero intrinsic width. For API 21 and above, an elevation will be set on the drawer 455 * instead of using the provided shadow drawable. 456 * 457 * <p>Note that for better support for both left-to-right and right-to-left layout 458 * directions, a drawable for RTL layout (in additional to the one in LTR layout) can be 459 * defined with a resource qualifier "ldrtl" for API 17 and above with the gravity 460 * {@link GravityCompat#START}. Alternatively, for API 23 and above, the drawable can 461 * auto-mirrored such that the drawable will be mirrored in RTL layout.</p> 462 * 463 * @param resId Resource id of a shadow drawable to use at the edge of a drawer 464 * @param gravity Which drawer the shadow should apply to 465 */ setDrawerShadow(@rawableRes int resId, @EdgeGravity int gravity)466 public void setDrawerShadow(@DrawableRes int resId, @EdgeGravity int gravity) { 467 setDrawerShadow(ContextCompat.getDrawable(getContext(), resId), gravity); 468 } 469 470 /** 471 * Set a color to use for the scrim that obscures primary content while a drawer is open. 472 * 473 * @param color Color to use in 0xAARRGGBB format. 474 */ setScrimColor(@olorInt int color)475 public void setScrimColor(@ColorInt int color) { 476 mScrimColor = color; 477 invalidate(); 478 } 479 480 /** 481 * Set a listener to be notified of drawer events. Note that this method is deprecated 482 * and you should use {@link #addDrawerListener(DrawerListener)} to add a listener and 483 * {@link #removeDrawerListener(DrawerListener)} to remove a registered listener. 484 * 485 * @param listener Listener to notify when drawer events occur 486 * @deprecated Use {@link #addDrawerListener(DrawerListener)} 487 * @see DrawerListener 488 * @see #addDrawerListener(DrawerListener) 489 * @see #removeDrawerListener(DrawerListener) 490 */ 491 @Deprecated setDrawerListener(DrawerListener listener)492 public void setDrawerListener(DrawerListener listener) { 493 // The logic in this method emulates what we had before support for multiple 494 // registered listeners. 495 if (mListener != null) { 496 removeDrawerListener(mListener); 497 } 498 if (listener != null) { 499 addDrawerListener(listener); 500 } 501 // Update the deprecated field so that we can remove the passed listener the next 502 // time we're called 503 mListener = listener; 504 } 505 506 /** 507 * Adds the specified listener to the list of listeners that will be notified of drawer events. 508 * 509 * @param listener Listener to notify when drawer events occur. 510 * @see #removeDrawerListener(DrawerListener) 511 */ addDrawerListener(@onNull DrawerListener listener)512 public void addDrawerListener(@NonNull DrawerListener listener) { 513 if (listener == null) { 514 return; 515 } 516 if (mListeners == null) { 517 mListeners = new ArrayList<DrawerListener>(); 518 } 519 mListeners.add(listener); 520 } 521 522 /** 523 * Removes the specified listener from the list of listeners that will be notified of drawer 524 * events. 525 * 526 * @param listener Listener to remove from being notified of drawer events 527 * @see #addDrawerListener(DrawerListener) 528 */ removeDrawerListener(@onNull DrawerListener listener)529 public void removeDrawerListener(@NonNull DrawerListener listener) { 530 if (listener == null) { 531 return; 532 } 533 if (mListeners == null) { 534 // This can happen if this method is called before the first call to addDrawerListener 535 return; 536 } 537 mListeners.remove(listener); 538 } 539 540 /** 541 * Enable or disable interaction with all drawers. 542 * 543 * <p>This allows the application to restrict the user's ability to open or close 544 * any drawer within this layout. DrawerLayout will still respond to calls to 545 * {@link #openDrawer(int)}, {@link #closeDrawer(int)} and friends if a drawer is locked.</p> 546 * 547 * <p>Locking drawers open or closed will implicitly open or close 548 * any drawers as appropriate.</p> 549 * 550 * @param lockMode The new lock mode for the given drawer. One of {@link #LOCK_MODE_UNLOCKED}, 551 * {@link #LOCK_MODE_LOCKED_CLOSED} or {@link #LOCK_MODE_LOCKED_OPEN}. 552 */ setDrawerLockMode(@ockMode int lockMode)553 public void setDrawerLockMode(@LockMode int lockMode) { 554 setDrawerLockMode(lockMode, Gravity.LEFT); 555 setDrawerLockMode(lockMode, Gravity.RIGHT); 556 } 557 558 /** 559 * Enable or disable interaction with the given drawer. 560 * 561 * <p>This allows the application to restrict the user's ability to open or close 562 * the given drawer. DrawerLayout will still respond to calls to {@link #openDrawer(int)}, 563 * {@link #closeDrawer(int)} and friends if a drawer is locked.</p> 564 * 565 * <p>Locking a drawer open or closed will implicitly open or close 566 * that drawer as appropriate.</p> 567 * 568 * @param lockMode The new lock mode for the given drawer. One of {@link #LOCK_MODE_UNLOCKED}, 569 * {@link #LOCK_MODE_LOCKED_CLOSED} or {@link #LOCK_MODE_LOCKED_OPEN}. 570 * @param edgeGravity Gravity.LEFT, RIGHT, START or END. 571 * Expresses which drawer to change the mode for. 572 * 573 * @see #LOCK_MODE_UNLOCKED 574 * @see #LOCK_MODE_LOCKED_CLOSED 575 * @see #LOCK_MODE_LOCKED_OPEN 576 */ setDrawerLockMode(@ockMode int lockMode, @EdgeGravity int edgeGravity)577 public void setDrawerLockMode(@LockMode int lockMode, @EdgeGravity int edgeGravity) { 578 final int absGravity = GravityCompat.getAbsoluteGravity(edgeGravity, 579 ViewCompat.getLayoutDirection(this)); 580 581 switch (edgeGravity) { 582 case Gravity.LEFT: 583 mLockModeLeft = lockMode; 584 break; 585 case Gravity.RIGHT: 586 mLockModeRight = lockMode; 587 break; 588 case GravityCompat.START: 589 mLockModeStart = lockMode; 590 break; 591 case GravityCompat.END: 592 mLockModeEnd = lockMode; 593 break; 594 } 595 596 if (lockMode != LOCK_MODE_UNLOCKED) { 597 // Cancel interaction in progress 598 final ViewDragHelper helper = absGravity == Gravity.LEFT ? mLeftDragger : mRightDragger; 599 helper.cancel(); 600 } 601 switch (lockMode) { 602 case LOCK_MODE_LOCKED_OPEN: 603 final View toOpen = findDrawerWithGravity(absGravity); 604 if (toOpen != null) { 605 openDrawer(toOpen); 606 } 607 break; 608 case LOCK_MODE_LOCKED_CLOSED: 609 final View toClose = findDrawerWithGravity(absGravity); 610 if (toClose != null) { 611 closeDrawer(toClose); 612 } 613 break; 614 // default: do nothing 615 } 616 } 617 618 /** 619 * Enable or disable interaction with the given drawer. 620 * 621 * <p>This allows the application to restrict the user's ability to open or close 622 * the given drawer. DrawerLayout will still respond to calls to {@link #openDrawer(int)}, 623 * {@link #closeDrawer(int)} and friends if a drawer is locked.</p> 624 * 625 * <p>Locking a drawer open or closed will implicitly open or close 626 * that drawer as appropriate.</p> 627 * 628 * @param lockMode The new lock mode for the given drawer. One of {@link #LOCK_MODE_UNLOCKED}, 629 * {@link #LOCK_MODE_LOCKED_CLOSED} or {@link #LOCK_MODE_LOCKED_OPEN}. 630 * @param drawerView The drawer view to change the lock mode for 631 * 632 * @see #LOCK_MODE_UNLOCKED 633 * @see #LOCK_MODE_LOCKED_CLOSED 634 * @see #LOCK_MODE_LOCKED_OPEN 635 */ setDrawerLockMode(@ockMode int lockMode, @NonNull View drawerView)636 public void setDrawerLockMode(@LockMode int lockMode, @NonNull View drawerView) { 637 if (!isDrawerView(drawerView)) { 638 throw new IllegalArgumentException("View " + drawerView + " is not a " 639 + "drawer with appropriate layout_gravity"); 640 } 641 final int gravity = ((LayoutParams) drawerView.getLayoutParams()).gravity; 642 setDrawerLockMode(lockMode, gravity); 643 } 644 645 /** 646 * Check the lock mode of the drawer with the given gravity. 647 * 648 * @param edgeGravity Gravity of the drawer to check 649 * @return one of {@link #LOCK_MODE_UNLOCKED}, {@link #LOCK_MODE_LOCKED_CLOSED} or 650 * {@link #LOCK_MODE_LOCKED_OPEN}. 651 */ 652 @LockMode getDrawerLockMode(@dgeGravity int edgeGravity)653 public int getDrawerLockMode(@EdgeGravity int edgeGravity) { 654 int layoutDirection = ViewCompat.getLayoutDirection(this); 655 656 switch (edgeGravity) { 657 case Gravity.LEFT: 658 if (mLockModeLeft != LOCK_MODE_UNDEFINED) { 659 return mLockModeLeft; 660 } 661 int leftLockMode = (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR) 662 ? mLockModeStart : mLockModeEnd; 663 if (leftLockMode != LOCK_MODE_UNDEFINED) { 664 return leftLockMode; 665 } 666 break; 667 case Gravity.RIGHT: 668 if (mLockModeRight != LOCK_MODE_UNDEFINED) { 669 return mLockModeRight; 670 } 671 int rightLockMode = (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR) 672 ? mLockModeEnd : mLockModeStart; 673 if (rightLockMode != LOCK_MODE_UNDEFINED) { 674 return rightLockMode; 675 } 676 break; 677 case GravityCompat.START: 678 if (mLockModeStart != LOCK_MODE_UNDEFINED) { 679 return mLockModeStart; 680 } 681 int startLockMode = (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR) 682 ? mLockModeLeft : mLockModeRight; 683 if (startLockMode != LOCK_MODE_UNDEFINED) { 684 return startLockMode; 685 } 686 break; 687 case GravityCompat.END: 688 if (mLockModeEnd != LOCK_MODE_UNDEFINED) { 689 return mLockModeEnd; 690 } 691 int endLockMode = (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR) 692 ? mLockModeRight : mLockModeLeft; 693 if (endLockMode != LOCK_MODE_UNDEFINED) { 694 return endLockMode; 695 } 696 break; 697 } 698 699 return LOCK_MODE_UNLOCKED; 700 } 701 702 /** 703 * Check the lock mode of the given drawer view. 704 * 705 * @param drawerView Drawer view to check lock mode 706 * @return one of {@link #LOCK_MODE_UNLOCKED}, {@link #LOCK_MODE_LOCKED_CLOSED} or 707 * {@link #LOCK_MODE_LOCKED_OPEN}. 708 */ 709 @LockMode getDrawerLockMode(@onNull View drawerView)710 public int getDrawerLockMode(@NonNull View drawerView) { 711 if (!isDrawerView(drawerView)) { 712 throw new IllegalArgumentException("View " + drawerView + " is not a drawer"); 713 } 714 final int drawerGravity = ((LayoutParams) drawerView.getLayoutParams()).gravity; 715 return getDrawerLockMode(drawerGravity); 716 } 717 718 /** 719 * Sets the title of the drawer with the given gravity. 720 * <p> 721 * When accessibility is turned on, this is the title that will be used to 722 * identify the drawer to the active accessibility service. 723 * 724 * @param edgeGravity Gravity.LEFT, RIGHT, START or END. Expresses which 725 * drawer to set the title for. 726 * @param title The title for the drawer. 727 */ setDrawerTitle(@dgeGravity int edgeGravity, @Nullable CharSequence title)728 public void setDrawerTitle(@EdgeGravity int edgeGravity, @Nullable CharSequence title) { 729 final int absGravity = GravityCompat.getAbsoluteGravity( 730 edgeGravity, ViewCompat.getLayoutDirection(this)); 731 if (absGravity == Gravity.LEFT) { 732 mTitleLeft = title; 733 } else if (absGravity == Gravity.RIGHT) { 734 mTitleRight = title; 735 } 736 } 737 738 /** 739 * Returns the title of the drawer with the given gravity. 740 * 741 * @param edgeGravity Gravity.LEFT, RIGHT, START or END. Expresses which 742 * drawer to return the title for. 743 * @return The title of the drawer, or null if none set. 744 * @see #setDrawerTitle(int, CharSequence) 745 */ 746 @Nullable getDrawerTitle(@dgeGravity int edgeGravity)747 public CharSequence getDrawerTitle(@EdgeGravity int edgeGravity) { 748 final int absGravity = GravityCompat.getAbsoluteGravity( 749 edgeGravity, ViewCompat.getLayoutDirection(this)); 750 if (absGravity == Gravity.LEFT) { 751 return mTitleLeft; 752 } else if (absGravity == Gravity.RIGHT) { 753 return mTitleRight; 754 } 755 return null; 756 } 757 758 /** 759 * Returns true if x and y coord in DrawerLayout's coordinate space are inside the bounds of the 760 * child's coordinate space. 761 */ isInBoundsOfChild(float x, float y, View child)762 private boolean isInBoundsOfChild(float x, float y, View child) { 763 if (mChildHitRect == null) { 764 mChildHitRect = new Rect(); 765 } 766 child.getHitRect(mChildHitRect); 767 return mChildHitRect.contains((int) x, (int) y); 768 } 769 770 /** 771 * Copied from ViewGroup#dispatchTransformedGenericPointerEvent(MotionEvent, View) then modified 772 * in order to make calls that are otherwise too visibility restricted to make. 773 */ dispatchTransformedGenericPointerEvent(MotionEvent event, View child)774 private boolean dispatchTransformedGenericPointerEvent(MotionEvent event, View child) { 775 boolean handled; 776 final Matrix childMatrix = child.getMatrix(); 777 if (!childMatrix.isIdentity()) { 778 MotionEvent transformedEvent = getTransformedMotionEvent(event, child); 779 handled = child.dispatchGenericMotionEvent(transformedEvent); 780 transformedEvent.recycle(); 781 } else { 782 final float offsetX = getScrollX() - child.getLeft(); 783 final float offsetY = getScrollY() - child.getTop(); 784 event.offsetLocation(offsetX, offsetY); 785 handled = child.dispatchGenericMotionEvent(event); 786 event.offsetLocation(-offsetX, -offsetY); 787 } 788 return handled; 789 } 790 791 /** 792 * Copied from ViewGroup#getTransformedMotionEvent(MotionEvent, View) then modified in order to 793 * make calls that are otherwise too visibility restricted to make. 794 */ getTransformedMotionEvent(MotionEvent event, View child)795 private MotionEvent getTransformedMotionEvent(MotionEvent event, View child) { 796 final float offsetX = getScrollX() - child.getLeft(); 797 final float offsetY = getScrollY() - child.getTop(); 798 final MotionEvent transformedEvent = MotionEvent.obtain(event); 799 transformedEvent.offsetLocation(offsetX, offsetY); 800 final Matrix childMatrix = child.getMatrix(); 801 if (!childMatrix.isIdentity()) { 802 if (mChildInvertedMatrix == null) { 803 mChildInvertedMatrix = new Matrix(); 804 } 805 childMatrix.invert(mChildInvertedMatrix); 806 transformedEvent.transform(mChildInvertedMatrix); 807 } 808 return transformedEvent; 809 } 810 811 /** 812 * Resolve the shared state of all drawers from the component ViewDragHelpers. 813 * Should be called whenever a ViewDragHelper's state changes. 814 */ updateDrawerState(int forGravity, @State int activeState, View activeDrawer)815 void updateDrawerState(int forGravity, @State int activeState, View activeDrawer) { 816 final int leftState = mLeftDragger.getViewDragState(); 817 final int rightState = mRightDragger.getViewDragState(); 818 819 final int state; 820 if (leftState == STATE_DRAGGING || rightState == STATE_DRAGGING) { 821 state = STATE_DRAGGING; 822 } else if (leftState == STATE_SETTLING || rightState == STATE_SETTLING) { 823 state = STATE_SETTLING; 824 } else { 825 state = STATE_IDLE; 826 } 827 828 if (activeDrawer != null && activeState == STATE_IDLE) { 829 final LayoutParams lp = (LayoutParams) activeDrawer.getLayoutParams(); 830 if (lp.onScreen == 0) { 831 dispatchOnDrawerClosed(activeDrawer); 832 } else if (lp.onScreen == 1) { 833 dispatchOnDrawerOpened(activeDrawer); 834 } 835 } 836 837 if (state != mDrawerState) { 838 mDrawerState = state; 839 840 if (mListeners != null) { 841 // Notify the listeners. Do that from the end of the list so that if a listener 842 // removes itself as the result of being called, it won't mess up with our iteration 843 int listenerCount = mListeners.size(); 844 for (int i = listenerCount - 1; i >= 0; i--) { 845 mListeners.get(i).onDrawerStateChanged(state); 846 } 847 } 848 } 849 } 850 dispatchOnDrawerClosed(View drawerView)851 void dispatchOnDrawerClosed(View drawerView) { 852 final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); 853 if ((lp.openState & LayoutParams.FLAG_IS_OPENED) == 1) { 854 lp.openState = 0; 855 856 if (mListeners != null) { 857 // Notify the listeners. Do that from the end of the list so that if a listener 858 // removes itself as the result of being called, it won't mess up with our iteration 859 int listenerCount = mListeners.size(); 860 for (int i = listenerCount - 1; i >= 0; i--) { 861 mListeners.get(i).onDrawerClosed(drawerView); 862 } 863 } 864 865 updateChildrenImportantForAccessibility(drawerView, false); 866 867 // Only send WINDOW_STATE_CHANGE if the host has window focus. This 868 // may change if support for multiple foreground windows (e.g. IME) 869 // improves. 870 if (hasWindowFocus()) { 871 final View rootView = getRootView(); 872 if (rootView != null) { 873 rootView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); 874 } 875 } 876 } 877 } 878 dispatchOnDrawerOpened(View drawerView)879 void dispatchOnDrawerOpened(View drawerView) { 880 final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); 881 if ((lp.openState & LayoutParams.FLAG_IS_OPENED) == 0) { 882 lp.openState = LayoutParams.FLAG_IS_OPENED; 883 if (mListeners != null) { 884 // Notify the listeners. Do that from the end of the list so that if a listener 885 // removes itself as the result of being called, it won't mess up with our iteration 886 int listenerCount = mListeners.size(); 887 for (int i = listenerCount - 1; i >= 0; i--) { 888 mListeners.get(i).onDrawerOpened(drawerView); 889 } 890 } 891 892 updateChildrenImportantForAccessibility(drawerView, true); 893 894 // Only send WINDOW_STATE_CHANGE if the host has window focus. 895 if (hasWindowFocus()) { 896 sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); 897 } 898 } 899 } 900 updateChildrenImportantForAccessibility(View drawerView, boolean isDrawerOpen)901 private void updateChildrenImportantForAccessibility(View drawerView, boolean isDrawerOpen) { 902 final int childCount = getChildCount(); 903 for (int i = 0; i < childCount; i++) { 904 final View child = getChildAt(i); 905 if ((!isDrawerOpen && !isDrawerView(child)) || (isDrawerOpen && child == drawerView)) { 906 // Drawer is closed and this is a content view or this is an 907 // open drawer view, so it should be visible. 908 ViewCompat.setImportantForAccessibility(child, 909 ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES); 910 } else { 911 ViewCompat.setImportantForAccessibility(child, 912 ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); 913 } 914 } 915 } 916 dispatchOnDrawerSlide(View drawerView, float slideOffset)917 void dispatchOnDrawerSlide(View drawerView, float slideOffset) { 918 if (mListeners != null) { 919 // Notify the listeners. Do that from the end of the list so that if a listener 920 // removes itself as the result of being called, it won't mess up with our iteration 921 int listenerCount = mListeners.size(); 922 for (int i = listenerCount - 1; i >= 0; i--) { 923 mListeners.get(i).onDrawerSlide(drawerView, slideOffset); 924 } 925 } 926 } 927 setDrawerViewOffset(View drawerView, float slideOffset)928 void setDrawerViewOffset(View drawerView, float slideOffset) { 929 final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); 930 if (slideOffset == lp.onScreen) { 931 return; 932 } 933 934 lp.onScreen = slideOffset; 935 dispatchOnDrawerSlide(drawerView, slideOffset); 936 } 937 getDrawerViewOffset(View drawerView)938 float getDrawerViewOffset(View drawerView) { 939 return ((LayoutParams) drawerView.getLayoutParams()).onScreen; 940 } 941 942 /** 943 * @return the absolute gravity of the child drawerView, resolved according 944 * to the current layout direction 945 */ getDrawerViewAbsoluteGravity(View drawerView)946 int getDrawerViewAbsoluteGravity(View drawerView) { 947 final int gravity = ((LayoutParams) drawerView.getLayoutParams()).gravity; 948 return GravityCompat.getAbsoluteGravity(gravity, ViewCompat.getLayoutDirection(this)); 949 } 950 checkDrawerViewAbsoluteGravity(View drawerView, int checkFor)951 boolean checkDrawerViewAbsoluteGravity(View drawerView, int checkFor) { 952 final int absGravity = getDrawerViewAbsoluteGravity(drawerView); 953 return (absGravity & checkFor) == checkFor; 954 } 955 findOpenDrawer()956 View findOpenDrawer() { 957 final int childCount = getChildCount(); 958 for (int i = 0; i < childCount; i++) { 959 final View child = getChildAt(i); 960 final LayoutParams childLp = (LayoutParams) child.getLayoutParams(); 961 if ((childLp.openState & LayoutParams.FLAG_IS_OPENED) == 1) { 962 return child; 963 } 964 } 965 return null; 966 } 967 moveDrawerToOffset(View drawerView, float slideOffset)968 void moveDrawerToOffset(View drawerView, float slideOffset) { 969 final float oldOffset = getDrawerViewOffset(drawerView); 970 final int width = drawerView.getWidth(); 971 final int oldPos = (int) (width * oldOffset); 972 final int newPos = (int) (width * slideOffset); 973 final int dx = newPos - oldPos; 974 975 drawerView.offsetLeftAndRight( 976 checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT) ? dx : -dx); 977 setDrawerViewOffset(drawerView, slideOffset); 978 } 979 980 /** 981 * @param gravity the gravity of the child to return. If specified as a 982 * relative value, it will be resolved according to the current 983 * layout direction. 984 * @return the drawer with the specified gravity 985 */ findDrawerWithGravity(int gravity)986 View findDrawerWithGravity(int gravity) { 987 final int absHorizGravity = GravityCompat.getAbsoluteGravity( 988 gravity, ViewCompat.getLayoutDirection(this)) & Gravity.HORIZONTAL_GRAVITY_MASK; 989 final int childCount = getChildCount(); 990 for (int i = 0; i < childCount; i++) { 991 final View child = getChildAt(i); 992 final int childAbsGravity = getDrawerViewAbsoluteGravity(child); 993 if ((childAbsGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == absHorizGravity) { 994 return child; 995 } 996 } 997 return null; 998 } 999 1000 /** 1001 * Simple gravity to string - only supports LEFT and RIGHT for debugging output. 1002 * 1003 * @param gravity Absolute gravity value 1004 * @return LEFT or RIGHT as appropriate, or a hex string 1005 */ gravityToString(@dgeGravity int gravity)1006 static String gravityToString(@EdgeGravity int gravity) { 1007 if ((gravity & Gravity.LEFT) == Gravity.LEFT) { 1008 return "LEFT"; 1009 } 1010 if ((gravity & Gravity.RIGHT) == Gravity.RIGHT) { 1011 return "RIGHT"; 1012 } 1013 return Integer.toHexString(gravity); 1014 } 1015 1016 @Override onDetachedFromWindow()1017 protected void onDetachedFromWindow() { 1018 super.onDetachedFromWindow(); 1019 mFirstLayout = true; 1020 } 1021 1022 @Override onAttachedToWindow()1023 protected void onAttachedToWindow() { 1024 super.onAttachedToWindow(); 1025 mFirstLayout = true; 1026 } 1027 1028 @SuppressLint("WrongConstant") 1029 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)1030 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 1031 int widthMode = MeasureSpec.getMode(widthMeasureSpec); 1032 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 1033 int widthSize = MeasureSpec.getSize(widthMeasureSpec); 1034 int heightSize = MeasureSpec.getSize(heightMeasureSpec); 1035 1036 if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) { 1037 if (isInEditMode()) { 1038 // Don't crash the layout editor. Consume all of the space if specified 1039 // or pick a magic number from thin air otherwise. 1040 // TODO Better communication with tools of this bogus state. 1041 // It will crash on a real device. 1042 if (widthMode == MeasureSpec.AT_MOST) { 1043 widthMode = MeasureSpec.EXACTLY; 1044 } else if (widthMode == MeasureSpec.UNSPECIFIED) { 1045 widthMode = MeasureSpec.EXACTLY; 1046 widthSize = 300; 1047 } 1048 if (heightMode == MeasureSpec.AT_MOST) { 1049 heightMode = MeasureSpec.EXACTLY; 1050 } else if (heightMode == MeasureSpec.UNSPECIFIED) { 1051 heightMode = MeasureSpec.EXACTLY; 1052 heightSize = 300; 1053 } 1054 } else { 1055 throw new IllegalArgumentException( 1056 "DrawerLayout must be measured with MeasureSpec.EXACTLY."); 1057 } 1058 } 1059 1060 setMeasuredDimension(widthSize, heightSize); 1061 1062 final boolean applyInsets = mLastInsets != null && ViewCompat.getFitsSystemWindows(this); 1063 final int layoutDirection = ViewCompat.getLayoutDirection(this); 1064 1065 // Only one drawer is permitted along each vertical edge (left / right). These two booleans 1066 // are tracking the presence of the edge drawers. 1067 boolean hasDrawerOnLeftEdge = false; 1068 boolean hasDrawerOnRightEdge = false; 1069 final int childCount = getChildCount(); 1070 for (int i = 0; i < childCount; i++) { 1071 final View child = getChildAt(i); 1072 1073 if (child.getVisibility() == GONE) { 1074 continue; 1075 } 1076 1077 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 1078 1079 if (applyInsets) { 1080 final int cgrav = GravityCompat.getAbsoluteGravity(lp.gravity, layoutDirection); 1081 if (ViewCompat.getFitsSystemWindows(child)) { 1082 if (Build.VERSION.SDK_INT >= 21) { 1083 WindowInsets wi = (WindowInsets) mLastInsets; 1084 if (cgrav == Gravity.LEFT) { 1085 wi = wi.replaceSystemWindowInsets(wi.getSystemWindowInsetLeft(), 1086 wi.getSystemWindowInsetTop(), 0, 1087 wi.getSystemWindowInsetBottom()); 1088 } else if (cgrav == Gravity.RIGHT) { 1089 wi = wi.replaceSystemWindowInsets(0, wi.getSystemWindowInsetTop(), 1090 wi.getSystemWindowInsetRight(), 1091 wi.getSystemWindowInsetBottom()); 1092 } 1093 child.dispatchApplyWindowInsets(wi); 1094 } 1095 } else { 1096 if (Build.VERSION.SDK_INT >= 21) { 1097 WindowInsets wi = (WindowInsets) mLastInsets; 1098 if (cgrav == Gravity.LEFT) { 1099 wi = wi.replaceSystemWindowInsets(wi.getSystemWindowInsetLeft(), 1100 wi.getSystemWindowInsetTop(), 0, 1101 wi.getSystemWindowInsetBottom()); 1102 } else if (cgrav == Gravity.RIGHT) { 1103 wi = wi.replaceSystemWindowInsets(0, wi.getSystemWindowInsetTop(), 1104 wi.getSystemWindowInsetRight(), 1105 wi.getSystemWindowInsetBottom()); 1106 } 1107 lp.leftMargin = wi.getSystemWindowInsetLeft(); 1108 lp.topMargin = wi.getSystemWindowInsetTop(); 1109 lp.rightMargin = wi.getSystemWindowInsetRight(); 1110 lp.bottomMargin = wi.getSystemWindowInsetBottom(); 1111 } 1112 } 1113 } 1114 1115 if (isContentView(child)) { 1116 // Content views get measured at exactly the layout's size. 1117 final int contentWidthSpec = MeasureSpec.makeMeasureSpec( 1118 widthSize - lp.leftMargin - lp.rightMargin, MeasureSpec.EXACTLY); 1119 final int contentHeightSpec = MeasureSpec.makeMeasureSpec( 1120 heightSize - lp.topMargin - lp.bottomMargin, MeasureSpec.EXACTLY); 1121 child.measure(contentWidthSpec, contentHeightSpec); 1122 } else if (isDrawerView(child)) { 1123 if (SET_DRAWER_SHADOW_FROM_ELEVATION) { 1124 if (ViewCompat.getElevation(child) != mDrawerElevation) { 1125 ViewCompat.setElevation(child, mDrawerElevation); 1126 } 1127 } 1128 final @EdgeGravity int childGravity = 1129 getDrawerViewAbsoluteGravity(child) & Gravity.HORIZONTAL_GRAVITY_MASK; 1130 // Note that the isDrawerView check guarantees that childGravity here is either 1131 // LEFT or RIGHT 1132 boolean isLeftEdgeDrawer = (childGravity == Gravity.LEFT); 1133 if ((isLeftEdgeDrawer && hasDrawerOnLeftEdge) 1134 || (!isLeftEdgeDrawer && hasDrawerOnRightEdge)) { 1135 throw new IllegalStateException("Child drawer has absolute gravity " 1136 + gravityToString(childGravity) + " but this " + TAG + " already has a " 1137 + "drawer view along that edge"); 1138 } 1139 if (isLeftEdgeDrawer) { 1140 hasDrawerOnLeftEdge = true; 1141 } else { 1142 hasDrawerOnRightEdge = true; 1143 } 1144 final int drawerWidthSpec = getChildMeasureSpec(widthMeasureSpec, 1145 mMinDrawerMargin + lp.leftMargin + lp.rightMargin, 1146 lp.width); 1147 final int drawerHeightSpec = getChildMeasureSpec(heightMeasureSpec, 1148 lp.topMargin + lp.bottomMargin, 1149 lp.height); 1150 child.measure(drawerWidthSpec, drawerHeightSpec); 1151 } else { 1152 throw new IllegalStateException("Child " + child + " at index " + i 1153 + " does not have a valid layout_gravity - must be Gravity.LEFT, " 1154 + "Gravity.RIGHT or Gravity.NO_GRAVITY"); 1155 } 1156 } 1157 } 1158 resolveShadowDrawables()1159 private void resolveShadowDrawables() { 1160 if (SET_DRAWER_SHADOW_FROM_ELEVATION) { 1161 return; 1162 } 1163 mShadowLeftResolved = resolveLeftShadow(); 1164 mShadowRightResolved = resolveRightShadow(); 1165 } 1166 resolveLeftShadow()1167 private Drawable resolveLeftShadow() { 1168 int layoutDirection = ViewCompat.getLayoutDirection(this); 1169 // Prefer shadows defined with start/end gravity over left and right. 1170 if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR) { 1171 if (mShadowStart != null) { 1172 // Correct drawable layout direction, if needed. 1173 mirror(mShadowStart, layoutDirection); 1174 return mShadowStart; 1175 } 1176 } else { 1177 if (mShadowEnd != null) { 1178 // Correct drawable layout direction, if needed. 1179 mirror(mShadowEnd, layoutDirection); 1180 return mShadowEnd; 1181 } 1182 } 1183 return mShadowLeft; 1184 } 1185 resolveRightShadow()1186 private Drawable resolveRightShadow() { 1187 int layoutDirection = ViewCompat.getLayoutDirection(this); 1188 if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR) { 1189 if (mShadowEnd != null) { 1190 // Correct drawable layout direction, if needed. 1191 mirror(mShadowEnd, layoutDirection); 1192 return mShadowEnd; 1193 } 1194 } else { 1195 if (mShadowStart != null) { 1196 // Correct drawable layout direction, if needed. 1197 mirror(mShadowStart, layoutDirection); 1198 return mShadowStart; 1199 } 1200 } 1201 return mShadowRight; 1202 } 1203 1204 /** 1205 * Change the layout direction of the given drawable. 1206 * Return true if auto-mirror is supported and drawable's layout direction can be changed. 1207 * Otherwise, return false. 1208 */ mirror(Drawable drawable, int layoutDirection)1209 private boolean mirror(Drawable drawable, int layoutDirection) { 1210 if (drawable == null || !DrawableCompat.isAutoMirrored(drawable)) { 1211 return false; 1212 } 1213 1214 DrawableCompat.setLayoutDirection(drawable, layoutDirection); 1215 return true; 1216 } 1217 1218 @Override onLayout(boolean changed, int l, int t, int r, int b)1219 protected void onLayout(boolean changed, int l, int t, int r, int b) { 1220 mInLayout = true; 1221 final int width = r - l; 1222 final int childCount = getChildCount(); 1223 for (int i = 0; i < childCount; i++) { 1224 final View child = getChildAt(i); 1225 1226 if (child.getVisibility() == GONE) { 1227 continue; 1228 } 1229 1230 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 1231 1232 if (isContentView(child)) { 1233 child.layout(lp.leftMargin, lp.topMargin, 1234 lp.leftMargin + child.getMeasuredWidth(), 1235 lp.topMargin + child.getMeasuredHeight()); 1236 } else { // Drawer, if it wasn't onMeasure would have thrown an exception. 1237 final int childWidth = child.getMeasuredWidth(); 1238 final int childHeight = child.getMeasuredHeight(); 1239 int childLeft; 1240 1241 final float newOffset; 1242 if (checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) { 1243 childLeft = -childWidth + (int) (childWidth * lp.onScreen); 1244 newOffset = (float) (childWidth + childLeft) / childWidth; 1245 } else { // Right; onMeasure checked for us. 1246 childLeft = width - (int) (childWidth * lp.onScreen); 1247 newOffset = (float) (width - childLeft) / childWidth; 1248 } 1249 1250 final boolean changeOffset = newOffset != lp.onScreen; 1251 1252 final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK; 1253 1254 switch (vgrav) { 1255 default: 1256 case Gravity.TOP: { 1257 child.layout(childLeft, lp.topMargin, childLeft + childWidth, 1258 lp.topMargin + childHeight); 1259 break; 1260 } 1261 1262 case Gravity.BOTTOM: { 1263 final int height = b - t; 1264 child.layout(childLeft, 1265 height - lp.bottomMargin - child.getMeasuredHeight(), 1266 childLeft + childWidth, 1267 height - lp.bottomMargin); 1268 break; 1269 } 1270 1271 case Gravity.CENTER_VERTICAL: { 1272 final int height = b - t; 1273 int childTop = (height - childHeight) / 2; 1274 1275 // Offset for margins. If things don't fit right because of 1276 // bad measurement before, oh well. 1277 if (childTop < lp.topMargin) { 1278 childTop = lp.topMargin; 1279 } else if (childTop + childHeight > height - lp.bottomMargin) { 1280 childTop = height - lp.bottomMargin - childHeight; 1281 } 1282 child.layout(childLeft, childTop, childLeft + childWidth, 1283 childTop + childHeight); 1284 break; 1285 } 1286 } 1287 1288 if (changeOffset) { 1289 setDrawerViewOffset(child, newOffset); 1290 } 1291 1292 final int newVisibility = lp.onScreen > 0 ? VISIBLE : INVISIBLE; 1293 if (child.getVisibility() != newVisibility) { 1294 child.setVisibility(newVisibility); 1295 } 1296 } 1297 } 1298 mInLayout = false; 1299 mFirstLayout = false; 1300 } 1301 1302 @Override requestLayout()1303 public void requestLayout() { 1304 if (!mInLayout) { 1305 super.requestLayout(); 1306 } 1307 } 1308 1309 @Override computeScroll()1310 public void computeScroll() { 1311 final int childCount = getChildCount(); 1312 float scrimOpacity = 0; 1313 for (int i = 0; i < childCount; i++) { 1314 final float onscreen = ((LayoutParams) getChildAt(i).getLayoutParams()).onScreen; 1315 scrimOpacity = Math.max(scrimOpacity, onscreen); 1316 } 1317 mScrimOpacity = scrimOpacity; 1318 1319 boolean leftDraggerSettling = mLeftDragger.continueSettling(true); 1320 boolean rightDraggerSettling = mRightDragger.continueSettling(true); 1321 if (leftDraggerSettling || rightDraggerSettling) { 1322 ViewCompat.postInvalidateOnAnimation(this); 1323 } 1324 } 1325 hasOpaqueBackground(View v)1326 private static boolean hasOpaqueBackground(View v) { 1327 final Drawable bg = v.getBackground(); 1328 if (bg != null) { 1329 return bg.getOpacity() == PixelFormat.OPAQUE; 1330 } 1331 return false; 1332 } 1333 1334 /** 1335 * Set a drawable to draw in the insets area for the status bar. 1336 * Note that this will only be activated if this DrawerLayout fitsSystemWindows. 1337 * 1338 * @param bg Background drawable to draw behind the status bar 1339 */ setStatusBarBackground(@ullable Drawable bg)1340 public void setStatusBarBackground(@Nullable Drawable bg) { 1341 mStatusBarBackground = bg; 1342 invalidate(); 1343 } 1344 1345 /** 1346 * Gets the drawable used to draw in the insets area for the status bar. 1347 * 1348 * @return The status bar background drawable, or null if none set 1349 */ 1350 @Nullable getStatusBarBackgroundDrawable()1351 public Drawable getStatusBarBackgroundDrawable() { 1352 return mStatusBarBackground; 1353 } 1354 1355 /** 1356 * Set a drawable to draw in the insets area for the status bar. 1357 * Note that this will only be activated if this DrawerLayout fitsSystemWindows. 1358 * 1359 * @param resId Resource id of a background drawable to draw behind the status bar 1360 */ setStatusBarBackground(int resId)1361 public void setStatusBarBackground(int resId) { 1362 mStatusBarBackground = resId != 0 ? ContextCompat.getDrawable(getContext(), resId) : null; 1363 invalidate(); 1364 } 1365 1366 /** 1367 * Set a drawable to draw in the insets area for the status bar. 1368 * Note that this will only be activated if this DrawerLayout fitsSystemWindows. 1369 * 1370 * @param color Color to use as a background drawable to draw behind the status bar 1371 * in 0xAARRGGBB format. 1372 */ setStatusBarBackgroundColor(@olorInt int color)1373 public void setStatusBarBackgroundColor(@ColorInt int color) { 1374 mStatusBarBackground = new ColorDrawable(color); 1375 invalidate(); 1376 } 1377 1378 @Override onRtlPropertiesChanged(int layoutDirection)1379 public void onRtlPropertiesChanged(int layoutDirection) { 1380 resolveShadowDrawables(); 1381 } 1382 1383 @Override onDraw(Canvas c)1384 public void onDraw(Canvas c) { 1385 super.onDraw(c); 1386 if (mDrawStatusBarBackground && mStatusBarBackground != null) { 1387 final int inset; 1388 if (Build.VERSION.SDK_INT >= 21) { 1389 inset = mLastInsets != null 1390 ? ((WindowInsets) mLastInsets).getSystemWindowInsetTop() : 0; 1391 } else { 1392 inset = 0; 1393 } 1394 if (inset > 0) { 1395 mStatusBarBackground.setBounds(0, 0, getWidth(), inset); 1396 mStatusBarBackground.draw(c); 1397 } 1398 } 1399 } 1400 1401 @Override drawChild(Canvas canvas, View child, long drawingTime)1402 protected boolean drawChild(Canvas canvas, View child, long drawingTime) { 1403 final int height = getHeight(); 1404 final boolean drawingContent = isContentView(child); 1405 int clipLeft = 0, clipRight = getWidth(); 1406 1407 final int restoreCount = canvas.save(); 1408 if (drawingContent) { 1409 final int childCount = getChildCount(); 1410 for (int i = 0; i < childCount; i++) { 1411 final View v = getChildAt(i); 1412 if (v == child || v.getVisibility() != VISIBLE 1413 || !hasOpaqueBackground(v) || !isDrawerView(v) 1414 || v.getHeight() < height) { 1415 continue; 1416 } 1417 1418 if (checkDrawerViewAbsoluteGravity(v, Gravity.LEFT)) { 1419 final int vright = v.getRight(); 1420 if (vright > clipLeft) clipLeft = vright; 1421 } else { 1422 final int vleft = v.getLeft(); 1423 if (vleft < clipRight) clipRight = vleft; 1424 } 1425 } 1426 canvas.clipRect(clipLeft, 0, clipRight, getHeight()); 1427 } 1428 final boolean result = super.drawChild(canvas, child, drawingTime); 1429 canvas.restoreToCount(restoreCount); 1430 1431 if (mScrimOpacity > 0 && drawingContent) { 1432 final int baseAlpha = (mScrimColor & 0xff000000) >>> 24; 1433 final int imag = (int) (baseAlpha * mScrimOpacity); 1434 final int color = imag << 24 | (mScrimColor & 0xffffff); 1435 mScrimPaint.setColor(color); 1436 1437 canvas.drawRect(clipLeft, 0, clipRight, getHeight(), mScrimPaint); 1438 } else if (mShadowLeftResolved != null 1439 && checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) { 1440 final int shadowWidth = mShadowLeftResolved.getIntrinsicWidth(); 1441 final int childRight = child.getRight(); 1442 final int drawerPeekDistance = mLeftDragger.getEdgeSize(); 1443 final float alpha = 1444 Math.max(0, Math.min((float) childRight / drawerPeekDistance, 1.f)); 1445 mShadowLeftResolved.setBounds(childRight, child.getTop(), 1446 childRight + shadowWidth, child.getBottom()); 1447 mShadowLeftResolved.setAlpha((int) (0xff * alpha)); 1448 mShadowLeftResolved.draw(canvas); 1449 } else if (mShadowRightResolved != null 1450 && checkDrawerViewAbsoluteGravity(child, Gravity.RIGHT)) { 1451 final int shadowWidth = mShadowRightResolved.getIntrinsicWidth(); 1452 final int childLeft = child.getLeft(); 1453 final int showing = getWidth() - childLeft; 1454 final int drawerPeekDistance = mRightDragger.getEdgeSize(); 1455 final float alpha = 1456 Math.max(0, Math.min((float) showing / drawerPeekDistance, 1.f)); 1457 mShadowRightResolved.setBounds(childLeft - shadowWidth, child.getTop(), 1458 childLeft, child.getBottom()); 1459 mShadowRightResolved.setAlpha((int) (0xff * alpha)); 1460 mShadowRightResolved.draw(canvas); 1461 } 1462 return result; 1463 } 1464 isContentView(View child)1465 boolean isContentView(View child) { 1466 return ((LayoutParams) child.getLayoutParams()).gravity == Gravity.NO_GRAVITY; 1467 } 1468 isDrawerView(View child)1469 boolean isDrawerView(View child) { 1470 final int gravity = ((LayoutParams) child.getLayoutParams()).gravity; 1471 final int absGravity = GravityCompat.getAbsoluteGravity(gravity, 1472 ViewCompat.getLayoutDirection(child)); 1473 if ((absGravity & Gravity.LEFT) != 0) { 1474 // This child is a left-edge drawer 1475 return true; 1476 } 1477 if ((absGravity & Gravity.RIGHT) != 0) { 1478 // This child is a right-edge drawer 1479 return true; 1480 } 1481 return false; 1482 } 1483 1484 @SuppressWarnings("ShortCircuitBoolean") 1485 @Override onInterceptTouchEvent(MotionEvent ev)1486 public boolean onInterceptTouchEvent(MotionEvent ev) { 1487 final int action = ev.getActionMasked(); 1488 1489 // "|" used deliberately here; both methods should be invoked. 1490 final boolean interceptForDrag = mLeftDragger.shouldInterceptTouchEvent(ev) 1491 | mRightDragger.shouldInterceptTouchEvent(ev); 1492 1493 boolean interceptForTap = false; 1494 1495 switch (action) { 1496 case MotionEvent.ACTION_DOWN: { 1497 final float x = ev.getX(); 1498 final float y = ev.getY(); 1499 mInitialMotionX = x; 1500 mInitialMotionY = y; 1501 if (mScrimOpacity > 0) { 1502 final View child = mLeftDragger.findTopChildUnder((int) x, (int) y); 1503 if (child != null && isContentView(child)) { 1504 interceptForTap = true; 1505 } 1506 } 1507 mDisallowInterceptRequested = false; 1508 mChildrenCanceledTouch = false; 1509 break; 1510 } 1511 1512 case MotionEvent.ACTION_MOVE: { 1513 // If we cross the touch slop, don't perform the delayed peek for an edge touch. 1514 if (mLeftDragger.checkTouchSlop(ViewDragHelper.DIRECTION_ALL)) { 1515 mLeftCallback.removeCallbacks(); 1516 mRightCallback.removeCallbacks(); 1517 } 1518 break; 1519 } 1520 1521 case MotionEvent.ACTION_CANCEL: 1522 case MotionEvent.ACTION_UP: { 1523 closeDrawers(true); 1524 mDisallowInterceptRequested = false; 1525 mChildrenCanceledTouch = false; 1526 } 1527 } 1528 1529 return interceptForDrag || interceptForTap || hasPeekingDrawer() || mChildrenCanceledTouch; 1530 } 1531 1532 @Override dispatchGenericMotionEvent(MotionEvent event)1533 public boolean dispatchGenericMotionEvent(MotionEvent event) { 1534 1535 // If this is not a pointer event, or if this is an hover exit, or we are not displaying 1536 // that the content view can't be interacted with, then don't override and do anything 1537 // special. 1538 if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0 1539 || event.getAction() == MotionEvent.ACTION_HOVER_EXIT 1540 || mScrimOpacity <= 0) { 1541 return super.dispatchGenericMotionEvent(event); 1542 } 1543 1544 final int childrenCount = getChildCount(); 1545 if (childrenCount != 0) { 1546 final float x = event.getX(); 1547 final float y = event.getY(); 1548 1549 // Walk through children from top to bottom. 1550 for (int i = childrenCount - 1; i >= 0; i--) { 1551 final View child = getChildAt(i); 1552 1553 // If the event is out of bounds or the child is the content view, don't dispatch 1554 // to it. 1555 if (!isInBoundsOfChild(x, y, child) || isContentView(child)) { 1556 continue; 1557 } 1558 1559 // If a child handles it, return true. 1560 if (dispatchTransformedGenericPointerEvent(event, child)) { 1561 return true; 1562 } 1563 } 1564 } 1565 1566 return false; 1567 } 1568 1569 @Override onTouchEvent(MotionEvent ev)1570 public boolean onTouchEvent(MotionEvent ev) { 1571 mLeftDragger.processTouchEvent(ev); 1572 mRightDragger.processTouchEvent(ev); 1573 1574 final int action = ev.getAction(); 1575 boolean wantTouchEvents = true; 1576 1577 switch (action & MotionEvent.ACTION_MASK) { 1578 case MotionEvent.ACTION_DOWN: { 1579 final float x = ev.getX(); 1580 final float y = ev.getY(); 1581 mInitialMotionX = x; 1582 mInitialMotionY = y; 1583 mDisallowInterceptRequested = false; 1584 mChildrenCanceledTouch = false; 1585 break; 1586 } 1587 1588 case MotionEvent.ACTION_UP: { 1589 final float x = ev.getX(); 1590 final float y = ev.getY(); 1591 boolean peekingOnly = true; 1592 final View touchedView = mLeftDragger.findTopChildUnder((int) x, (int) y); 1593 if (touchedView != null && isContentView(touchedView)) { 1594 final float dx = x - mInitialMotionX; 1595 final float dy = y - mInitialMotionY; 1596 final int slop = mLeftDragger.getTouchSlop(); 1597 if (dx * dx + dy * dy < slop * slop) { 1598 // Taps close a dimmed open drawer but only if it isn't locked open. 1599 final View openDrawer = findOpenDrawer(); 1600 if (openDrawer != null) { 1601 peekingOnly = getDrawerLockMode(openDrawer) == LOCK_MODE_LOCKED_OPEN; 1602 } 1603 } 1604 } 1605 closeDrawers(peekingOnly); 1606 mDisallowInterceptRequested = false; 1607 break; 1608 } 1609 1610 case MotionEvent.ACTION_CANCEL: { 1611 closeDrawers(true); 1612 mDisallowInterceptRequested = false; 1613 mChildrenCanceledTouch = false; 1614 break; 1615 } 1616 } 1617 1618 return wantTouchEvents; 1619 } 1620 1621 @Override requestDisallowInterceptTouchEvent(boolean disallowIntercept)1622 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { 1623 if (CHILDREN_DISALLOW_INTERCEPT 1624 || (!mLeftDragger.isEdgeTouched(ViewDragHelper.EDGE_LEFT) 1625 && !mRightDragger.isEdgeTouched(ViewDragHelper.EDGE_RIGHT))) { 1626 // If we have an edge touch we want to skip this and track it for later instead. 1627 super.requestDisallowInterceptTouchEvent(disallowIntercept); 1628 } 1629 mDisallowInterceptRequested = disallowIntercept; 1630 if (disallowIntercept) { 1631 closeDrawers(true); 1632 } 1633 } 1634 1635 /** 1636 * Close all currently open drawer views by animating them out of view. 1637 */ closeDrawers()1638 public void closeDrawers() { 1639 closeDrawers(false); 1640 } 1641 closeDrawers(boolean peekingOnly)1642 void closeDrawers(boolean peekingOnly) { 1643 boolean needsInvalidate = false; 1644 final int childCount = getChildCount(); 1645 for (int i = 0; i < childCount; i++) { 1646 final View child = getChildAt(i); 1647 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 1648 1649 if (!isDrawerView(child) || (peekingOnly && !lp.isPeeking)) { 1650 continue; 1651 } 1652 1653 final int childWidth = child.getWidth(); 1654 1655 if (checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) { 1656 needsInvalidate |= mLeftDragger.smoothSlideViewTo(child, 1657 -childWidth, child.getTop()); 1658 } else { 1659 needsInvalidate |= mRightDragger.smoothSlideViewTo(child, 1660 getWidth(), child.getTop()); 1661 } 1662 1663 lp.isPeeking = false; 1664 } 1665 1666 mLeftCallback.removeCallbacks(); 1667 mRightCallback.removeCallbacks(); 1668 1669 if (needsInvalidate) { 1670 invalidate(); 1671 } 1672 } 1673 1674 /** 1675 * Open the specified drawer view by animating it into view. 1676 * 1677 * @param drawerView Drawer view to open 1678 */ openDrawer(@onNull View drawerView)1679 public void openDrawer(@NonNull View drawerView) { 1680 openDrawer(drawerView, true); 1681 } 1682 1683 /** 1684 * Open the specified drawer view. 1685 * 1686 * @param drawerView Drawer view to open 1687 * @param animate Whether opening of the drawer should be animated. 1688 */ openDrawer(@onNull View drawerView, boolean animate)1689 public void openDrawer(@NonNull View drawerView, boolean animate) { 1690 if (!isDrawerView(drawerView)) { 1691 throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer"); 1692 } 1693 1694 final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); 1695 if (mFirstLayout) { 1696 lp.onScreen = 1.f; 1697 lp.openState = LayoutParams.FLAG_IS_OPENED; 1698 1699 updateChildrenImportantForAccessibility(drawerView, true); 1700 } else if (animate) { 1701 lp.openState |= LayoutParams.FLAG_IS_OPENING; 1702 1703 if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) { 1704 mLeftDragger.smoothSlideViewTo(drawerView, 0, drawerView.getTop()); 1705 } else { 1706 mRightDragger.smoothSlideViewTo(drawerView, getWidth() - drawerView.getWidth(), 1707 drawerView.getTop()); 1708 } 1709 } else { 1710 moveDrawerToOffset(drawerView, 1.f); 1711 updateDrawerState(lp.gravity, STATE_IDLE, drawerView); 1712 drawerView.setVisibility(VISIBLE); 1713 } 1714 invalidate(); 1715 } 1716 1717 /** 1718 * Open the specified drawer by animating it out of view. 1719 * 1720 * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right. 1721 * GravityCompat.START or GravityCompat.END may also be used. 1722 */ openDrawer(@dgeGravity int gravity)1723 public void openDrawer(@EdgeGravity int gravity) { 1724 openDrawer(gravity, true); 1725 } 1726 1727 /** 1728 * Open the specified drawer. 1729 * 1730 * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right. 1731 * GravityCompat.START or GravityCompat.END may also be used. 1732 * @param animate Whether opening of the drawer should be animated. 1733 */ openDrawer(@dgeGravity int gravity, boolean animate)1734 public void openDrawer(@EdgeGravity int gravity, boolean animate) { 1735 final View drawerView = findDrawerWithGravity(gravity); 1736 if (drawerView == null) { 1737 throw new IllegalArgumentException("No drawer view found with gravity " 1738 + gravityToString(gravity)); 1739 } 1740 openDrawer(drawerView, animate); 1741 } 1742 1743 /** 1744 * Close the specified drawer view by animating it into view. 1745 * 1746 * @param drawerView Drawer view to close 1747 */ closeDrawer(@onNull View drawerView)1748 public void closeDrawer(@NonNull View drawerView) { 1749 closeDrawer(drawerView, true); 1750 } 1751 1752 /** 1753 * Close the specified drawer view. 1754 * 1755 * @param drawerView Drawer view to close 1756 * @param animate Whether closing of the drawer should be animated. 1757 */ closeDrawer(@onNull View drawerView, boolean animate)1758 public void closeDrawer(@NonNull View drawerView, boolean animate) { 1759 if (!isDrawerView(drawerView)) { 1760 throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer"); 1761 } 1762 1763 final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); 1764 if (mFirstLayout) { 1765 lp.onScreen = 0.f; 1766 lp.openState = 0; 1767 } else if (animate) { 1768 lp.openState |= LayoutParams.FLAG_IS_CLOSING; 1769 1770 if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) { 1771 mLeftDragger.smoothSlideViewTo(drawerView, -drawerView.getWidth(), 1772 drawerView.getTop()); 1773 } else { 1774 mRightDragger.smoothSlideViewTo(drawerView, getWidth(), drawerView.getTop()); 1775 } 1776 } else { 1777 moveDrawerToOffset(drawerView, 0.f); 1778 updateDrawerState(lp.gravity, STATE_IDLE, drawerView); 1779 drawerView.setVisibility(INVISIBLE); 1780 } 1781 invalidate(); 1782 } 1783 1784 /** 1785 * Close the specified drawer by animating it out of view. 1786 * 1787 * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right. 1788 * GravityCompat.START or GravityCompat.END may also be used. 1789 */ closeDrawer(@dgeGravity int gravity)1790 public void closeDrawer(@EdgeGravity int gravity) { 1791 closeDrawer(gravity, true); 1792 } 1793 1794 /** 1795 * Close the specified drawer. 1796 * 1797 * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right. 1798 * GravityCompat.START or GravityCompat.END may also be used. 1799 * @param animate Whether closing of the drawer should be animated. 1800 */ closeDrawer(@dgeGravity int gravity, boolean animate)1801 public void closeDrawer(@EdgeGravity int gravity, boolean animate) { 1802 final View drawerView = findDrawerWithGravity(gravity); 1803 if (drawerView == null) { 1804 throw new IllegalArgumentException("No drawer view found with gravity " 1805 + gravityToString(gravity)); 1806 } 1807 closeDrawer(drawerView, animate); 1808 } 1809 1810 /** 1811 * Check if the given drawer view is currently in an open state. 1812 * To be considered "open" the drawer must have settled into its fully 1813 * visible state. To check for partial visibility use 1814 * {@link #isDrawerVisible(android.view.View)}. 1815 * 1816 * @param drawer Drawer view to check 1817 * @return true if the given drawer view is in an open state 1818 * @see #isDrawerVisible(android.view.View) 1819 */ isDrawerOpen(@onNull View drawer)1820 public boolean isDrawerOpen(@NonNull View drawer) { 1821 if (!isDrawerView(drawer)) { 1822 throw new IllegalArgumentException("View " + drawer + " is not a drawer"); 1823 } 1824 LayoutParams drawerLp = (LayoutParams) drawer.getLayoutParams(); 1825 return (drawerLp.openState & LayoutParams.FLAG_IS_OPENED) == 1; 1826 } 1827 1828 /** 1829 * Check if the given drawer view is currently in an open state. 1830 * To be considered "open" the drawer must have settled into its fully 1831 * visible state. If there is no drawer with the given gravity this method 1832 * will return false. 1833 * 1834 * @param drawerGravity Gravity of the drawer to check 1835 * @return true if the given drawer view is in an open state 1836 */ isDrawerOpen(@dgeGravity int drawerGravity)1837 public boolean isDrawerOpen(@EdgeGravity int drawerGravity) { 1838 final View drawerView = findDrawerWithGravity(drawerGravity); 1839 if (drawerView != null) { 1840 return isDrawerOpen(drawerView); 1841 } 1842 return false; 1843 } 1844 1845 /** 1846 * Check if a given drawer view is currently visible on-screen. The drawer 1847 * may be only peeking onto the screen, fully extended, or anywhere inbetween. 1848 * 1849 * @param drawer Drawer view to check 1850 * @return true if the given drawer is visible on-screen 1851 * @see #isDrawerOpen(android.view.View) 1852 */ isDrawerVisible(@onNull View drawer)1853 public boolean isDrawerVisible(@NonNull View drawer) { 1854 if (!isDrawerView(drawer)) { 1855 throw new IllegalArgumentException("View " + drawer + " is not a drawer"); 1856 } 1857 return ((LayoutParams) drawer.getLayoutParams()).onScreen > 0; 1858 } 1859 1860 /** 1861 * Check if a given drawer view is currently visible on-screen. The drawer 1862 * may be only peeking onto the screen, fully extended, or anywhere in between. 1863 * If there is no drawer with the given gravity this method will return false. 1864 * 1865 * @param drawerGravity Gravity of the drawer to check 1866 * @return true if the given drawer is visible on-screen 1867 */ isDrawerVisible(@dgeGravity int drawerGravity)1868 public boolean isDrawerVisible(@EdgeGravity int drawerGravity) { 1869 final View drawerView = findDrawerWithGravity(drawerGravity); 1870 if (drawerView != null) { 1871 return isDrawerVisible(drawerView); 1872 } 1873 return false; 1874 } 1875 hasPeekingDrawer()1876 private boolean hasPeekingDrawer() { 1877 final int childCount = getChildCount(); 1878 for (int i = 0; i < childCount; i++) { 1879 final LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams(); 1880 if (lp.isPeeking) { 1881 return true; 1882 } 1883 } 1884 return false; 1885 } 1886 1887 @Override generateDefaultLayoutParams()1888 protected ViewGroup.LayoutParams generateDefaultLayoutParams() { 1889 return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 1890 } 1891 1892 @Override generateLayoutParams(ViewGroup.LayoutParams p)1893 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 1894 return p instanceof LayoutParams 1895 ? new LayoutParams((LayoutParams) p) 1896 : p instanceof ViewGroup.MarginLayoutParams 1897 ? new LayoutParams((MarginLayoutParams) p) 1898 : new LayoutParams(p); 1899 } 1900 1901 @Override checkLayoutParams(ViewGroup.LayoutParams p)1902 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 1903 return p instanceof LayoutParams && super.checkLayoutParams(p); 1904 } 1905 1906 @Override generateLayoutParams(AttributeSet attrs)1907 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { 1908 return new LayoutParams(getContext(), attrs); 1909 } 1910 1911 @Override addFocusables(ArrayList<View> views, int direction, int focusableMode)1912 public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { 1913 if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) { 1914 return; 1915 } 1916 1917 // Only the views in the open drawers are focusables. Add normal child views when 1918 // no drawers are opened. 1919 final int childCount = getChildCount(); 1920 boolean isDrawerOpen = false; 1921 for (int i = 0; i < childCount; i++) { 1922 final View child = getChildAt(i); 1923 if (isDrawerView(child)) { 1924 if (isDrawerOpen(child)) { 1925 isDrawerOpen = true; 1926 child.addFocusables(views, direction, focusableMode); 1927 } 1928 } else { 1929 mNonDrawerViews.add(child); 1930 } 1931 } 1932 1933 if (!isDrawerOpen) { 1934 final int nonDrawerViewsCount = mNonDrawerViews.size(); 1935 for (int i = 0; i < nonDrawerViewsCount; ++i) { 1936 final View child = mNonDrawerViews.get(i); 1937 if (child.getVisibility() == View.VISIBLE) { 1938 child.addFocusables(views, direction, focusableMode); 1939 } 1940 } 1941 } 1942 1943 mNonDrawerViews.clear(); 1944 } 1945 hasVisibleDrawer()1946 private boolean hasVisibleDrawer() { 1947 return findVisibleDrawer() != null; 1948 } 1949 findVisibleDrawer()1950 View findVisibleDrawer() { 1951 final int childCount = getChildCount(); 1952 for (int i = 0; i < childCount; i++) { 1953 final View child = getChildAt(i); 1954 if (isDrawerView(child) && isDrawerVisible(child)) { 1955 return child; 1956 } 1957 } 1958 return null; 1959 } 1960 cancelChildViewTouch()1961 void cancelChildViewTouch() { 1962 // Cancel child touches 1963 if (!mChildrenCanceledTouch) { 1964 final long now = SystemClock.uptimeMillis(); 1965 final MotionEvent cancelEvent = MotionEvent.obtain(now, now, 1966 MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0); 1967 final int childCount = getChildCount(); 1968 for (int i = 0; i < childCount; i++) { 1969 getChildAt(i).dispatchTouchEvent(cancelEvent); 1970 } 1971 cancelEvent.recycle(); 1972 mChildrenCanceledTouch = true; 1973 } 1974 } 1975 1976 @Override onKeyDown(int keyCode, KeyEvent event)1977 public boolean onKeyDown(int keyCode, KeyEvent event) { 1978 if (keyCode == KeyEvent.KEYCODE_BACK && hasVisibleDrawer()) { 1979 event.startTracking(); 1980 return true; 1981 } 1982 return super.onKeyDown(keyCode, event); 1983 } 1984 1985 @Override onKeyUp(int keyCode, KeyEvent event)1986 public boolean onKeyUp(int keyCode, KeyEvent event) { 1987 if (keyCode == KeyEvent.KEYCODE_BACK) { 1988 final View visibleDrawer = findVisibleDrawer(); 1989 if (visibleDrawer != null && getDrawerLockMode(visibleDrawer) == LOCK_MODE_UNLOCKED) { 1990 closeDrawers(); 1991 } 1992 return visibleDrawer != null; 1993 } 1994 return super.onKeyUp(keyCode, event); 1995 } 1996 1997 @Override onRestoreInstanceState(Parcelable state)1998 protected void onRestoreInstanceState(Parcelable state) { 1999 if (!(state instanceof SavedState)) { 2000 super.onRestoreInstanceState(state); 2001 return; 2002 } 2003 2004 final SavedState ss = (SavedState) state; 2005 super.onRestoreInstanceState(ss.getSuperState()); 2006 2007 if (ss.openDrawerGravity != Gravity.NO_GRAVITY) { 2008 final View toOpen = findDrawerWithGravity(ss.openDrawerGravity); 2009 if (toOpen != null) { 2010 openDrawer(toOpen); 2011 } 2012 } 2013 2014 if (ss.lockModeLeft != LOCK_MODE_UNDEFINED) { 2015 setDrawerLockMode(ss.lockModeLeft, Gravity.LEFT); 2016 } 2017 if (ss.lockModeRight != LOCK_MODE_UNDEFINED) { 2018 setDrawerLockMode(ss.lockModeRight, Gravity.RIGHT); 2019 } 2020 if (ss.lockModeStart != LOCK_MODE_UNDEFINED) { 2021 setDrawerLockMode(ss.lockModeStart, GravityCompat.START); 2022 } 2023 if (ss.lockModeEnd != LOCK_MODE_UNDEFINED) { 2024 setDrawerLockMode(ss.lockModeEnd, GravityCompat.END); 2025 } 2026 } 2027 2028 @Override onSaveInstanceState()2029 protected Parcelable onSaveInstanceState() { 2030 final Parcelable superState = super.onSaveInstanceState(); 2031 final SavedState ss = new SavedState(superState); 2032 2033 final int childCount = getChildCount(); 2034 for (int i = 0; i < childCount; i++) { 2035 final View child = getChildAt(i); 2036 LayoutParams lp = (LayoutParams) child.getLayoutParams(); 2037 // Is the current child fully opened (that is, not closing)? 2038 boolean isOpenedAndNotClosing = (lp.openState == LayoutParams.FLAG_IS_OPENED); 2039 // Is the current child opening? 2040 boolean isClosedAndOpening = (lp.openState == LayoutParams.FLAG_IS_OPENING); 2041 if (isOpenedAndNotClosing || isClosedAndOpening) { 2042 // If one of the conditions above holds, save the child's gravity 2043 // so that we open that child during state restore. 2044 ss.openDrawerGravity = lp.gravity; 2045 break; 2046 } 2047 } 2048 2049 ss.lockModeLeft = mLockModeLeft; 2050 ss.lockModeRight = mLockModeRight; 2051 ss.lockModeStart = mLockModeStart; 2052 ss.lockModeEnd = mLockModeEnd; 2053 2054 return ss; 2055 } 2056 2057 @Override addView(View child, int index, ViewGroup.LayoutParams params)2058 public void addView(View child, int index, ViewGroup.LayoutParams params) { 2059 super.addView(child, index, params); 2060 2061 final View openDrawer = findOpenDrawer(); 2062 if (openDrawer != null || isDrawerView(child)) { 2063 // A drawer is already open or the new view is a drawer, so the 2064 // new view should start out hidden. 2065 ViewCompat.setImportantForAccessibility(child, 2066 ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); 2067 } else { 2068 // Otherwise this is a content view and no drawer is open, so the 2069 // new view should start out visible. 2070 ViewCompat.setImportantForAccessibility(child, 2071 ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES); 2072 } 2073 2074 // We only need a delegate here if the framework doesn't understand 2075 // NO_HIDE_DESCENDANTS importance. 2076 if (!CAN_HIDE_DESCENDANTS) { 2077 ViewCompat.setAccessibilityDelegate(child, mChildAccessibilityDelegate); 2078 } 2079 } 2080 includeChildForAccessibility(View child)2081 static boolean includeChildForAccessibility(View child) { 2082 // If the child is not important for accessibility we make 2083 // sure this hides the entire subtree rooted at it as the 2084 // IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDATS is not 2085 // supported on older platforms but we want to hide the entire 2086 // content and not opened drawers if a drawer is opened. 2087 return ViewCompat.getImportantForAccessibility(child) 2088 != ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS 2089 && ViewCompat.getImportantForAccessibility(child) 2090 != ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO; 2091 } 2092 2093 /** 2094 * State persisted across instances 2095 */ 2096 protected static class SavedState extends AbsSavedState { 2097 int openDrawerGravity = Gravity.NO_GRAVITY; 2098 @LockMode int lockModeLeft; 2099 @LockMode int lockModeRight; 2100 @LockMode int lockModeStart; 2101 @LockMode int lockModeEnd; 2102 SavedState(@onNull Parcel in, @Nullable ClassLoader loader)2103 public SavedState(@NonNull Parcel in, @Nullable ClassLoader loader) { 2104 super(in, loader); 2105 openDrawerGravity = in.readInt(); 2106 lockModeLeft = in.readInt(); 2107 lockModeRight = in.readInt(); 2108 lockModeStart = in.readInt(); 2109 lockModeEnd = in.readInt(); 2110 } 2111 SavedState(@onNull Parcelable superState)2112 public SavedState(@NonNull Parcelable superState) { 2113 super(superState); 2114 } 2115 2116 @Override writeToParcel(Parcel dest, int flags)2117 public void writeToParcel(Parcel dest, int flags) { 2118 super.writeToParcel(dest, flags); 2119 dest.writeInt(openDrawerGravity); 2120 dest.writeInt(lockModeLeft); 2121 dest.writeInt(lockModeRight); 2122 dest.writeInt(lockModeStart); 2123 dest.writeInt(lockModeEnd); 2124 } 2125 2126 public static final Creator<SavedState> CREATOR = new ClassLoaderCreator<SavedState>() { 2127 @Override 2128 public SavedState createFromParcel(Parcel in, ClassLoader loader) { 2129 return new SavedState(in, loader); 2130 } 2131 2132 @Override 2133 public SavedState createFromParcel(Parcel in) { 2134 return new SavedState(in, null); 2135 } 2136 2137 @Override 2138 public SavedState[] newArray(int size) { 2139 return new SavedState[size]; 2140 } 2141 }; 2142 } 2143 2144 private class ViewDragCallback extends ViewDragHelper.Callback { 2145 private final int mAbsGravity; 2146 private ViewDragHelper mDragger; 2147 2148 private final Runnable mPeekRunnable = new Runnable() { 2149 @Override public void run() { 2150 peekDrawer(); 2151 } 2152 }; 2153 ViewDragCallback(int gravity)2154 ViewDragCallback(int gravity) { 2155 mAbsGravity = gravity; 2156 } 2157 setDragger(ViewDragHelper dragger)2158 public void setDragger(ViewDragHelper dragger) { 2159 mDragger = dragger; 2160 } 2161 removeCallbacks()2162 public void removeCallbacks() { 2163 DrawerLayout.this.removeCallbacks(mPeekRunnable); 2164 } 2165 2166 @Override tryCaptureView(View child, int pointerId)2167 public boolean tryCaptureView(View child, int pointerId) { 2168 // Only capture views where the gravity matches what we're looking for. 2169 // This lets us use two ViewDragHelpers, one for each side drawer. 2170 return isDrawerView(child) && checkDrawerViewAbsoluteGravity(child, mAbsGravity) 2171 && getDrawerLockMode(child) == LOCK_MODE_UNLOCKED; 2172 } 2173 2174 @Override onViewDragStateChanged(int state)2175 public void onViewDragStateChanged(int state) { 2176 updateDrawerState(mAbsGravity, state, mDragger.getCapturedView()); 2177 } 2178 2179 @Override onViewPositionChanged(View changedView, int left, int top, int dx, int dy)2180 public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { 2181 float offset; 2182 final int childWidth = changedView.getWidth(); 2183 2184 // This reverses the positioning shown in onLayout. 2185 if (checkDrawerViewAbsoluteGravity(changedView, Gravity.LEFT)) { 2186 offset = (float) (childWidth + left) / childWidth; 2187 } else { 2188 final int width = getWidth(); 2189 offset = (float) (width - left) / childWidth; 2190 } 2191 setDrawerViewOffset(changedView, offset); 2192 changedView.setVisibility(offset == 0 ? INVISIBLE : VISIBLE); 2193 invalidate(); 2194 } 2195 2196 @Override onViewCaptured(View capturedChild, int activePointerId)2197 public void onViewCaptured(View capturedChild, int activePointerId) { 2198 final LayoutParams lp = (LayoutParams) capturedChild.getLayoutParams(); 2199 lp.isPeeking = false; 2200 2201 closeOtherDrawer(); 2202 } 2203 closeOtherDrawer()2204 private void closeOtherDrawer() { 2205 final int otherGrav = mAbsGravity == Gravity.LEFT ? Gravity.RIGHT : Gravity.LEFT; 2206 final View toClose = findDrawerWithGravity(otherGrav); 2207 if (toClose != null) { 2208 closeDrawer(toClose); 2209 } 2210 } 2211 2212 @Override onViewReleased(View releasedChild, float xvel, float yvel)2213 public void onViewReleased(View releasedChild, float xvel, float yvel) { 2214 // Offset is how open the drawer is, therefore left/right values 2215 // are reversed from one another. 2216 final float offset = getDrawerViewOffset(releasedChild); 2217 final int childWidth = releasedChild.getWidth(); 2218 2219 int left; 2220 if (checkDrawerViewAbsoluteGravity(releasedChild, Gravity.LEFT)) { 2221 left = xvel > 0 || (xvel == 0 && offset > 0.5f) ? 0 : -childWidth; 2222 } else { 2223 final int width = getWidth(); 2224 left = xvel < 0 || (xvel == 0 && offset > 0.5f) ? width - childWidth : width; 2225 } 2226 2227 mDragger.settleCapturedViewAt(left, releasedChild.getTop()); 2228 invalidate(); 2229 } 2230 2231 @Override onEdgeTouched(int edgeFlags, int pointerId)2232 public void onEdgeTouched(int edgeFlags, int pointerId) { 2233 postDelayed(mPeekRunnable, PEEK_DELAY); 2234 } 2235 peekDrawer()2236 void peekDrawer() { 2237 final View toCapture; 2238 final int childLeft; 2239 final int peekDistance = mDragger.getEdgeSize(); 2240 final boolean leftEdge = mAbsGravity == Gravity.LEFT; 2241 if (leftEdge) { 2242 toCapture = findDrawerWithGravity(Gravity.LEFT); 2243 childLeft = (toCapture != null ? -toCapture.getWidth() : 0) + peekDistance; 2244 } else { 2245 toCapture = findDrawerWithGravity(Gravity.RIGHT); 2246 childLeft = getWidth() - peekDistance; 2247 } 2248 // Only peek if it would mean making the drawer more visible and the drawer isn't locked 2249 if (toCapture != null && ((leftEdge && toCapture.getLeft() < childLeft) 2250 || (!leftEdge && toCapture.getLeft() > childLeft)) 2251 && getDrawerLockMode(toCapture) == LOCK_MODE_UNLOCKED) { 2252 final LayoutParams lp = (LayoutParams) toCapture.getLayoutParams(); 2253 mDragger.smoothSlideViewTo(toCapture, childLeft, toCapture.getTop()); 2254 lp.isPeeking = true; 2255 invalidate(); 2256 2257 closeOtherDrawer(); 2258 2259 cancelChildViewTouch(); 2260 } 2261 } 2262 2263 @Override onEdgeLock(int edgeFlags)2264 public boolean onEdgeLock(int edgeFlags) { 2265 if (ALLOW_EDGE_LOCK) { 2266 final View drawer = findDrawerWithGravity(mAbsGravity); 2267 if (drawer != null && !isDrawerOpen(drawer)) { 2268 closeDrawer(drawer); 2269 } 2270 return true; 2271 } 2272 return false; 2273 } 2274 2275 @Override onEdgeDragStarted(int edgeFlags, int pointerId)2276 public void onEdgeDragStarted(int edgeFlags, int pointerId) { 2277 final View toCapture; 2278 if ((edgeFlags & ViewDragHelper.EDGE_LEFT) == ViewDragHelper.EDGE_LEFT) { 2279 toCapture = findDrawerWithGravity(Gravity.LEFT); 2280 } else { 2281 toCapture = findDrawerWithGravity(Gravity.RIGHT); 2282 } 2283 2284 if (toCapture != null && getDrawerLockMode(toCapture) == LOCK_MODE_UNLOCKED) { 2285 mDragger.captureChildView(toCapture, pointerId); 2286 } 2287 } 2288 2289 @Override getViewHorizontalDragRange(View child)2290 public int getViewHorizontalDragRange(View child) { 2291 return isDrawerView(child) ? child.getWidth() : 0; 2292 } 2293 2294 @Override clampViewPositionHorizontal(View child, int left, int dx)2295 public int clampViewPositionHorizontal(View child, int left, int dx) { 2296 if (checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) { 2297 return Math.max(-child.getWidth(), Math.min(left, 0)); 2298 } else { 2299 final int width = getWidth(); 2300 return Math.max(width - child.getWidth(), Math.min(left, width)); 2301 } 2302 } 2303 2304 @Override clampViewPositionVertical(View child, int top, int dy)2305 public int clampViewPositionVertical(View child, int top, int dy) { 2306 return child.getTop(); 2307 } 2308 } 2309 2310 public static class LayoutParams extends ViewGroup.MarginLayoutParams { 2311 private static final int FLAG_IS_OPENED = 0x1; 2312 private static final int FLAG_IS_OPENING = 0x2; 2313 private static final int FLAG_IS_CLOSING = 0x4; 2314 2315 public int gravity = Gravity.NO_GRAVITY; 2316 float onScreen; 2317 boolean isPeeking; 2318 int openState; 2319 LayoutParams(@onNull Context c, @Nullable AttributeSet attrs)2320 public LayoutParams(@NonNull Context c, @Nullable AttributeSet attrs) { 2321 super(c, attrs); 2322 2323 final TypedArray a = c.obtainStyledAttributes(attrs, LAYOUT_ATTRS); 2324 this.gravity = a.getInt(0, Gravity.NO_GRAVITY); 2325 a.recycle(); 2326 } 2327 LayoutParams(int width, int height)2328 public LayoutParams(int width, int height) { 2329 super(width, height); 2330 } 2331 LayoutParams(int width, int height, int gravity)2332 public LayoutParams(int width, int height, int gravity) { 2333 this(width, height); 2334 this.gravity = gravity; 2335 } 2336 LayoutParams(@onNull LayoutParams source)2337 public LayoutParams(@NonNull LayoutParams source) { 2338 super(source); 2339 this.gravity = source.gravity; 2340 } 2341 LayoutParams(@onNull ViewGroup.LayoutParams source)2342 public LayoutParams(@NonNull ViewGroup.LayoutParams source) { 2343 super(source); 2344 } 2345 LayoutParams(@onNull ViewGroup.MarginLayoutParams source)2346 public LayoutParams(@NonNull ViewGroup.MarginLayoutParams source) { 2347 super(source); 2348 } 2349 } 2350 2351 class AccessibilityDelegate extends AccessibilityDelegateCompat { 2352 private final Rect mTmpRect = new Rect(); 2353 2354 @Override onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info)2355 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) { 2356 if (CAN_HIDE_DESCENDANTS) { 2357 super.onInitializeAccessibilityNodeInfo(host, info); 2358 } else { 2359 // Obtain a node for the host, then manually generate the list 2360 // of children to only include non-obscured views. 2361 final AccessibilityNodeInfoCompat superNode = 2362 AccessibilityNodeInfoCompat.obtain(info); 2363 super.onInitializeAccessibilityNodeInfo(host, superNode); 2364 2365 info.setSource(host); 2366 final ViewParent parent = ViewCompat.getParentForAccessibility(host); 2367 if (parent instanceof View) { 2368 info.setParent((View) parent); 2369 } 2370 copyNodeInfoNoChildren(info, superNode); 2371 superNode.recycle(); 2372 2373 addChildrenForAccessibility(info, (ViewGroup) host); 2374 } 2375 2376 info.setClassName(DrawerLayout.class.getName()); 2377 2378 // This view reports itself as focusable so that it can intercept 2379 // the back button, but we should prevent this view from reporting 2380 // itself as focusable to accessibility services. 2381 info.setFocusable(false); 2382 info.setFocused(false); 2383 info.removeAction(AccessibilityActionCompat.ACTION_FOCUS); 2384 info.removeAction(AccessibilityActionCompat.ACTION_CLEAR_FOCUS); 2385 } 2386 2387 @Override onInitializeAccessibilityEvent(View host, AccessibilityEvent event)2388 public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) { 2389 super.onInitializeAccessibilityEvent(host, event); 2390 2391 event.setClassName(DrawerLayout.class.getName()); 2392 } 2393 2394 @Override dispatchPopulateAccessibilityEvent(View host, AccessibilityEvent event)2395 public boolean dispatchPopulateAccessibilityEvent(View host, AccessibilityEvent event) { 2396 // Special case to handle window state change events. As far as 2397 // accessibility services are concerned, state changes from 2398 // DrawerLayout invalidate the entire contents of the screen (like 2399 // an Activity or Dialog) and they should announce the title of the 2400 // new content. 2401 if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { 2402 final List<CharSequence> eventText = event.getText(); 2403 final View visibleDrawer = findVisibleDrawer(); 2404 if (visibleDrawer != null) { 2405 final int edgeGravity = getDrawerViewAbsoluteGravity(visibleDrawer); 2406 final CharSequence title = getDrawerTitle(edgeGravity); 2407 if (title != null) { 2408 eventText.add(title); 2409 } 2410 } 2411 2412 return true; 2413 } 2414 2415 return super.dispatchPopulateAccessibilityEvent(host, event); 2416 } 2417 2418 @Override onRequestSendAccessibilityEvent(ViewGroup host, View child, AccessibilityEvent event)2419 public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child, 2420 AccessibilityEvent event) { 2421 if (CAN_HIDE_DESCENDANTS || includeChildForAccessibility(child)) { 2422 return super.onRequestSendAccessibilityEvent(host, child, event); 2423 } 2424 return false; 2425 } 2426 addChildrenForAccessibility(AccessibilityNodeInfoCompat info, ViewGroup v)2427 private void addChildrenForAccessibility(AccessibilityNodeInfoCompat info, ViewGroup v) { 2428 final int childCount = v.getChildCount(); 2429 for (int i = 0; i < childCount; i++) { 2430 final View child = v.getChildAt(i); 2431 if (includeChildForAccessibility(child)) { 2432 info.addChild(child); 2433 } 2434 } 2435 } 2436 2437 /** 2438 * This should really be in AccessibilityNodeInfoCompat, but there unfortunately 2439 * seem to be a few elements that are not easily cloneable using the underlying API. 2440 * Leave it private here as it's not general-purpose useful. 2441 */ copyNodeInfoNoChildren(AccessibilityNodeInfoCompat dest, AccessibilityNodeInfoCompat src)2442 private void copyNodeInfoNoChildren(AccessibilityNodeInfoCompat dest, 2443 AccessibilityNodeInfoCompat src) { 2444 final Rect rect = mTmpRect; 2445 2446 src.getBoundsInParent(rect); 2447 dest.setBoundsInParent(rect); 2448 2449 src.getBoundsInScreen(rect); 2450 dest.setBoundsInScreen(rect); 2451 2452 dest.setVisibleToUser(src.isVisibleToUser()); 2453 dest.setPackageName(src.getPackageName()); 2454 dest.setClassName(src.getClassName()); 2455 dest.setContentDescription(src.getContentDescription()); 2456 2457 dest.setEnabled(src.isEnabled()); 2458 dest.setClickable(src.isClickable()); 2459 dest.setFocusable(src.isFocusable()); 2460 dest.setFocused(src.isFocused()); 2461 dest.setAccessibilityFocused(src.isAccessibilityFocused()); 2462 dest.setSelected(src.isSelected()); 2463 dest.setLongClickable(src.isLongClickable()); 2464 2465 dest.addAction(src.getActions()); 2466 } 2467 } 2468 2469 static final class ChildAccessibilityDelegate extends AccessibilityDelegateCompat { 2470 @Override onInitializeAccessibilityNodeInfo(View child, AccessibilityNodeInfoCompat info)2471 public void onInitializeAccessibilityNodeInfo(View child, 2472 AccessibilityNodeInfoCompat info) { 2473 super.onInitializeAccessibilityNodeInfo(child, info); 2474 2475 if (!includeChildForAccessibility(child)) { 2476 // If we are ignoring the sub-tree rooted at the child, 2477 // break the connection to the rest of the node tree. 2478 // For details refer to includeChildForAccessibility. 2479 info.setParent(null); 2480 } 2481 } 2482 } 2483 } 2484