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