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