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