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