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