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