1 /* 2 * Copyright (C) 2015 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 package android.car.ui.provider; 17 18 import android.content.Context; 19 import android.content.res.Resources; 20 import android.content.res.TypedArray; 21 import android.graphics.Canvas; 22 import android.graphics.Color; 23 import android.graphics.Paint; 24 import android.graphics.PixelFormat; 25 import android.graphics.Rect; 26 import android.graphics.drawable.Drawable; 27 import android.os.Handler; 28 import android.os.Parcel; 29 import android.os.Parcelable; 30 import android.support.annotation.NonNull; 31 import android.support.car.ui.CarUiResourceLoader; 32 import android.support.car.ui.QuantumInterpolator; 33 import android.support.car.ui.R; 34 import android.support.car.ui.ReversibleInterpolator; 35 import android.support.v4.view.GravityCompat; 36 import android.support.v4.view.MotionEventCompat; 37 import android.support.v4.view.ViewCompat; 38 import android.support.v4.view.ViewGroupCompat; 39 import android.support.v4.widget.ViewDragHelper; 40 import android.util.AttributeSet; 41 import android.view.Gravity; 42 import android.view.KeyEvent; 43 import android.view.MotionEvent; 44 import android.view.View; 45 import android.view.ViewGroup; 46 import android.widget.FrameLayout; 47 48 import java.util.ArrayList; 49 import java.util.HashSet; 50 import java.util.Iterator; 51 import java.util.List; 52 import java.util.Set; 53 54 /** 55 * Acts as a top-level container for window content that allows for 56 * interactive "drawer" views to be pulled out from the edge of the window. 57 * 58 * <p>Drawer positioning and layout is controlled using the <code>android:layout_gravity</code> 59 * attribute on child views corresponding to which side of the view you want the drawer 60 * to emerge from: left or right. (Or start/end on platform versions that support layout direction.) 61 * </p> 62 * 63 * <p> To use CarDrawerLayout, add your drawer view as the first view in the CarDrawerLayout 64 * element and set the <code>layout_gravity</code> appropriately. Drawers commonly use 65 * <code>match_parent</code> for height with a fixed width. Add the content views as sibling views 66 * after the drawer view.</p> 67 * 68 * <p>{@link DrawerListener} can be used to monitor the state and motion of drawer views. 69 * Avoid performing expensive operations such as layout during animation as it can cause 70 * stuttering; try to perform expensive operations during the {@link #STATE_IDLE} state. 71 * {@link SimpleDrawerListener} offers default/no-op implementations of each callback method.</p> 72 */ 73 public class CarDrawerLayout extends ViewGroup { 74 /** 75 * Indicates that any drawers are in an idle, settled state. No animation is in progress. 76 */ 77 public static final int STATE_IDLE = ViewDragHelper.STATE_IDLE; 78 79 /** 80 * The drawer is unlocked. 81 */ 82 public static final int LOCK_MODE_UNLOCKED = 0; 83 84 /** 85 * The drawer is locked closed. The user may not open it, though 86 * the app may open it programmatically. 87 */ 88 public static final int LOCK_MODE_LOCKED_CLOSED = 1; 89 90 /** 91 * The drawer is locked open. The user may not close it, though the app 92 * may close it programmatically. 93 */ 94 public static final int LOCK_MODE_LOCKED_OPEN = 2; 95 96 private static final float MAX_SCRIM_ALPHA = 0.8f; 97 98 private static final boolean SCRIM_ENABLED = true; 99 100 private static final boolean SHADOW_ENABLED = true; 101 102 /** 103 * Minimum velocity that will be detected as a fling 104 */ 105 private static final int MIN_FLING_VELOCITY = 400; // dips per second 106 107 /** 108 * Experimental feature. 109 */ 110 private static final boolean ALLOW_EDGE_LOCK = false; 111 112 private static final boolean EDGE_DRAG_ENABLED = false; 113 114 private static final boolean CHILDREN_DISALLOW_INTERCEPT = true; 115 116 private static final float TOUCH_SLOP_SENSITIVITY = 1.f; 117 118 private static final int[] LAYOUT_ATTRS = new int[] { 119 android.R.attr.layout_gravity 120 }; 121 122 public static final int DEFAULT_SCRIM_COLOR = 0xff262626; 123 124 private int mScrimColor = DEFAULT_SCRIM_COLOR; 125 private final Paint mScrimPaint = new Paint(); 126 private final Paint mEdgeHighlightPaint = new Paint(); 127 128 private final ViewDragHelper mDragger; 129 130 private final Runnable mInvalidateRunnable = new Runnable() { 131 @Override 132 public void run() { 133 requestLayout(); 134 invalidate(); 135 } 136 }; 137 138 // view faders who will be given different colors as the drawer opens 139 private final Set<ViewFaderHolder> mViewFaders; 140 private final ReversibleInterpolator mViewFaderInterpolator; 141 private final ReversibleInterpolator mDrawerFadeInterpolator; 142 private final Handler mHandler = new Handler(); 143 144 private int mEndingViewColor; 145 private int mStartingViewColor; 146 private int mDrawerState; 147 private boolean mInLayout; 148 /** Whether we have done a layout yet. Used to initialize some view-related state. */ 149 private boolean mFirstLayout = true; 150 private boolean mHasInflated; 151 private int mLockModeLeft; 152 private int mLockModeRight; 153 private boolean mChildrenCanceledTouch; 154 private DrawerListener mDrawerListener; 155 private DrawerControllerListener mDrawerControllerListener; 156 private Drawable mShadow; 157 private View mDrawerView; 158 private View mContentView; 159 private boolean mNeedsFocus; 160 /** Whether or not the drawer started open for the current gesture */ 161 private boolean mStartedOpen; 162 private boolean mHasWheel; 163 164 /** 165 * Listener for monitoring events about drawers. 166 */ 167 public interface DrawerListener { 168 /** 169 * Called when a drawer's position changes. 170 * @param drawerView The child view that was moved 171 * @param slideOffset The new offset of this drawer within its range, from 0-1 172 */ onDrawerSlide(View drawerView, float slideOffset)173 void onDrawerSlide(View drawerView, float slideOffset); 174 175 /** 176 * Called when a drawer has settled in a completely open state. 177 * The drawer is interactive at this point. 178 * 179 * @param drawerView Drawer view that is now open 180 */ onDrawerOpened(View drawerView)181 void onDrawerOpened(View drawerView); 182 183 /** 184 * Called when a drawer has settled in a completely closed state. 185 * 186 * @param drawerView Drawer view that is now closed 187 */ onDrawerClosed(View drawerView)188 void onDrawerClosed(View drawerView); 189 190 /** 191 * Called when a drawer is starting to open. 192 * 193 * @param drawerView Drawer view that is opening 194 */ onDrawerOpening(View drawerView)195 void onDrawerOpening(View drawerView); 196 197 /** 198 * Called when a drawer is starting to close. 199 * 200 * @param drawerView Drawer view that is closing 201 */ onDrawerClosing(View drawerView)202 void onDrawerClosing(View drawerView); 203 204 /** 205 * Called when the drawer motion state changes. The new state will 206 * be one of {@link #STATE_IDLE}, {@link #STATE_DRAGGING} or {@link #STATE_SETTLING}. 207 * 208 * @param newState The new drawer motion state 209 */ onDrawerStateChanged(int newState)210 void onDrawerStateChanged(int newState); 211 } 212 213 /** 214 * Used to execute when the drawer needs to handle state that the underlying views would like 215 * to handle in a specific way. 216 */ 217 public interface DrawerControllerListener { onBack()218 void onBack(); onScroll()219 boolean onScroll(); 220 } 221 222 /** 223 * Stub/no-op implementations of all methods of {@link DrawerListener}. 224 * Override this if you only care about a few of the available callback methods. 225 */ 226 public static abstract class SimpleDrawerListener implements DrawerListener { 227 @Override onDrawerSlide(View drawerView, float slideOffset)228 public void onDrawerSlide(View drawerView, float slideOffset) { 229 } 230 231 @Override onDrawerOpened(View drawerView)232 public void onDrawerOpened(View drawerView) { 233 } 234 235 @Override onDrawerClosed(View drawerView)236 public void onDrawerClosed(View drawerView) { 237 } 238 239 @Override onDrawerOpening(View drawerView)240 public void onDrawerOpening(View drawerView) { 241 } 242 243 @Override onDrawerClosing(View drawerView)244 public void onDrawerClosing(View drawerView) { 245 } 246 247 @Override onDrawerStateChanged(int newState)248 public void onDrawerStateChanged(int newState) { 249 } 250 } 251 252 /** 253 * Sets the color of (or tints) a view (or views). 254 */ 255 public interface ViewFader { setColor(int color)256 void setColor(int color); 257 } 258 CarDrawerLayout(Context context)259 public CarDrawerLayout(Context context) { 260 this(context, null); 261 } 262 CarDrawerLayout(Context context, AttributeSet attrs)263 public CarDrawerLayout(Context context, AttributeSet attrs) { 264 this(context, attrs, 0); 265 } 266 CarDrawerLayout(final Context context, AttributeSet attrs, int defStyle)267 public CarDrawerLayout(final Context context, AttributeSet attrs, int defStyle) { 268 super(context, attrs, defStyle); 269 270 mViewFaders = new HashSet<>(); 271 mEndingViewColor = getResources().getColor(R.color.car_tint); 272 273 mEdgeHighlightPaint.setColor(getResources().getColor(android.R.color.black)); 274 275 final float density = getResources().getDisplayMetrics().density; 276 final float minVel = MIN_FLING_VELOCITY * density; 277 278 ViewDragCallback viewDragCallback = new ViewDragCallback(); 279 mDragger = ViewDragHelper.create(this, TOUCH_SLOP_SENSITIVITY, viewDragCallback); 280 mDragger.setMinVelocity(minVel); 281 viewDragCallback.setDragger(mDragger); 282 283 ViewGroupCompat.setMotionEventSplittingEnabled(this, false); 284 285 if (SHADOW_ENABLED) { 286 setDrawerShadow(CarUiResourceLoader.getDrawable(context, "drawer_shadow")); 287 } 288 289 Resources.Theme theme = context.getTheme(); 290 TypedArray ta = theme.obtainStyledAttributes(new int[] { 291 android.R.attr.colorPrimaryDark 292 }); 293 setScrimColor(ta.getColor(0, context.getResources().getColor(R.color.car_grey_900))); 294 295 mViewFaderInterpolator = new ReversibleInterpolator( 296 new QuantumInterpolator(QuantumInterpolator.FAST_OUT_SLOW_IN, 0.25f, 0.25f, 0.5f), 297 new QuantumInterpolator(QuantumInterpolator.FAST_OUT_SLOW_IN, 0.43f, 0.14f, 0.43f) 298 ); 299 mDrawerFadeInterpolator = new ReversibleInterpolator( 300 new QuantumInterpolator(QuantumInterpolator.FAST_OUT_SLOW_IN, 0.625f, 0.25f, 0.125f), 301 new QuantumInterpolator(QuantumInterpolator.FAST_OUT_LINEAR_IN, 0.58f, 0.14f, 0.28f) 302 ); 303 304 mHasWheel = CarUiResourceLoader.getBoolean(context, "has_wheel", false); 305 } 306 307 @Override dispatchKeyEvent(@onNull KeyEvent keyEvent)308 public boolean dispatchKeyEvent(@NonNull KeyEvent keyEvent) { 309 int action = keyEvent.getAction(); 310 int keyCode = keyEvent.getKeyCode(); 311 final View drawerView = findDrawerView(); 312 if (drawerView != null && getDrawerLockMode(drawerView) == LOCK_MODE_UNLOCKED) { 313 if (isDrawerOpen()) { 314 if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT 315 || keyCode == KeyEvent.KEYCODE_SOFT_RIGHT) { 316 closeDrawer(); 317 return true; 318 } else if (keyCode == KeyEvent.KEYCODE_BACK 319 && action == KeyEvent.ACTION_UP 320 && mDrawerControllerListener != null) { 321 mDrawerControllerListener.onBack(); 322 return true; 323 } else { 324 return drawerView.dispatchKeyEvent(keyEvent); 325 } 326 } 327 } 328 329 return mContentView.dispatchKeyEvent(keyEvent); 330 } 331 332 @Override dispatchGenericMotionEvent(MotionEvent ev)333 public boolean dispatchGenericMotionEvent(MotionEvent ev) { 334 final View drawerView = findDrawerView(); 335 if (drawerView != null 336 && ev.getAction() == MotionEvent.ACTION_SCROLL 337 && mDrawerControllerListener != null 338 && mDrawerControllerListener.onScroll()) { 339 return true; 340 } 341 return super.dispatchGenericMotionEvent(ev); 342 } 343 344 @Override onFinishInflate()345 protected void onFinishInflate() { 346 super.onFinishInflate(); 347 mHasInflated = true; 348 setAutoDayNightMode(); 349 350 setOnGenericMotionListener(new OnGenericMotionListener() { 351 @Override 352 public boolean onGenericMotion(View view, MotionEvent event) { 353 if (getChildCount() == 0) { 354 return false; 355 } 356 if (isDrawerOpen()) { 357 View drawerView = findDrawerView(); 358 ViewGroup viewGroup = (ViewGroup) ((FrameLayout) drawerView).getChildAt(0); 359 return viewGroup.getChildAt(0).onGenericMotionEvent(event); 360 } 361 View contentView = findContentView(); 362 ViewGroup viewGroup = (ViewGroup) ((FrameLayout) contentView).getChildAt(0); 363 return viewGroup.getChildAt(0).onGenericMotionEvent(event); 364 } 365 }); 366 } 367 368 /** 369 * Set a simple drawable used for the left or right shadow. 370 * The drawable provided must have a nonzero intrinsic width. 371 * 372 * @param shadowDrawable Shadow drawable to use at the edge of a drawer 373 */ setDrawerShadow(Drawable shadowDrawable)374 public void setDrawerShadow(Drawable shadowDrawable) { 375 mShadow = shadowDrawable; 376 invalidate(); 377 } 378 379 380 381 /** 382 * Set a color to use for the scrim that obscures primary content while a drawer is open. 383 * 384 * @param color Color to use in 0xAARRGGBB format. 385 */ setScrimColor(int color)386 public void setScrimColor(int color) { 387 mScrimColor = color; 388 invalidate(); 389 } 390 391 /** 392 * Set a listener to be notified of drawer events. 393 * 394 * @param listener Listener to notify when drawer events occur 395 * @see DrawerListener 396 */ setDrawerListener(DrawerListener listener)397 public void setDrawerListener(DrawerListener listener) { 398 mDrawerListener = listener; 399 } 400 setDrawerControllerListener(DrawerControllerListener listener)401 public void setDrawerControllerListener(DrawerControllerListener listener) { 402 mDrawerControllerListener = listener; 403 } 404 405 /** 406 * Enable or disable interaction with all drawers. 407 * 408 * <p>This allows the application to restrict the user's ability to open or close 409 * any drawer within this layout. DrawerLayout will still respond to calls to 410 * {@link #openDrawer()}, {@link #closeDrawer()} and friends if a drawer is locked.</p> 411 * 412 * <p>Locking drawers open or closed will implicitly open or close 413 * any drawers as appropriate.</p> 414 * 415 * @param lockMode The new lock mode for the given drawer. One of {@link #LOCK_MODE_UNLOCKED}, 416 * {@link #LOCK_MODE_LOCKED_CLOSED} or {@link #LOCK_MODE_LOCKED_OPEN}. 417 */ setDrawerLockMode(int lockMode)418 public void setDrawerLockMode(int lockMode) { 419 LayoutParams lp = (LayoutParams) findDrawerView().getLayoutParams(); 420 setDrawerLockMode(lockMode, lp.gravity); 421 } 422 423 /** 424 * Enable or disable interaction with the given drawer. 425 * 426 * <p>This allows the application to restrict the user's ability to open or close 427 * the given drawer. DrawerLayout will still respond to calls to {@link #openDrawer()}, 428 * {@link #closeDrawer()} and friends if a drawer is locked.</p> 429 * 430 * <p>Locking a drawer open or closed will implicitly open or close 431 * that drawer 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 * @param edgeGravity Gravity.LEFT, RIGHT, START or END. 436 * Expresses which drawer to change the mode for. 437 * 438 * @see #LOCK_MODE_UNLOCKED 439 * @see #LOCK_MODE_LOCKED_CLOSED 440 * @see #LOCK_MODE_LOCKED_OPEN 441 */ setDrawerLockMode(int lockMode, int edgeGravity)442 public void setDrawerLockMode(int lockMode, int edgeGravity) { 443 final int absGravity = GravityCompat.getAbsoluteGravity(edgeGravity, 444 ViewCompat.getLayoutDirection(this)); 445 if (absGravity == Gravity.LEFT) { 446 mLockModeLeft = lockMode; 447 } else if (absGravity == Gravity.RIGHT) { 448 mLockModeRight = lockMode; 449 } 450 if (lockMode != LOCK_MODE_UNLOCKED) { 451 // Cancel interaction in progress 452 mDragger.cancel(); 453 } 454 switch (lockMode) { 455 case LOCK_MODE_LOCKED_OPEN: 456 openDrawer(); 457 break; 458 case LOCK_MODE_LOCKED_CLOSED: 459 closeDrawer(); 460 break; 461 // default: do nothing 462 } 463 } 464 465 /** 466 * All view faders will be light when the drawer is open and fade to dark and it closes. 467 * NOTE: this will clear any existing view faders. 468 */ setLightMode()469 public void setLightMode() { 470 mStartingViewColor = getResources().getColor(R.color.car_title_light); 471 mEndingViewColor = getResources().getColor(R.color.car_tint); 472 updateViewFaders(); 473 } 474 475 /** 476 * All view faders will be dark when the drawer is open and stay that way when it closes. 477 * NOTE: this will clear any existing view faders. 478 */ setDarkMode()479 public void setDarkMode() { 480 mStartingViewColor = getResources().getColor(R.color.car_title_dark); 481 mEndingViewColor = getResources().getColor(R.color.car_tint); 482 updateViewFaders(); 483 } 484 485 /** 486 * All view faders will be dark during the day and light at night. 487 * NOTE: this will clear any existing view faders. 488 */ setAutoDayNightMode()489 public void setAutoDayNightMode() { 490 mStartingViewColor = getResources().getColor(R.color.car_title); 491 mEndingViewColor = getResources().getColor(R.color.car_tint); 492 updateViewFaders(); 493 } 494 resetViewFaders()495 private void resetViewFaders() { 496 mViewFaders.clear(); 497 } 498 499 /** 500 * Check the lock mode of the given drawer view. 501 * 502 * @param drawerView Drawer view to check lock mode 503 * @return one of {@link #LOCK_MODE_UNLOCKED}, {@link #LOCK_MODE_LOCKED_CLOSED} or 504 * {@link #LOCK_MODE_LOCKED_OPEN}. 505 */ getDrawerLockMode(View drawerView)506 public int getDrawerLockMode(View drawerView) { 507 final int absGravity = getDrawerViewAbsoluteGravity(drawerView); 508 if (absGravity == Gravity.LEFT) { 509 return mLockModeLeft; 510 } else if (absGravity == Gravity.RIGHT) { 511 return mLockModeRight; 512 } 513 return LOCK_MODE_UNLOCKED; 514 } 515 516 /** 517 * Resolve the shared state of all drawers from the component ViewDragHelpers. 518 * Should be called whenever a ViewDragHelper's state changes. 519 */ updateDrawerState(int activeState)520 private void updateDrawerState(int activeState) { 521 View drawerView = findDrawerView(); 522 if (drawerView != null && activeState == STATE_IDLE) { 523 if (onScreen() == 0) { 524 dispatchOnDrawerClosed(drawerView); 525 } else if (onScreen() == 1) { 526 dispatchOnDrawerOpened(drawerView); 527 } 528 } 529 530 if (mDragger.getViewDragState() != mDrawerState) { 531 mDrawerState = mDragger.getViewDragState(); 532 533 if (mDrawerListener != null) { 534 mDrawerListener.onDrawerStateChanged(mDragger.getViewDragState()); 535 } 536 } 537 } 538 dispatchOnDrawerClosed(View drawerView)539 private void dispatchOnDrawerClosed(View drawerView) { 540 final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); 541 if (lp.knownOpen) { 542 lp.knownOpen = false; 543 if (mDrawerListener != null) { 544 mDrawerListener.onDrawerClosed(drawerView); 545 } 546 } 547 } 548 dispatchOnDrawerOpened(View drawerView)549 private void dispatchOnDrawerOpened(View drawerView) { 550 final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); 551 if (!lp.knownOpen) { 552 lp.knownOpen = true; 553 if (mDrawerListener != null) { 554 mDrawerListener.onDrawerOpened(drawerView); 555 } 556 } 557 } 558 dispatchOnDrawerSlide(View drawerView, float slideOffset)559 private void dispatchOnDrawerSlide(View drawerView, float slideOffset) { 560 if (mDrawerListener != null) { 561 mDrawerListener.onDrawerSlide(drawerView, slideOffset); 562 } 563 } 564 dispatchOnDrawerOpening(View drawerView)565 private void dispatchOnDrawerOpening(View drawerView) { 566 if (mDrawerListener != null) { 567 mDrawerListener.onDrawerOpening(drawerView); 568 } 569 } 570 dispatchOnDrawerClosing(View drawerView)571 private void dispatchOnDrawerClosing(View drawerView) { 572 if (mDrawerListener != null) { 573 mDrawerListener.onDrawerClosing(drawerView); 574 } 575 } 576 setDrawerViewOffset(View drawerView, float slideOffset)577 private void setDrawerViewOffset(View drawerView, float slideOffset) { 578 if (slideOffset == onScreen()) { 579 return; 580 } 581 582 LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); 583 lp.onScreen = slideOffset; 584 dispatchOnDrawerSlide(drawerView, slideOffset); 585 } 586 onScreen()587 private float onScreen() { 588 return ((LayoutParams) findDrawerView().getLayoutParams()).onScreen; 589 } 590 591 /** 592 * @return the absolute gravity of the child drawerView, resolved according 593 * to the current layout direction 594 */ getDrawerViewAbsoluteGravity(View drawerView)595 private int getDrawerViewAbsoluteGravity(View drawerView) { 596 final int gravity = ((LayoutParams) drawerView.getLayoutParams()).gravity; 597 return GravityCompat.getAbsoluteGravity(gravity, ViewCompat.getLayoutDirection(this)); 598 } 599 checkDrawerViewAbsoluteGravity(View drawerView, int checkFor)600 private boolean checkDrawerViewAbsoluteGravity(View drawerView, int checkFor) { 601 final int absGravity = getDrawerViewAbsoluteGravity(drawerView); 602 return (absGravity & checkFor) == checkFor; 603 } 604 605 /** 606 * @return the drawer view 607 */ findDrawerView()608 private View findDrawerView() { 609 if (mDrawerView != null) { 610 return mDrawerView; 611 } 612 613 final int childCount = getChildCount(); 614 for (int i = 0; i < childCount; i++) { 615 final View child = getChildAt(i); 616 final int childAbsGravity = getDrawerViewAbsoluteGravity(child); 617 if (childAbsGravity != Gravity.NO_GRAVITY) { 618 mDrawerView = child; 619 return child; 620 } 621 } 622 throw new IllegalStateException("No drawer view found."); 623 } 624 625 /** 626 * @return the content. NOTE: this is the view with no gravity. 627 */ findContentView()628 private View findContentView() { 629 if (mContentView != null) { 630 return mContentView; 631 } 632 633 final int childCount = getChildCount(); 634 for (int i = childCount - 1; i >= 0; --i) { 635 final View child = getChildAt(i); 636 if (isDrawerView(child)) { 637 continue; 638 } 639 mContentView = child; 640 return child; 641 } 642 throw new IllegalStateException("No content view found."); 643 } 644 645 @Override onDetachedFromWindow()646 protected void onDetachedFromWindow() { 647 super.onDetachedFromWindow(); 648 } 649 650 @Override requestFocus(int direction, Rect rect)651 public boolean requestFocus(int direction, Rect rect) { 652 // Optimally we want to check isInTouchMode(), but that value isn't always correct. 653 if (mHasWheel) { 654 mNeedsFocus = true; 655 } 656 return super.requestFocus(direction, rect); 657 } 658 659 @Override onAttachedToWindow()660 protected void onAttachedToWindow() { 661 super.onAttachedToWindow(); 662 mFirstLayout = true; 663 // There needs to be a layout pending if we're not going to animate the drawer until the 664 // next layout, so make it so. 665 requestLayout(); 666 } 667 668 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)669 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 670 int widthMode = MeasureSpec.getMode(widthMeasureSpec); 671 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 672 int widthSize = MeasureSpec.getSize(widthMeasureSpec); 673 int heightSize = MeasureSpec.getSize(heightMeasureSpec); 674 675 if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) { 676 if (isInEditMode()) { 677 // Don't crash the layout editor. Consume all of the space if specified 678 // or pick a magic number from thin air otherwise. 679 // TODO Better communication with tools of this bogus state. 680 // It will crash on a real device. 681 if (widthMode == MeasureSpec.UNSPECIFIED) { 682 widthSize = 300; 683 } 684 else if (heightMode == MeasureSpec.UNSPECIFIED) { 685 heightSize = 300; 686 } 687 } else { 688 throw new IllegalArgumentException( 689 "DrawerLayout must be measured with MeasureSpec.EXACTLY."); 690 } 691 } 692 693 setMeasuredDimension(widthSize, heightSize); 694 695 View view = findContentView(); 696 LayoutParams lp = ((LayoutParams) view.getLayoutParams()); 697 // Content views get measured at exactly the layout's size. 698 final int contentWidthSpec = MeasureSpec.makeMeasureSpec( 699 widthSize - lp.leftMargin - lp.rightMargin, MeasureSpec.EXACTLY); 700 final int contentHeightSpec = MeasureSpec.makeMeasureSpec( 701 heightSize - lp.topMargin - lp.bottomMargin, MeasureSpec.EXACTLY); 702 view.measure(contentWidthSpec, contentHeightSpec); 703 704 view = findDrawerView(); 705 lp = ((LayoutParams) view.getLayoutParams()); 706 final int drawerWidthSpec = getChildMeasureSpec(widthMeasureSpec, 707 lp.leftMargin + lp.rightMargin, 708 lp.width); 709 final int drawerHeightSpec = getChildMeasureSpec(heightMeasureSpec, 710 lp.topMargin + lp.bottomMargin, 711 lp.height); 712 view.measure(drawerWidthSpec, drawerHeightSpec); 713 } 714 715 @Override onLayout(boolean changed, int l, int t, int r, int b)716 protected void onLayout(boolean changed, int l, int t, int r, int b) { 717 mInLayout = true; 718 final int width = r - l; 719 720 View contentView = findContentView(); 721 View drawerView = findDrawerView(); 722 723 LayoutParams drawerLp = (LayoutParams) drawerView.getLayoutParams(); 724 LayoutParams contentLp = (LayoutParams) contentView.getLayoutParams(); 725 726 int contentRight = contentLp.getMarginStart() + getWidth(); 727 contentView.layout(contentRight - contentView.getMeasuredWidth(), 728 contentLp.topMargin, contentRight, 729 contentLp.topMargin + contentView.getMeasuredHeight()); 730 731 final int childHeight = drawerView.getMeasuredHeight(); 732 int onScreen = (int) (drawerView.getWidth() * drawerLp.onScreen); 733 int offset; 734 if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) { 735 offset = onScreen - drawerView.getWidth(); 736 } else { 737 offset = width - onScreen; 738 } 739 drawerView.layout(drawerLp.getMarginStart() + offset, drawerLp.topMargin, 740 width - drawerLp.getMarginEnd() + offset, 741 childHeight + drawerLp.topMargin); 742 updateDrawerAlpha(); 743 updateViewFaders(); 744 if (mFirstLayout) { 745 746 // TODO(b/15394507): Normally, onMeasure()/onLayout() are called three times when 747 // you create CarDrawerLayout, but when you pop it back it's only called once which 748 // leaves us in a weird state. This is a pretty ugly hack to fix that. 749 mHandler.post(mInvalidateRunnable); 750 751 mFirstLayout = false; 752 } 753 754 if (mNeedsFocus) { 755 if (initializeFocus()) { 756 mNeedsFocus = false; 757 } 758 } 759 760 mInLayout = false; 761 } 762 initializeFocus()763 private boolean initializeFocus() { 764 // Only request focus if the current view that needs focus doesn't already have it. This 765 // prevents some nasty bugs where focus ends up snapping to random elements and also saves 766 // a bunch of cycles in the average case. 767 mDrawerView.setFocusable(false); 768 mContentView.setFocusable(false); 769 boolean needFocus = !mDrawerView.hasFocus() && !mContentView.hasFocus(); 770 if (!needFocus) { 771 return true; 772 } 773 774 // Find something in the hierarchy to give focus to. 775 List<View> focusables; 776 boolean drawerOpen = isDrawerOpen(); 777 if (drawerOpen) { 778 focusables = mDrawerView.getFocusables(FOCUS_DOWN); 779 } else { 780 focusables = mContentView.getFocusables(FOCUS_DOWN); 781 } 782 783 // The 2 else cases here are a catch all for when nothing is focusable in view hierarchy. 784 // If you don't have anything focusable on screen, key events will not be delivered to 785 // the view hierarchy and you end up getting stuck without being able to open / close the 786 // drawer or launch gsa. 787 788 if (!focusables.isEmpty()) { 789 focusables.get(0).requestFocus(); 790 return true; 791 } else if (drawerOpen) { 792 mDrawerView.setFocusable(true); 793 } else { 794 mContentView.setFocusable(true); 795 } 796 return false; 797 } 798 799 @Override requestLayout()800 public void requestLayout() { 801 if (!mInLayout) { 802 super.requestLayout(); 803 } 804 } 805 806 @Override computeScroll()807 public void computeScroll() { 808 if (mDragger.continueSettling(true)) { 809 ViewCompat.postInvalidateOnAnimation(this); 810 } 811 } 812 hasOpaqueBackground(View v)813 private static boolean hasOpaqueBackground(View v) { 814 final Drawable bg = v.getBackground(); 815 return bg != null && bg.getOpacity() == PixelFormat.OPAQUE; 816 } 817 818 @Override drawChild(Canvas canvas, View child, long drawingTime)819 protected boolean drawChild(Canvas canvas, View child, long drawingTime) { 820 final int height = getHeight(); 821 final boolean drawingContent = isContentView(child); 822 int clipLeft = findContentView().getLeft(); 823 int clipRight = findContentView().getRight(); 824 final int baseAlpha = (mScrimColor & 0xff000000) >>> 24; 825 826 final int restoreCount = canvas.save(); 827 if (drawingContent) { 828 final int childCount = getChildCount(); 829 for (int i = 0; i < childCount; i++) { 830 final View v = getChildAt(i); 831 if (v == child || v.getVisibility() != VISIBLE || 832 !hasOpaqueBackground(v) || !isDrawerView(v) || 833 v.getHeight() < height) { 834 continue; 835 } 836 837 if (checkDrawerViewAbsoluteGravity(v, Gravity.LEFT)) { 838 final int vright = v.getRight(); 839 if (vright > clipLeft) { 840 clipLeft = vright; 841 } 842 } else { 843 final int vleft = v.getLeft(); 844 if (vleft < clipRight) { 845 clipRight = vleft; 846 } 847 } 848 } 849 canvas.clipRect(clipLeft, 0, clipRight, getHeight()); 850 } 851 final boolean result = super.drawChild(canvas, child, drawingTime); 852 canvas.restoreToCount(restoreCount); 853 854 if (drawingContent) { 855 int scrimAlpha = SCRIM_ENABLED ? 856 (int) (baseAlpha * Math.max(0, Math.min(1, onScreen())) * MAX_SCRIM_ALPHA) : 0; 857 858 if (scrimAlpha > 0) { 859 int color = scrimAlpha << 24 | (mScrimColor & 0xffffff); 860 mScrimPaint.setColor(color); 861 862 canvas.drawRect(clipLeft, 0, clipRight, getHeight(), mScrimPaint); 863 864 canvas.drawRect(clipLeft - 1, 0, clipLeft, getHeight(), mEdgeHighlightPaint); 865 } 866 867 LayoutParams drawerLp = (LayoutParams) findDrawerView().getLayoutParams(); 868 if (mShadow != null 869 && checkDrawerViewAbsoluteGravity(findDrawerView(), Gravity.LEFT)) { 870 final int offScreen = (int) ((1 - drawerLp.onScreen) * findDrawerView().getWidth()); 871 final int drawerRight = getWidth() - drawerLp.getMarginEnd() - offScreen; 872 final int shadowWidth = mShadow.getIntrinsicWidth(); 873 final float alpha = 874 Math.max(0, Math.min((float) drawerRight / mDragger.getEdgeSize(), 1.f)); 875 mShadow.setBounds(drawerRight, child.getTop(), 876 drawerRight + shadowWidth, child.getBottom()); 877 mShadow.setAlpha((int) (255 * alpha * alpha * alpha)); 878 mShadow.draw(canvas); 879 } else if (mShadow != null 880 && checkDrawerViewAbsoluteGravity(findDrawerView(),Gravity.RIGHT)) { 881 final int onScreen = (int) (findDrawerView().getWidth() * drawerLp.onScreen); 882 final int drawerLeft = drawerLp.getMarginStart() + getWidth() - onScreen; 883 final int shadowWidth = mShadow.getIntrinsicWidth(); 884 final float alpha = 885 Math.max(0, Math.min((float) onScreen / mDragger.getEdgeSize(), 1.f)); 886 canvas.save(); 887 canvas.translate(2 * drawerLeft - shadowWidth, 0); 888 canvas.scale(-1.0f, 1.0f); 889 mShadow.setBounds(drawerLeft - shadowWidth, child.getTop(), 890 drawerLeft, child.getBottom()); 891 mShadow.setAlpha((int) (255 * alpha * alpha * alpha * alpha)); 892 mShadow.draw(canvas); 893 canvas.restore(); 894 } 895 } 896 return result; 897 } 898 isContentView(View child)899 private boolean isContentView(View child) { 900 return child == findContentView(); 901 } 902 isDrawerView(View child)903 private boolean isDrawerView(View child) { 904 return child == findDrawerView(); 905 } 906 updateDrawerAlpha()907 private void updateDrawerAlpha() { 908 float alpha; 909 if (mStartedOpen) { 910 alpha = mDrawerFadeInterpolator.getReverseInterpolation(onScreen()); 911 } else { 912 alpha = mDrawerFadeInterpolator.getForwardInterpolation(onScreen()); 913 } 914 ViewGroup drawerView = (ViewGroup) findDrawerView(); 915 int drawerChildCount = drawerView.getChildCount(); 916 for (int i = 0; i < drawerChildCount; i++) { 917 drawerView.getChildAt(i).setAlpha(alpha); 918 } 919 } 920 921 /** 922 * Add a view fader whose color will be set as the drawer opens and closes. 923 */ addViewFader(ViewFader viewFader)924 public void addViewFader(ViewFader viewFader) { 925 addViewFader(viewFader, mStartingViewColor, mEndingViewColor); 926 } 927 addViewFader(ViewFader viewFader, int startingColor, int endingColor)928 public void addViewFader(ViewFader viewFader, int startingColor, int endingColor) { 929 mViewFaders.add(new ViewFaderHolder(viewFader, startingColor, endingColor)); 930 updateViewFaders(); 931 } 932 removeViewFader(ViewFader viewFader)933 public void removeViewFader(ViewFader viewFader) { 934 for (Iterator<ViewFaderHolder> it = mViewFaders.iterator(); it.hasNext(); ) { 935 ViewFaderHolder viewFaderHolder = it.next(); 936 if (viewFaderHolder.viewFader.equals(viewFader)) { 937 it.remove(); 938 } 939 } 940 } 941 updateViewFaders()942 private void updateViewFaders() { 943 if (!mHasInflated) { 944 return; 945 } 946 947 float fadeProgress; 948 if (mStartedOpen) { 949 fadeProgress = mViewFaderInterpolator.getReverseInterpolation(onScreen()); 950 } else { 951 fadeProgress = mViewFaderInterpolator.getForwardInterpolation(onScreen()); 952 } 953 for (Iterator<ViewFaderHolder> it = mViewFaders.iterator(); it.hasNext(); ) { 954 ViewFaderHolder viewFaderHolder = it.next(); 955 int startingColor = viewFaderHolder.startingColor; 956 int endingColor = viewFaderHolder.endingColor; 957 int alpha = weightedAverage(Color.alpha(startingColor), 958 Color.alpha(endingColor), fadeProgress); 959 int red = weightedAverage(Color.red(startingColor), 960 Color.red(endingColor), fadeProgress); 961 int green = weightedAverage(Color.green(startingColor), 962 Color.green(endingColor), fadeProgress); 963 int blue = weightedAverage(Color.blue(startingColor), 964 Color.blue(endingColor), fadeProgress); 965 viewFaderHolder.viewFader.setColor(alpha << 24 | red << 16 | green << 8 | blue); 966 } 967 } 968 weightedAverage(int starting, int ending, float weight)969 private int weightedAverage(int starting, int ending, float weight) { 970 return (int) ((1f - weight) * starting + weight * ending); 971 } 972 973 @Override onInterceptTouchEvent(MotionEvent ev)974 public boolean onInterceptTouchEvent(MotionEvent ev) { 975 final int action = MotionEventCompat.getActionMasked(ev); 976 977 // "|" used deliberately here; both methods should be invoked. 978 final boolean interceptForDrag = mDragger.shouldInterceptTouchEvent(ev); 979 980 boolean interceptForTap = false; 981 982 switch (action) { 983 case MotionEvent.ACTION_DOWN: { 984 final float x = ev.getX(); 985 final float y = ev.getY(); 986 if (onScreen() > 0 && isContentView(mDragger.findTopChildUnder((int) x, (int) y))) { 987 interceptForTap = true; 988 } 989 mChildrenCanceledTouch = false; 990 break; 991 } 992 case MotionEvent.ACTION_CANCEL: 993 case MotionEvent.ACTION_UP: { 994 mChildrenCanceledTouch = false; 995 } 996 } 997 998 return interceptForDrag || interceptForTap || mChildrenCanceledTouch; 999 } 1000 1001 @Override onTouchEvent(@onNull MotionEvent ev)1002 public boolean onTouchEvent(@NonNull MotionEvent ev) { 1003 mDragger.processTouchEvent(ev); 1004 final int absGravity = getDrawerViewAbsoluteGravity(findDrawerView()); 1005 final int edge; 1006 if (absGravity == Gravity.LEFT) { 1007 edge = ViewDragHelper.EDGE_LEFT; 1008 } else { 1009 edge = ViewDragHelper.EDGE_RIGHT; 1010 } 1011 1012 // don't allow views behind the drawer to be touched 1013 boolean drawerPartiallyOpen = onScreen() > 0; 1014 return mDragger.isEdgeTouched(edge) || 1015 mDragger.getCapturedView() != null || 1016 drawerPartiallyOpen; 1017 } 1018 1019 @Override requestDisallowInterceptTouchEvent(boolean disallowIntercept)1020 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { 1021 if (CHILDREN_DISALLOW_INTERCEPT) { 1022 // If we have an edge touch we want to skip this and track it for later instead. 1023 super.requestDisallowInterceptTouchEvent(disallowIntercept); 1024 } 1025 1026 View drawerView = findDrawerView(); 1027 if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) { 1028 super.requestDisallowInterceptTouchEvent(disallowIntercept); 1029 } 1030 1031 if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.RIGHT)) { 1032 super.requestDisallowInterceptTouchEvent(disallowIntercept); 1033 } 1034 } 1035 1036 /** 1037 * Open the drawer view by animating it into view. 1038 */ openDrawer()1039 public void openDrawer() { 1040 ViewGroup drawerView = (ViewGroup) findDrawerView(); 1041 mStartedOpen = false; 1042 1043 if (hasWindowFocus()) { 1044 int left; 1045 LayoutParams drawerLp = (LayoutParams) drawerView.getLayoutParams(); 1046 if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) { 1047 left = drawerLp.getMarginStart(); 1048 } else { 1049 left = drawerLp.getMarginStart() + getWidth() - drawerView.getWidth(); 1050 } 1051 mDragger.smoothSlideViewTo(drawerView, left, drawerView.getTop()); 1052 dispatchOnDrawerOpening(drawerView); 1053 } else { 1054 final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); 1055 lp.onScreen = 1.f; 1056 dispatchOnDrawerOpened(drawerView); 1057 } 1058 1059 ViewGroup contentView = (ViewGroup) findContentView(); 1060 contentView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); 1061 drawerView.setDescendantFocusability(ViewGroup. FOCUS_AFTER_DESCENDANTS); 1062 1063 View focusable = drawerView.getChildAt(0); 1064 if (focusable != null) { 1065 focusable.requestFocus(); 1066 } 1067 invalidate(); 1068 } 1069 1070 /** 1071 * Close the specified drawer view by animating it into view. 1072 */ closeDrawer()1073 public void closeDrawer() { 1074 ViewGroup drawerView = (ViewGroup) findDrawerView(); 1075 if (!isDrawerView(drawerView)) { 1076 throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer"); 1077 } 1078 mStartedOpen = true; 1079 1080 // Don't trigger the close drawer animation if drawer is not open. 1081 if (hasWindowFocus() && isDrawerOpen()) { 1082 int left; 1083 LayoutParams drawerLp = (LayoutParams) drawerView.getLayoutParams(); 1084 if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) { 1085 left = drawerLp.getMarginStart() - drawerView.getWidth(); 1086 } else { 1087 left = drawerLp.getMarginStart() + getWidth(); 1088 } 1089 mDragger.smoothSlideViewTo(drawerView, left, drawerView.getTop()); 1090 dispatchOnDrawerClosing(drawerView); 1091 } else { 1092 final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); 1093 lp.onScreen = 0.f; 1094 dispatchOnDrawerClosed(drawerView); 1095 } 1096 1097 ViewGroup contentView = (ViewGroup) findContentView(); 1098 drawerView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); 1099 contentView.setDescendantFocusability(ViewGroup. FOCUS_AFTER_DESCENDANTS); 1100 1101 if (!isInTouchMode()) { 1102 List<View> focusables = contentView.getFocusables(FOCUS_DOWN); 1103 if (focusables.size() > 0) { 1104 View candidate = focusables.get(0); 1105 candidate.requestFocus(); 1106 } 1107 } 1108 invalidate(); 1109 } 1110 1111 @Override addFocusables(@onNull ArrayList<View> views, int direction, int focusableMode)1112 public void addFocusables(@NonNull ArrayList<View> views, int direction, int focusableMode) { 1113 boolean drawerOpen = isDrawerOpen(); 1114 if (drawerOpen) { 1115 findDrawerView().addFocusables(views, direction, focusableMode); 1116 } else { 1117 findContentView().addFocusables(views, direction, focusableMode); 1118 } 1119 } 1120 1121 /** 1122 * Check if the given drawer view is currently in an open state. 1123 * To be considered "open" the drawer must have settled into its fully 1124 * visible state. To check for partial visibility use 1125 * {@link #isDrawerVisible(android.view.View)}. 1126 * 1127 * @return true if the given drawer view is in an open state 1128 * @see #isDrawerVisible(android.view.View) 1129 */ isDrawerOpen()1130 public boolean isDrawerOpen() { 1131 return ((LayoutParams) findDrawerView().getLayoutParams()).knownOpen; 1132 } 1133 1134 /** 1135 * Check if a given drawer view is currently visible on-screen. The drawer 1136 * may be fully extended or anywhere in between. 1137 * 1138 * @param drawer Drawer view to check 1139 * @return true if the given drawer is visible on-screen 1140 * @see #isDrawerOpen() 1141 */ isDrawerVisible(View drawer)1142 public boolean isDrawerVisible(View drawer) { 1143 if (!isDrawerView(drawer)) { 1144 throw new IllegalArgumentException("View " + drawer + " is not a drawer"); 1145 } 1146 return onScreen() > 0; 1147 } 1148 1149 /** 1150 * Check if a given drawer view is currently visible on-screen. The drawer 1151 * may be fully extended or anywhere in between. 1152 * If there is no drawer with the given gravity this method will return false. 1153 * 1154 * @return true if the given drawer is visible on-screen 1155 */ isDrawerVisible()1156 public boolean isDrawerVisible() { 1157 final View drawerView = findDrawerView(); 1158 return drawerView != null && isDrawerVisible(drawerView); 1159 } 1160 1161 @Override generateDefaultLayoutParams()1162 protected ViewGroup.LayoutParams generateDefaultLayoutParams() { 1163 return new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1164 ViewGroup.LayoutParams.MATCH_PARENT); 1165 } 1166 1167 @Override generateLayoutParams(ViewGroup.LayoutParams p)1168 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 1169 return p instanceof LayoutParams 1170 ? new LayoutParams((LayoutParams) p) 1171 : p instanceof MarginLayoutParams 1172 ? new LayoutParams((MarginLayoutParams) p) 1173 : new LayoutParams(p); 1174 } 1175 1176 @Override checkLayoutParams(ViewGroup.LayoutParams p)1177 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 1178 return p instanceof LayoutParams && super.checkLayoutParams(p); 1179 } 1180 1181 @Override generateLayoutParams(AttributeSet attrs)1182 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { 1183 return new LayoutParams(getContext(), attrs); 1184 } 1185 hasVisibleDrawer()1186 private boolean hasVisibleDrawer() { 1187 return findVisibleDrawer() != null; 1188 } 1189 findVisibleDrawer()1190 private View findVisibleDrawer() { 1191 final int childCount = getChildCount(); 1192 for (int i = 0; i < childCount; i++) { 1193 final View child = getChildAt(i); 1194 if (isDrawerView(child) && isDrawerVisible(child)) { 1195 return child; 1196 } 1197 } 1198 return null; 1199 } 1200 1201 @Override onRestoreInstanceState(Parcelable state)1202 protected void onRestoreInstanceState(Parcelable state) { 1203 SavedState ss = null; 1204 if (state.getClass().getClassLoader() != getClass().getClassLoader()) { 1205 // Class loader mismatch, recreate from parcel. 1206 Parcel stateParcel = Parcel.obtain(); 1207 state.writeToParcel(stateParcel, 0); 1208 ss = SavedState.CREATOR.createFromParcel(stateParcel); 1209 } else { 1210 ss = (SavedState) state; 1211 } 1212 super.onRestoreInstanceState(ss.getSuperState()); 1213 1214 if (ss.openDrawerGravity != Gravity.NO_GRAVITY) { 1215 openDrawer(); 1216 } 1217 1218 setDrawerLockMode(ss.lockModeLeft, Gravity.LEFT); 1219 setDrawerLockMode(ss.lockModeRight, Gravity.RIGHT); 1220 } 1221 1222 @Override onSaveInstanceState()1223 protected Parcelable onSaveInstanceState() { 1224 final Parcelable superState = super.onSaveInstanceState(); 1225 1226 final SavedState ss = new SavedState(superState); 1227 1228 final int childCount = getChildCount(); 1229 for (int i = 0; i < childCount; i++) { 1230 final View child = getChildAt(i); 1231 if (!isDrawerView(child)) { 1232 continue; 1233 } 1234 1235 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 1236 if (lp.knownOpen) { 1237 ss.openDrawerGravity = lp.gravity; 1238 // Only one drawer can be open at a time. 1239 break; 1240 } 1241 } 1242 1243 ss.lockModeLeft = mLockModeLeft; 1244 ss.lockModeRight = mLockModeRight; 1245 1246 return ss; 1247 } 1248 1249 /** 1250 * State persisted across instances 1251 */ 1252 protected static class SavedState extends BaseSavedState { 1253 int openDrawerGravity = Gravity.NO_GRAVITY; 1254 int lockModeLeft = LOCK_MODE_UNLOCKED; 1255 int lockModeRight = LOCK_MODE_UNLOCKED; 1256 SavedState(Parcel in)1257 public SavedState(Parcel in) { 1258 super(in); 1259 openDrawerGravity = in.readInt(); 1260 lockModeLeft = in.readInt(); 1261 lockModeRight = in.readInt(); 1262 } 1263 SavedState(Parcelable superState)1264 public SavedState(Parcelable superState) { 1265 super(superState); 1266 } 1267 1268 @Override writeToParcel(@onNull Parcel dest, int flags)1269 public void writeToParcel(@NonNull Parcel dest, int flags) { 1270 super.writeToParcel(dest, flags); 1271 dest.writeInt(openDrawerGravity); 1272 dest.writeInt(lockModeLeft); 1273 dest.writeInt(lockModeRight); 1274 } 1275 1276 @SuppressWarnings("hiding") 1277 public static final Creator<SavedState> CREATOR = 1278 new Creator<SavedState>() { 1279 @Override 1280 public SavedState createFromParcel(Parcel source) { 1281 return new SavedState(source); 1282 } 1283 1284 @Override 1285 public SavedState[] newArray(int size) { 1286 return new SavedState[size]; 1287 } 1288 }; 1289 } 1290 1291 private class ViewDragCallback extends ViewDragHelper.Callback { 1292 @SuppressWarnings("hiding") 1293 private ViewDragHelper mDragger; 1294 setDragger(ViewDragHelper dragger)1295 public void setDragger(ViewDragHelper dragger) { 1296 mDragger = dragger; 1297 } 1298 1299 @Override tryCaptureView(View child, int pointerId)1300 public boolean tryCaptureView(View child, int pointerId) { 1301 CarDrawerLayout.LayoutParams lp = (LayoutParams) findDrawerView().getLayoutParams(); 1302 int edges = EDGE_DRAG_ENABLED ? ViewDragHelper.EDGE_ALL : 0; 1303 boolean captured = isContentView(child) && 1304 getDrawerLockMode(child) == LOCK_MODE_UNLOCKED && 1305 (lp.knownOpen || mDragger.isEdgeTouched(edges)); 1306 if (captured && lp.knownOpen) { 1307 mStartedOpen = true; 1308 } else if (captured && !lp.knownOpen) { 1309 mStartedOpen = false; 1310 } 1311 // We want dragging starting on the content view to drag the drawer. Therefore when 1312 // touch events try to capture the content view, we force capture of the drawer view. 1313 if (captured) { 1314 mDragger.captureChildView(findDrawerView(), pointerId); 1315 } 1316 return false; 1317 } 1318 1319 @Override onViewDragStateChanged(int state)1320 public void onViewDragStateChanged(int state) { 1321 updateDrawerState(state); 1322 } 1323 1324 @Override onViewPositionChanged(View changedView, int left, int top, int dx, int dy)1325 public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { 1326 float offset; 1327 View drawerView = findDrawerView(); 1328 final int drawerWidth = drawerView.getWidth(); 1329 // This reverses the positioning shown in onLayout. 1330 if (checkDrawerViewAbsoluteGravity(findDrawerView(), Gravity.LEFT)) { 1331 offset = (float) (left + drawerWidth) / drawerWidth; 1332 } else { 1333 offset = (float) (getWidth() - left) / drawerWidth; 1334 } 1335 setDrawerViewOffset(findDrawerView(), offset); 1336 1337 updateDrawerAlpha(); 1338 1339 updateViewFaders(); 1340 invalidate(); 1341 } 1342 1343 @Override onViewReleased(View releasedChild, float xvel, float yvel)1344 public void onViewReleased(View releasedChild, float xvel, float yvel) { 1345 final View drawerView = findDrawerView(); 1346 final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); 1347 int left; 1348 if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) { 1349 // Open the drawer if they are swiping right or if they are not currently moving but 1350 // have moved the drawer in the current gesture and released the drawer when it was 1351 // fully open. 1352 // Close otherwise. 1353 left = xvel > 0 ? lp.getMarginStart() : lp.getMarginStart() - drawerView.getWidth(); 1354 } else { 1355 // See comment for left drawer. 1356 left = xvel < 0 ? lp.getMarginStart() + getWidth() - drawerView.getWidth() 1357 : lp.getMarginStart() + getWidth(); 1358 } 1359 1360 mDragger.settleCapturedViewAt(left, releasedChild.getTop()); 1361 invalidate(); 1362 } 1363 1364 @Override 1365 public boolean onEdgeLock(int edgeFlags) { 1366 if (ALLOW_EDGE_LOCK) { 1367 if (!isDrawerOpen()) { 1368 closeDrawer(); 1369 } 1370 return true; 1371 } 1372 return false; 1373 } 1374 1375 @Override 1376 public void onEdgeDragStarted(int edgeFlags, int pointerId) { 1377 View drawerView = findDrawerView(); 1378 if ((edgeFlags & ViewDragHelper.EDGE_LEFT) == ViewDragHelper.EDGE_LEFT) { 1379 if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.RIGHT)) { 1380 drawerView = null; 1381 } 1382 } else { 1383 if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) { 1384 drawerView = null; 1385 } 1386 } 1387 1388 if (drawerView != null && getDrawerLockMode(drawerView) == LOCK_MODE_UNLOCKED) { 1389 mDragger.captureChildView(drawerView, pointerId); 1390 } 1391 } 1392 1393 @Override 1394 public int getViewHorizontalDragRange(View child) { 1395 return child.getWidth(); 1396 } 1397 1398 @Override 1399 public int clampViewPositionHorizontal(View child, int left, int dx) { 1400 final View drawerView = findDrawerView(); 1401 LayoutParams drawerLp = (LayoutParams) drawerView.getLayoutParams(); 1402 if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) { 1403 return Math.max(drawerLp.getMarginStart() - drawerView.getWidth(), 1404 Math.min(left, drawerLp.getMarginStart())); 1405 } else { 1406 return Math.max(drawerLp.getMarginStart() + getWidth() - drawerView.getWidth(), 1407 Math.min(left, drawerLp.getMarginStart() + getWidth())); 1408 } 1409 } 1410 1411 @Override 1412 public int clampViewPositionVertical(View child, int top, int dy) { 1413 return child.getTop(); 1414 } 1415 } 1416 1417 public static class LayoutParams extends MarginLayoutParams { 1418 1419 public int gravity = Gravity.NO_GRAVITY; 1420 float onScreen; 1421 boolean knownOpen; 1422 1423 public LayoutParams(Context c, AttributeSet attrs) { 1424 super(c, attrs); 1425 1426 final TypedArray a = c.obtainStyledAttributes(attrs, LAYOUT_ATTRS); 1427 gravity = a.getInt(0, Gravity.NO_GRAVITY); 1428 a.recycle(); 1429 } 1430 1431 public LayoutParams(int width, int height) { 1432 super(width, height); 1433 } 1434 1435 public LayoutParams(int width, int height, int gravity) { 1436 this(width, height); 1437 this.gravity = gravity; 1438 } 1439 1440 public LayoutParams(LayoutParams source) { 1441 super(source); 1442 gravity = source.gravity; 1443 } 1444 1445 public LayoutParams(ViewGroup.LayoutParams source) { 1446 super(source); 1447 } 1448 1449 public LayoutParams(MarginLayoutParams source) { 1450 super(source); 1451 } 1452 } 1453 1454 private static final class ViewFaderHolder { 1455 public final ViewFader viewFader; 1456 public final int startingColor; 1457 public final int endingColor; 1458 1459 public ViewFaderHolder(ViewFader viewFader, int startingColor, int endingColor) { 1460 this.viewFader = viewFader; 1461 this.startingColor = startingColor; 1462 this.endingColor = endingColor; 1463 } 1464 1465 } 1466 } 1467