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