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