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