SlidingPaneLayout.java revision 1e43161e9e1f1dc10637a68d5c2304c1f95c9c46
1/* 2 * Copyright (C) 2012 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 17package android.support.v4.widget; 18 19import android.content.Context; 20import android.content.res.TypedArray; 21import android.graphics.Bitmap; 22import android.graphics.Canvas; 23import android.graphics.Paint; 24import android.graphics.PorterDuff; 25import android.graphics.PorterDuffColorFilter; 26import android.graphics.Rect; 27import android.graphics.drawable.Drawable; 28import android.os.Build; 29import android.os.Parcel; 30import android.os.Parcelable; 31import android.support.v4.view.MotionEventCompat; 32import android.support.v4.view.VelocityTrackerCompat; 33import android.support.v4.view.ViewCompat; 34import android.support.v4.view.ViewConfigurationCompat; 35import android.util.AttributeSet; 36import android.util.Log; 37import android.view.MotionEvent; 38import android.view.VelocityTracker; 39import android.view.View; 40import android.view.ViewConfiguration; 41import android.view.ViewGroup; 42import android.view.animation.Interpolator; 43import android.widget.Scroller; 44 45import java.lang.reflect.Field; 46import java.lang.reflect.Method; 47 48/** 49 * SlidingPaneLayout provides a horizontal, multi-pane layout for use at the top level 50 * of a UI. A left (or first) pane is treated as a content list or browser, subordinate to a 51 * primary detail view for displaying content. 52 * 53 * <p>Child views may overlap if their combined width exceeds the available width 54 * in the SlidingPaneLayout. When this occurs the user may slide the topmost view out of the way 55 * by dragging it, or by navigating in the direction of the overlapped view using a keyboard. 56 * If the content of the dragged child view is itself horizontally scrollable, the user may 57 * grab it by the very edge.</p> 58 * 59 * <p>Thanks to this sliding behavior, SlidingPaneLayout may be suitable for creating layouts 60 * that can smoothly adapt across many different screen sizes, expanding out fully on larger 61 * screens and collapsing on smaller screens.</p> 62 * 63 * <p>SlidingPaneLayout is distinct from a navigation drawer as described in the design 64 * guide and should not be used in the same scenarios. SlidingPaneLayout should be thought 65 * of only as a way to allow a two-pane layout normally used on larger screens to adapt to smaller 66 * screens in a natural way. The interaction patterns expressed by SlidingPaneLayout imply 67 * a physicality and direct information hierarchy between panes that does not necessarily exist 68 * in a scenario where a navigation drawer should be used instead.</p> 69 * 70 * <p>Appropriate uses of SlidingPaneLayout include pairings of panes such as a contact list and 71 * subordinate interactions with those contacts, or an email thread list with the content pane 72 * displaying the contents of the selected thread. Inappropriate uses of SlidingPaneLayout include 73 * switching between disparate functions of your app, such as jumping from a social stream view 74 * to a view of your personal profile - cases such as this should use the navigation drawer 75 * pattern instead. (TODO: insert doc link to nav drawer widget.)</p> 76 * 77 * <p>Like {@link android.widget.LinearLayout LinearLayout}, SlidingPaneLayout supports 78 * the use of the layout parameter <code>layout_weight</code> on child views to determine 79 * how to divide leftover space after measurement is complete. It is only relevant for width. 80 * When views do not overlap weight behaves as it does in a LinearLayout.</p> 81 * 82 * <p>When views do overlap, weight on a slideable pane indicates that the pane should be 83 * sized to fill all available space in the closed state. Weight on a pane that becomes covered 84 * indicates that the pane should be sized to fill all available space except a small minimum strip 85 * that the user may use to grab the slideable view and pull it back over into a closed state.</p> 86 * 87 * <p>Experimental. This class may be removed.</p> 88 */ 89public class SlidingPaneLayout extends ViewGroup { 90 private static final String TAG = "SlidingPaneLayout"; 91 92 /** 93 * Default size of the touch gutter along the edge where the user 94 * may grab and drag a sliding pane, even if its internal content 95 * may horizontally scroll. 96 */ 97 private static final int DEFAULT_GUTTER_SIZE = 16; // dp 98 99 /** 100 * Default size of the overhang for a pane in the open state. 101 * At least this much of a sliding pane will remain visible. 102 * This indicates that there is more content available and provides 103 * a "physical" edge to grab to pull it closed. 104 */ 105 private static final int DEFAULT_OVERHANG_SIZE = 32; // dp; 106 107 private static final int MAX_SETTLE_DURATION = 600; // ms 108 109 /** 110 * If no fade color is given by default it will fade to 80% gray. 111 */ 112 private static final int DEFAULT_FADE_COLOR = 0xcccccccc; 113 114 /** 115 * Base duration for programmatic scrolling of the sliding pane. 116 * This will be increased relative to the distance to be covered. 117 */ 118 private static final int BASE_SCROLL_DURATION = 256; // ms 119 120 /** 121 * The fade color used for the sliding panel. 0 = no fading. 122 */ 123 private int mSliderFadeColor = DEFAULT_FADE_COLOR; 124 125 /** 126 * The fade color used for the panel covered by the slider. 0 = no fading. 127 */ 128 private int mCoveredFadeColor; 129 130 /** 131 * Drawable used to draw the shadow between panes. 132 */ 133 private Drawable mShadowDrawable; 134 135 /** 136 * The size of the touch gutter in pixels 137 */ 138 private final int mGutterSize; 139 140 /** 141 * The size of the overhang in pixels. 142 * This is the minimum section of the sliding panel that will 143 * be visible in the open state to allow for a closing drag. 144 */ 145 private final int mOverhangSize; 146 147 /** 148 * True if a panel can slide with the current measurements 149 */ 150 private boolean mCanSlide; 151 152 /** 153 * The child view that can slide, if any. 154 */ 155 private View mSlideableView; 156 157 /** 158 * How far the panel is offset from its closed position. 159 * range [0, 1] where 0 = closed, 1 = open. 160 */ 161 private float mSlideOffset; 162 163 /** 164 * How far the non-sliding panel is parallaxed from its usual position when open. 165 * range [0, 1] 166 */ 167 private float mParallaxOffset; 168 169 /** 170 * How far in pixels the slideable panel may move. 171 */ 172 private int mSlideRange; 173 174 /** 175 * A panel view is locked into internal scrolling or another condition that 176 * is preventing a drag. 177 */ 178 private boolean mIsUnableToDrag; 179 180 /** 181 * Distance in pixels to parallax the fixed pane by when fully closed 182 */ 183 private int mParallaxBy; 184 185 private int mTouchSlop; 186 private float mInitialMotionX; 187 private float mInitialMotionY; 188 private float mLastMotionX; 189 private float mLastMotionY; 190 private int mActivePointerId = INVALID_POINTER; 191 192 private VelocityTracker mVelocityTracker; 193 private float mMaxVelocity; 194 195 private PanelSlideListener mPanelSlideListener; 196 197 private static final int INVALID_POINTER = -1; 198 199 /** 200 * Indicates that the panels are in an idle, settled state. The current panel 201 * is fully in view and no animation is in progress. 202 */ 203 public static final int SCROLL_STATE_IDLE = 0; 204 205 /** 206 * Indicates that a panel is currently being dragged by the user. 207 */ 208 public static final int SCROLL_STATE_DRAGGING = 1; 209 210 /** 211 * Indicates that a panel is in the process of settling to a final position. 212 */ 213 public static final int SCROLL_STATE_SETTLING = 2; 214 215 private int mScrollState = SCROLL_STATE_IDLE; 216 217 private final Rect mTmpRect = new Rect(); 218 219 /** 220 * Interpolator defining the animation curve for mScroller 221 */ 222 private static final Interpolator sInterpolator = new Interpolator() { 223 public float getInterpolation(float t) { 224 t -= 1.0f; 225 return t * t * t * t * t + 1.0f; 226 } 227 }; 228 229 /** 230 * Used to animate flinging panes. 231 */ 232 private final Scroller mScroller; 233 234 static final SlidingPanelLayoutImpl IMPL; 235 236 static { 237 final int deviceVersion = Build.VERSION.SDK_INT; 238 if (deviceVersion >= 17) { 239 IMPL = new SlidingPanelLayoutImplJBMR1(); 240 } else if (deviceVersion >= 16) { 241 IMPL = new SlidingPanelLayoutImplJB(); 242 } else { 243 IMPL = new SlidingPanelLayoutImplBase(); 244 } 245 } 246 247 /** 248 * Listener for monitoring events about sliding panes. 249 */ 250 public interface PanelSlideListener { 251 /** 252 * Called when a sliding pane's position changes. 253 * @param panel The child view that was moved 254 * @param slideOffset The new offset of this sliding pane within its range, from 0-1 255 */ 256 public void onPanelSlide(View panel, float slideOffset); 257 /** 258 * Called when a sliding pane becomes slid completely open. The pane may or may not 259 * be interactive at this point depending on how much of the pane is visible. 260 * @param panel The child view that was slid to an open position, revealing other panes 261 */ 262 public void onPanelOpened(View panel); 263 264 /** 265 * Called when a sliding pane becomes slid completely closed. The pane is now guaranteed 266 * to be interactive. It may now obscure other views in the layout. 267 * @param panel The child view that was slid to a closed position 268 */ 269 public void onPanelClosed(View panel); 270 } 271 272 /** 273 * No-op stubs for {@link PanelSlideListener}. If you only want to implement a subset 274 * of the listener methods you can extend this instead of implement the full interface. 275 */ 276 public static class SimplePanelSlideListener implements PanelSlideListener { 277 @Override 278 public void onPanelSlide(View panel, float slideOffset) { 279 } 280 @Override 281 public void onPanelOpened(View panel) { 282 } 283 @Override 284 public void onPanelClosed(View panel) { 285 } 286 } 287 288 public SlidingPaneLayout(Context context) { 289 this(context, null); 290 } 291 292 public SlidingPaneLayout(Context context, AttributeSet attrs) { 293 this(context, attrs, 0); 294 } 295 296 public SlidingPaneLayout(Context context, AttributeSet attrs, int defStyle) { 297 super(context, attrs, defStyle); 298 299 mScroller = new Scroller(context, sInterpolator); 300 301 final float density = context.getResources().getDisplayMetrics().density; 302 mGutterSize = (int) (DEFAULT_GUTTER_SIZE * density + 0.5f); 303 mOverhangSize = (int) (DEFAULT_OVERHANG_SIZE * density + 0.5f); 304 305 final ViewConfiguration viewConfig = ViewConfiguration.get(context); 306 mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(viewConfig); 307 mMaxVelocity = viewConfig.getScaledMaximumFlingVelocity(); 308 309 setWillNotDraw(false); 310 } 311 312 /** 313 * Set a distance to parallax the lower pane by when the upper pane is in its 314 * fully closed state. The lower pane will scroll between this position and 315 * its fully open state. 316 * 317 * @param parallaxBy Distance to parallax by in pixels 318 */ 319 public void setParallaxDistance(int parallaxBy) { 320 mParallaxBy = parallaxBy; 321 requestLayout(); 322 } 323 324 /** 325 * @return The distance the lower pane will parallax by when the upper pane is fully closed. 326 * 327 * @see #setParallaxDistance(int) 328 */ 329 public int getParallaxDistance() { 330 return mParallaxBy; 331 } 332 333 /** 334 * Set the color used to fade the sliding pane out when it is slid most of the way offscreen. 335 * 336 * @param color An ARGB-packed color value 337 */ 338 public void setSliderFadeColor(int color) { 339 mSliderFadeColor = color; 340 } 341 342 /** 343 * @return The ARGB-packed color value used to fade the sliding pane 344 */ 345 public int getSliderFadeColor() { 346 return mSliderFadeColor; 347 } 348 349 /** 350 * Set the color used to fade the pane covered by the sliding pane out when the pane 351 * will become fully covered in the closed state. 352 * 353 * @param color An ARGB-packed color value 354 */ 355 public void setCoveredFadeColor(int color) { 356 mCoveredFadeColor = color; 357 } 358 359 /** 360 * @return The ARGB-packed color value used to fade the fixed pane 361 */ 362 public int getCoveredFadeColor() { 363 return mCoveredFadeColor; 364 } 365 366 void setScrollState(int state) { 367 if (mScrollState != state) { 368 mScrollState = state; 369 } 370 } 371 372 public void setPanelSlideListener(PanelSlideListener listener) { 373 mPanelSlideListener = listener; 374 } 375 376 void dispatchOnPanelSlide(View panel) { 377 if (mPanelSlideListener != null) { 378 mPanelSlideListener.onPanelSlide(panel, mSlideOffset); 379 } 380 } 381 382 void dispatchOnPanelOpened(View panel) { 383 if (mPanelSlideListener != null) { 384 mPanelSlideListener.onPanelOpened(panel); 385 } 386 } 387 388 void dispatchOnPanelClosed(View panel) { 389 if (mPanelSlideListener != null) { 390 mPanelSlideListener.onPanelClosed(panel); 391 } 392 } 393 394 @Override 395 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 396 final int widthMode = MeasureSpec.getMode(widthMeasureSpec); 397 final int widthSize = MeasureSpec.getSize(widthMeasureSpec); 398 final int heightMode = MeasureSpec.getMode(heightMeasureSpec); 399 final int heightSize = MeasureSpec.getSize(heightMeasureSpec); 400 401 if (widthMode != MeasureSpec.EXACTLY) { 402 throw new IllegalStateException("Width must have an exact value or MATCH_PARENT"); 403 } else if (heightMode == MeasureSpec.UNSPECIFIED) { 404 throw new IllegalStateException("Height must not be UNSPECIFIED"); 405 } 406 407 int layoutHeight = 0; 408 int maxLayoutHeight = -1; 409 switch (heightMode) { 410 case MeasureSpec.EXACTLY: 411 layoutHeight = maxLayoutHeight = heightSize - getPaddingTop() - getPaddingBottom(); 412 break; 413 case MeasureSpec.AT_MOST: 414 maxLayoutHeight = heightSize - getPaddingTop() - getPaddingBottom(); 415 break; 416 } 417 418 float weightSum = 0; 419 boolean canSlide = false; 420 int widthRemaining = widthSize - getPaddingLeft() - getPaddingRight(); 421 final int childCount = getChildCount(); 422 423 if (childCount > 2) { 424 Log.e(TAG, "onMeasure: More than two child views are not supported."); 425 } 426 427 // We'll find the current one below. 428 mSlideableView = null; 429 430 // First pass. Measure based on child LayoutParams width/height. 431 // Weight will incur a second pass. 432 for (int i = 0; i < childCount; i++) { 433 final View child = getChildAt(i); 434 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 435 436 if (child.getVisibility() == GONE) { 437 lp.dimWhenOffset = false; 438 continue; 439 } 440 441 if (lp.weight > 0) { 442 weightSum += lp.weight; 443 444 // If we have no width, weight is the only contributor to the final size. 445 // Measure this view on the weight pass only. 446 if (lp.width == 0) continue; 447 } 448 449 int childWidthSpec; 450 final int horizontalMargin = lp.leftMargin + lp.rightMargin; 451 if (lp.width == LayoutParams.WRAP_CONTENT) { 452 childWidthSpec = MeasureSpec.makeMeasureSpec(widthSize - horizontalMargin, 453 MeasureSpec.AT_MOST); 454 } else if (lp.width == LayoutParams.FILL_PARENT) { 455 childWidthSpec = MeasureSpec.makeMeasureSpec(widthSize - horizontalMargin, 456 MeasureSpec.EXACTLY); 457 } else { 458 childWidthSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY); 459 } 460 461 int childHeightSpec; 462 if (lp.height == LayoutParams.WRAP_CONTENT) { 463 childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.AT_MOST); 464 } else if (lp.height == LayoutParams.FILL_PARENT) { 465 childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.EXACTLY); 466 } else { 467 childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY); 468 } 469 470 child.measure(childWidthSpec, childHeightSpec); 471 final int childWidth = child.getMeasuredWidth(); 472 final int childHeight = child.getMeasuredHeight(); 473 474 if (heightMode == MeasureSpec.AT_MOST && childHeight > layoutHeight) { 475 layoutHeight = Math.min(childHeight, maxLayoutHeight); 476 } 477 478 widthRemaining -= childWidth; 479 canSlide |= lp.slideable = widthRemaining < 0; 480 if (lp.slideable) { 481 mSlideableView = child; 482 } 483 } 484 485 // Resolve weight and make sure non-sliding panels are smaller than the full screen. 486 if (canSlide || weightSum > 0) { 487 final int fixedPanelWidthLimit = widthSize - mOverhangSize; 488 489 for (int i = 0; i < childCount; i++) { 490 final View child = getChildAt(i); 491 492 if (child.getVisibility() == GONE) { 493 continue; 494 } 495 496 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 497 498 final boolean skippedFirstPass = lp.width == 0 && lp.weight > 0; 499 final int measuredWidth = skippedFirstPass ? 0 : child.getMeasuredWidth(); 500 if (canSlide && child != mSlideableView) { 501 if (lp.width < 0 && (measuredWidth > fixedPanelWidthLimit || lp.weight > 0)) { 502 // Fixed panels in a sliding configuration should 503 // be clamped to the fixed panel limit. 504 final int childHeightSpec; 505 if (skippedFirstPass) { 506 // Do initial height measurement if we skipped measuring this view 507 // the first time around. 508 if (lp.height == LayoutParams.WRAP_CONTENT) { 509 childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, 510 MeasureSpec.AT_MOST); 511 } else if (lp.height == LayoutParams.FILL_PARENT) { 512 childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, 513 MeasureSpec.EXACTLY); 514 } else { 515 childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height, 516 MeasureSpec.EXACTLY); 517 } 518 } else { 519 childHeightSpec = MeasureSpec.makeMeasureSpec( 520 child.getMeasuredHeight(), MeasureSpec.EXACTLY); 521 } 522 final int childWidthSpec = MeasureSpec.makeMeasureSpec( 523 fixedPanelWidthLimit, MeasureSpec.EXACTLY); 524 child.measure(childWidthSpec, childHeightSpec); 525 } 526 } else if (lp.weight > 0) { 527 int childHeightSpec; 528 if (lp.width == 0) { 529 // This was skipped the first time; figure out a real height spec. 530 if (lp.height == LayoutParams.WRAP_CONTENT) { 531 childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, 532 MeasureSpec.AT_MOST); 533 } else if (lp.height == LayoutParams.FILL_PARENT) { 534 childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, 535 MeasureSpec.EXACTLY); 536 } else { 537 childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height, 538 MeasureSpec.EXACTLY); 539 } 540 } else { 541 childHeightSpec = MeasureSpec.makeMeasureSpec( 542 child.getMeasuredHeight(), MeasureSpec.EXACTLY); 543 } 544 545 if (canSlide) { 546 // Consume available space 547 final int horizontalMargin = lp.leftMargin + lp.rightMargin; 548 final int newWidth = widthSize - horizontalMargin; 549 final int childWidthSpec = MeasureSpec.makeMeasureSpec( 550 newWidth, MeasureSpec.EXACTLY); 551 if (measuredWidth != newWidth) { 552 child.measure(childWidthSpec, childHeightSpec); 553 } 554 } else { 555 // Distribute the extra width proportionally similar to LinearLayout 556 final int widthToDistribute = Math.max(0, widthRemaining); 557 final int addedWidth = (int) (lp.weight * widthToDistribute / weightSum); 558 final int childWidthSpec = MeasureSpec.makeMeasureSpec( 559 measuredWidth + addedWidth, MeasureSpec.EXACTLY); 560 child.measure(childWidthSpec, childHeightSpec); 561 } 562 } 563 } 564 } 565 566 setMeasuredDimension(widthSize, layoutHeight); 567 mCanSlide = canSlide; 568 if (mScrollState != SCROLL_STATE_IDLE && !canSlide) { 569 // Cancel scrolling in progress, it's no longer relevant. 570 setScrollState(SCROLL_STATE_IDLE); 571 } 572 } 573 574 @Override 575 protected void onLayout(boolean changed, int l, int t, int r, int b) { 576 final int width = r - l; 577 final int height = b - t; 578 final int paddingLeft = getPaddingLeft(); 579 final int paddingRight = getPaddingRight(); 580 final int paddingTop = getPaddingTop(); 581 final int paddingBottom = getPaddingBottom(); 582 583 final int childCount = getChildCount(); 584 int xStart = paddingLeft; 585 int nextXStart = xStart; 586 587 for (int i = 0; i < childCount; i++) { 588 final View child = getChildAt(i); 589 590 if (child.getVisibility() == GONE) { 591 continue; 592 } 593 594 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 595 596 final int childWidth = child.getMeasuredWidth(); 597 int offset = 0; 598 599 if (lp.slideable) { 600 final int margin = lp.leftMargin + lp.rightMargin; 601 final int range = Math.min(nextXStart, 602 width - paddingRight - mOverhangSize) - xStart - margin; 603 mSlideRange = range; 604 lp.dimWhenOffset = width - paddingRight - (xStart + range) < childWidth / 2; 605 xStart += (int) (range * mSlideOffset) + lp.leftMargin; 606 } else if (mCanSlide && mParallaxBy != 0) { 607 offset = (int) ((1 - mSlideOffset) * mParallaxBy); 608 xStart = nextXStart; 609 } else { 610 xStart = nextXStart; 611 } 612 613 final int childLeft = xStart - offset; 614 final int childRight = childLeft + childWidth; 615 final int childTop = paddingTop; 616 final int childBottom = childTop + child.getMeasuredHeight(); 617 child.layout(childLeft, paddingTop, childRight, childBottom); 618 619 nextXStart += child.getWidth(); 620 } 621 } 622 623 @Override 624 public boolean onInterceptTouchEvent(MotionEvent ev) { 625 final int action = ev.getAction() & MotionEventCompat.ACTION_MASK; 626 627 if (!mCanSlide || (mIsUnableToDrag && action != MotionEvent.ACTION_DOWN)) { 628 return super.onInterceptTouchEvent(ev); 629 } 630 631 if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { 632 mActivePointerId = INVALID_POINTER; 633 if (mVelocityTracker != null) { 634 mVelocityTracker.recycle(); 635 mVelocityTracker = null; 636 } 637 return false; 638 } 639 640 boolean interceptTap = false; 641 642 switch (action) { 643 case MotionEvent.ACTION_MOVE: { 644 final int activePointerId = mActivePointerId; 645 if (activePointerId == INVALID_POINTER) { 646 // No valid pointer = no valid drag. Ignore. 647 break; 648 } 649 650 final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId); 651 final float x = MotionEventCompat.getX(ev, pointerIndex); 652 final float dx = x - mLastMotionX; 653 final float xDiff = Math.abs(dx); 654 final float y = MotionEventCompat.getY(ev, pointerIndex); 655 final float yDiff = Math.abs(y - mLastMotionY); 656 657 if (dx != 0 && !isGutterDrag(mLastMotionX, dx) && 658 canScroll(this, false, (int) dx, (int) x, (int) y)) { 659 mInitialMotionX = mLastMotionX = x; 660 mLastMotionY = y; 661 mIsUnableToDrag = true; 662 return false; 663 } 664 if (xDiff > mTouchSlop && xDiff > yDiff && isSlideablePaneUnder(x, y)) { 665 mLastMotionX = dx > 0 ? mInitialMotionX + mTouchSlop : 666 mInitialMotionX - mTouchSlop; 667 setScrollState(SCROLL_STATE_DRAGGING); 668 } else if (yDiff > mTouchSlop) { 669 mIsUnableToDrag = true; 670 } 671 if (mScrollState == SCROLL_STATE_DRAGGING && performDrag(x)) { 672 invalidate(); 673 } 674 break; 675 } 676 677 case MotionEvent.ACTION_DOWN: { 678 final float x = ev.getX(); 679 final float y = ev.getY(); 680 mIsUnableToDrag = false; 681 mInitialMotionX = x; 682 mInitialMotionY = y; 683 mActivePointerId = MotionEventCompat.getPointerId(ev, 0); 684 if (isSlideablePaneUnder(x, y)) { 685 mLastMotionX = x; 686 mLastMotionY = y; 687 if (mScrollState == SCROLL_STATE_SETTLING) { 688 // Start dragging immediately. "Catch" 689 setScrollState(SCROLL_STATE_DRAGGING); 690 } else if (isDimmed(mSlideableView)) { 691 interceptTap = true; 692 } 693 } 694 break; 695 } 696 697 case MotionEventCompat.ACTION_POINTER_UP: 698 onSecondaryPointerUp(ev); 699 break; 700 } 701 702 if (mVelocityTracker == null) { 703 mVelocityTracker = VelocityTracker.obtain(); 704 } 705 mVelocityTracker.addMovement(ev); 706 return mScrollState == SCROLL_STATE_DRAGGING || interceptTap; 707 } 708 709 @Override 710 public boolean onTouchEvent(MotionEvent ev) { 711 if (!mCanSlide) { 712 return super.onTouchEvent(ev); 713 } 714 715 if (mVelocityTracker == null) { 716 mVelocityTracker = VelocityTracker.obtain(); 717 } 718 mVelocityTracker.addMovement(ev); 719 720 final int action = ev.getAction(); 721 boolean needsInvalidate = false; 722 boolean wantTouchEvents = true; 723 724 switch (action & MotionEventCompat.ACTION_MASK) { 725 case MotionEvent.ACTION_DOWN: { 726 final float x = ev.getX(); 727 final float y = ev.getY(); 728 mActivePointerId = MotionEventCompat.getPointerId(ev, 0); 729 mInitialMotionX = x; 730 mInitialMotionY = y; 731 732 if (isSlideablePaneUnder(x, y)) { 733 mScroller.abortAnimation(); 734 wantTouchEvents = true; 735 mLastMotionX = x; 736 setScrollState(SCROLL_STATE_DRAGGING); 737 } 738 break; 739 } 740 741 case MotionEvent.ACTION_MOVE: { 742 if (mScrollState != SCROLL_STATE_DRAGGING) { 743 final int pointerIndex = MotionEventCompat.findPointerIndex( 744 ev, mActivePointerId); 745 final float x = MotionEventCompat.getX(ev, pointerIndex); 746 final float y = MotionEventCompat.getY(ev, pointerIndex); 747 final float dx = Math.abs(x - mLastMotionX); 748 final float dy = Math.abs(y - mLastMotionY); 749 if (dx > mTouchSlop && dx > dy && isSlideablePaneUnder(x, y)) { 750 mLastMotionX = x - mInitialMotionX > 0 ? mInitialMotionX + mTouchSlop : 751 mInitialMotionX - mTouchSlop; 752 setScrollState(SCROLL_STATE_DRAGGING); 753 } 754 } 755 if (mScrollState == SCROLL_STATE_DRAGGING) { 756 final int activePointerIndex = MotionEventCompat.findPointerIndex( 757 ev, mActivePointerId); 758 final float x = MotionEventCompat.getX(ev, activePointerIndex); 759 needsInvalidate |= performDrag(x); 760 } 761 break; 762 } 763 764 case MotionEvent.ACTION_UP: { 765 if (isDimmed(mSlideableView)) { 766 final int pi = MotionEventCompat.findPointerIndex(ev, mActivePointerId); 767 final float x = MotionEventCompat.getX(ev, pi); 768 final float y = MotionEventCompat.getY(ev, pi); 769 final float dx = x - mInitialMotionX; 770 final float dy = y - mInitialMotionY; 771 if (dx * dx + dy * dy < mTouchSlop * mTouchSlop && isSlideablePaneUnder(x, y)) { 772 // Taps close a dimmed open pane. 773 closePane(mSlideableView, 0); 774 mActivePointerId = INVALID_POINTER; 775 break; 776 } 777 } 778 if (mScrollState == SCROLL_STATE_DRAGGING) { 779 final VelocityTracker vt = mVelocityTracker; 780 vt.computeCurrentVelocity(1000, mMaxVelocity); 781 int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(vt, 782 mActivePointerId); 783 if (initialVelocity < 0 || (initialVelocity == 0 && mSlideOffset < 0.5f)) { 784 closePane(mSlideableView, initialVelocity); 785 } else { 786 openPane(mSlideableView, initialVelocity); 787 } 788 mActivePointerId = INVALID_POINTER; 789 } 790 break; 791 } 792 793 case MotionEvent.ACTION_CANCEL: { 794 if (mScrollState == SCROLL_STATE_DRAGGING) { 795 mActivePointerId = INVALID_POINTER; 796 if (mSlideOffset < 0.5f) { 797 closePane(mSlideableView, 0); 798 } else { 799 openPane(mSlideableView, 0); 800 } 801 } 802 break; 803 } 804 805 case MotionEventCompat.ACTION_POINTER_DOWN: { 806 final int index = MotionEventCompat.getActionIndex(ev); 807 mLastMotionX = MotionEventCompat.getX(ev, index); 808 mLastMotionY = MotionEventCompat.getY(ev, index); 809 mActivePointerId = MotionEventCompat.getPointerId(ev, index); 810 break; 811 } 812 813 case MotionEventCompat.ACTION_POINTER_UP: { 814 onSecondaryPointerUp(ev); 815 break; 816 } 817 } 818 819 if (needsInvalidate) { 820 invalidate(); 821 } 822 return wantTouchEvents; 823 } 824 825 private void closePane(View pane, int initialVelocity) { 826 if (mCanSlide) { 827 smoothSlideTo(0.f, initialVelocity); 828 } 829 } 830 831 private void openPane(View pane, int initialVelocity) { 832 if (mCanSlide) { 833 smoothSlideTo(1.f, initialVelocity); 834 } 835 } 836 837 /** 838 * Animate the sliding panel to its open state. 839 */ 840 public void smoothSlideOpen() { 841 if (mCanSlide) { 842 openPane(mSlideableView, 0); 843 } 844 } 845 846 /** 847 * Animate the sliding panel to its closed state. 848 */ 849 public void smoothSlideClosed() { 850 if (mCanSlide) { 851 closePane(mSlideableView, 0); 852 } 853 } 854 855 /** 856 * @return true if sliding panels are completely open 857 */ 858 public boolean isOpen() { 859 return !mCanSlide || mSlideOffset == 1; 860 } 861 862 /** 863 * @return true if content in this layout can be slid open and closed 864 */ 865 public boolean canSlide() { 866 return mCanSlide; 867 } 868 869 private boolean performDrag(float x) { 870 final float dxMotion = x - mLastMotionX; 871 mLastMotionX = x; 872 873 final LayoutParams lp = (LayoutParams) mSlideableView.getLayoutParams(); 874 final int leftBound = getPaddingLeft() + lp.leftMargin; 875 final int rightBound = leftBound + mSlideRange; 876 877 final float oldLeft = mSlideableView.getLeft(); 878 final float newLeft = Math.min(Math.max(oldLeft + dxMotion, leftBound), rightBound); 879 880 final int dxPane = (int) (newLeft - oldLeft); 881 882 if (dxPane == 0) { 883 return false; 884 } 885 886 mSlideableView.offsetLeftAndRight(dxPane); 887 888 mSlideOffset = (newLeft - leftBound) / mSlideRange; 889 890 if (mParallaxBy != 0) { 891 parallaxOtherViews(mSlideOffset); 892 } 893 894 mLastMotionX += newLeft - (int) newLeft; 895 if (lp.dimWhenOffset) { 896 dimChildView(mSlideableView, mSlideOffset, mSliderFadeColor); 897 } 898 dispatchOnPanelSlide(mSlideableView); 899 900 return true; 901 } 902 903 private void dimChildView(View v, float mag, int fadeColor) { 904 final LayoutParams lp = (LayoutParams) v.getLayoutParams(); 905 906 if (mag > 0 && fadeColor != 0) { 907 final int baseAlpha = (fadeColor & 0xff000000) >>> 24; 908 int imag = (int) (baseAlpha * mag); 909 int color = imag << 24 | (fadeColor & 0xffffff); 910 if (lp.dimPaint == null) { 911 lp.dimPaint = new Paint(); 912 } 913 lp.dimPaint.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_OVER)); 914 if (ViewCompat.getLayerType(v) != ViewCompat.LAYER_TYPE_HARDWARE) { 915 ViewCompat.setLayerType(v, ViewCompat.LAYER_TYPE_HARDWARE, lp.dimPaint); 916 } 917 invalidateChildRegion(v); 918 } else if (ViewCompat.getLayerType(v) != ViewCompat.LAYER_TYPE_NONE) { 919 ViewCompat.setLayerType(v, ViewCompat.LAYER_TYPE_NONE, null); 920 } 921 } 922 923 @Override 924 protected boolean drawChild(Canvas canvas, View child, long drawingTime) { 925 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 926 boolean result; 927 final int save = canvas.save(Canvas.CLIP_SAVE_FLAG); 928 929 if (mCanSlide && !lp.slideable && mSlideableView != null) { 930 // Clip against the slider; no sense drawing what will immediately be covered. 931 canvas.getClipBounds(mTmpRect); 932 mTmpRect.right = Math.min(mTmpRect.right, mSlideableView.getLeft()); 933 canvas.clipRect(mTmpRect); 934 } 935 936 if (Build.VERSION.SDK_INT >= 11) { // HC 937 result = super.drawChild(canvas, child, drawingTime); 938 } else { 939 if (lp.dimWhenOffset && mSlideOffset > 0) { 940 if (!child.isDrawingCacheEnabled()) { 941 child.setDrawingCacheEnabled(true); 942 } 943 final Bitmap cache = child.getDrawingCache(); 944 canvas.drawBitmap(cache, child.getLeft(), child.getTop(), lp.dimPaint); 945 result = false; 946 } else { 947 if (child.isDrawingCacheEnabled()) { 948 child.setDrawingCacheEnabled(false); 949 } 950 result = super.drawChild(canvas, child, drawingTime); 951 } 952 } 953 954 canvas.restoreToCount(save); 955 956 return result; 957 } 958 959 private void invalidateChildRegion(View v) { 960 IMPL.invalidateChildRegion(this, v); 961 } 962 963 private boolean isGutterDrag(float x, float dx) { 964 return (x < mGutterSize && dx > 0) || (x > getWidth() - mGutterSize && dx < 0); 965 } 966 967 /** 968 * Smoothly animate mDraggingPane to the target X position within its range. 969 * 970 * @param slideOffset position to animate to 971 * @param velocity initial velocity in case of fling, or 0. 972 */ 973 void smoothSlideTo(float slideOffset, int velocity) { 974 if (!mCanSlide) { 975 // Nothing to do. 976 return; 977 } 978 979 final LayoutParams lp = (LayoutParams) mSlideableView.getLayoutParams(); 980 981 final int leftBound = getPaddingLeft() + lp.leftMargin; 982 int sx = mSlideableView.getLeft(); 983 int x = (int) (leftBound + slideOffset * mSlideRange); 984 int dx = x - sx; 985 if (dx == 0) { 986 setScrollState(SCROLL_STATE_IDLE); 987 if (mSlideOffset == 0) { 988 dispatchOnPanelClosed(mSlideableView); 989 } else { 990 dispatchOnPanelOpened(mSlideableView); 991 } 992 return; 993 } 994 995 setScrollState(SCROLL_STATE_SETTLING); 996 997 final int width = getWidth(); 998 final int halfWidth = width / 2; 999 final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / width); 1000 final float distance = halfWidth + halfWidth * 1001 distanceInfluenceForSnapDuration(distanceRatio); 1002 1003 int duration = 0; 1004 velocity = Math.abs(velocity); 1005 if (velocity > 0) { 1006 duration = 4 * Math.round(1000 * Math.abs(distance / velocity)); 1007 } else { 1008 final float range = (float) Math.abs(dx) / mSlideRange; 1009 duration = (int) ((range + 1) * BASE_SCROLL_DURATION); 1010 } 1011 duration = Math.min(duration, MAX_SETTLE_DURATION); 1012 1013 mScroller.startScroll(sx, 0, dx, 0, duration); 1014 ViewCompat.postInvalidateOnAnimation(this); 1015 } 1016 1017 // We want the duration of the page snap animation to be influenced by the distance that 1018 // the screen has to travel, however, we don't want this duration to be effected in a 1019 // purely linear fashion. Instead, we use this method to moderate the effect that the distance 1020 // of travel has on the overall snap duration. 1021 float distanceInfluenceForSnapDuration(float f) { 1022 f -= 0.5f; // center the values about 0. 1023 f *= 0.3f * Math.PI / 2.0f; 1024 return (float) Math.sin(f); 1025 } 1026 1027 @Override 1028 public void computeScroll() { 1029 if (!mScroller.isFinished() && mScroller.computeScrollOffset()) { 1030 if (!mCanSlide) { 1031 mScroller.abortAnimation(); 1032 return; 1033 } 1034 1035 final int oldLeft = mSlideableView.getLeft(); 1036 final int newLeft = mScroller.getCurrX(); 1037 final int dx = newLeft - oldLeft; 1038 mSlideableView.offsetLeftAndRight(dx); 1039 1040 final LayoutParams lp = (LayoutParams) mSlideableView.getLayoutParams(); 1041 final int leftBound = getPaddingLeft() + lp.leftMargin; 1042 mSlideOffset = (float) (newLeft - leftBound) / mSlideRange; 1043 if (lp.dimWhenOffset) { 1044 dimChildView(mSlideableView, mSlideOffset, mSliderFadeColor); 1045 } 1046 dispatchOnPanelSlide(mSlideableView); 1047 1048 if (mParallaxBy != 0) { 1049 parallaxOtherViews(mSlideOffset); 1050 } 1051 1052 if (newLeft == mScroller.getFinalX()) { 1053 mScroller.abortAnimation(); 1054 } 1055 1056 if (mScroller.isFinished()) { 1057 setScrollState(SCROLL_STATE_IDLE); 1058 post(new Runnable() { 1059 public void run() { 1060 if (mSlideOffset == 0) { 1061 dispatchOnPanelClosed(mSlideableView); 1062 } else { 1063 dispatchOnPanelOpened(mSlideableView); 1064 } 1065 } 1066 }); 1067 } 1068 ViewCompat.postInvalidateOnAnimation(this); 1069 } 1070 1071 } 1072 1073 /** 1074 * Set a drawable to use as a shadow cast by the right pane onto the left pane 1075 * during opening/closing. 1076 * 1077 * @param d drawable to use as a shadow 1078 */ 1079 public void setShadowDrawable(Drawable d) { 1080 mShadowDrawable = d; 1081 } 1082 1083 /** 1084 * Set a drawable to use as a shadow cast by the right pane onto the left pane 1085 * during opening/closing. 1086 * 1087 * @param resId Resource ID of a drawable to use 1088 */ 1089 public void setShadowResource(int resId) { 1090 setShadowDrawable(getResources().getDrawable(resId)); 1091 } 1092 1093 @Override 1094 public void draw(Canvas c) { 1095 super.draw(c); 1096 1097 final View shadowView = getChildCount() > 1 ? getChildAt(1) : null; 1098 if (shadowView == null || mShadowDrawable == null) { 1099 // No need to draw a shadow if we don't have one. 1100 return; 1101 } 1102 1103 final int shadowWidth = mShadowDrawable.getIntrinsicWidth(); 1104 final int right = shadowView.getLeft(); 1105 final int top = shadowView.getTop(); 1106 final int bottom = shadowView.getBottom(); 1107 final int left = right - shadowWidth; 1108 mShadowDrawable.setBounds(left, top, right, bottom); 1109 mShadowDrawable.draw(c); 1110 } 1111 1112 private void parallaxOtherViews(float slideOffset) { 1113 final LayoutParams slideLp = (LayoutParams) mSlideableView.getLayoutParams(); 1114 final boolean dimViews = slideLp.dimWhenOffset && slideLp.leftMargin <= 0; 1115 final int childCount = getChildCount(); 1116 for (int i = 0; i < childCount; i++) { 1117 final View v = getChildAt(i); 1118 if (v == mSlideableView) continue; 1119 1120 final int oldOffset = (int) ((1 - mParallaxOffset) * mParallaxBy); 1121 mParallaxOffset = slideOffset; 1122 final int newOffset = (int) ((1 - slideOffset) * mParallaxBy); 1123 final int dx = oldOffset - newOffset; 1124 1125 v.offsetLeftAndRight(dx); 1126 1127 if (dimViews) { 1128 dimChildView(v, 1 - mParallaxOffset, mCoveredFadeColor); 1129 } 1130 } 1131 } 1132 1133 /** 1134 * Tests scrollability within child views of v given a delta of dx. 1135 * 1136 * @param v View to test for horizontal scrollability 1137 * @param checkV Whether the view v passed should itself be checked for scrollability (true), 1138 * or just its children (false). 1139 * @param dx Delta scrolled in pixels 1140 * @param x X coordinate of the active touch point 1141 * @param y Y coordinate of the active touch point 1142 * @return true if child views of v can be scrolled by delta of dx. 1143 */ 1144 protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) { 1145 if (v instanceof ViewGroup) { 1146 final ViewGroup group = (ViewGroup) v; 1147 final int scrollX = v.getScrollX(); 1148 final int scrollY = v.getScrollY(); 1149 final int count = group.getChildCount(); 1150 // Count backwards - let topmost views consume scroll distance first. 1151 for (int i = count - 1; i >= 0; i--) { 1152 // TODO: Add versioned support here for transformed views. 1153 // This will not work for transformed views in Honeycomb+ 1154 final View child = group.getChildAt(i); 1155 if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() && 1156 y + scrollY >= child.getTop() && y + scrollY < child.getBottom() && 1157 canScroll(child, true, dx, x + scrollX - child.getLeft(), 1158 y + scrollY - child.getTop())) { 1159 return true; 1160 } 1161 } 1162 } 1163 1164 return checkV && ViewCompat.canScrollHorizontally(v, -dx); 1165 } 1166 1167 private void onSecondaryPointerUp(MotionEvent ev) { 1168 final int pointerIndex = MotionEventCompat.getActionIndex(ev); 1169 final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex); 1170 if (pointerId == mActivePointerId) { 1171 // This was our active pointer going up. Choose a new 1172 // active pointer and adjust accordingly. 1173 final int newPointerIndex = pointerIndex == 0 ? 1 : 0; 1174 mLastMotionX = MotionEventCompat.getX(ev, newPointerIndex); 1175 mLastMotionY = MotionEventCompat.getY(ev, newPointerIndex); 1176 mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex); 1177 if (mVelocityTracker != null) { 1178 mVelocityTracker.clear(); 1179 } 1180 } 1181 } 1182 1183 boolean isSlideablePaneUnder(float x, float y) { 1184 final View child = mSlideableView; 1185 return child != null && 1186 x >= child.getLeft() - mGutterSize && 1187 x < child.getRight() + mGutterSize && 1188 y >= child.getTop() && 1189 y < child.getBottom(); 1190 } 1191 1192 boolean isDimmed(View child) { 1193 if (child == null) { 1194 return false; 1195 } 1196 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 1197 return mCanSlide && lp.dimWhenOffset && mSlideOffset > 0; 1198 } 1199 1200 @Override 1201 protected ViewGroup.LayoutParams generateDefaultLayoutParams() { 1202 return new LayoutParams(); 1203 } 1204 1205 @Override 1206 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 1207 return p instanceof MarginLayoutParams 1208 ? new LayoutParams((MarginLayoutParams) p) 1209 : new LayoutParams(p); 1210 } 1211 1212 @Override 1213 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 1214 return p instanceof LayoutParams && super.checkLayoutParams(p); 1215 } 1216 1217 @Override 1218 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { 1219 return new LayoutParams(getContext(), attrs); 1220 } 1221 1222 public static class LayoutParams extends ViewGroup.MarginLayoutParams { 1223 private static final int[] ATTRS = new int[] { 1224 android.R.attr.layout_weight 1225 }; 1226 1227 /** 1228 * The weighted proportion of how much of the leftover space 1229 * this child should consume after measurement. 1230 */ 1231 public float weight = 0; 1232 1233 /** 1234 * True if this pane is the slideable pane in the layout. 1235 */ 1236 boolean slideable; 1237 1238 /** 1239 * True if this view should be drawn dimmed 1240 * when it's been offset from its default position. 1241 */ 1242 boolean dimWhenOffset; 1243 1244 Paint dimPaint; 1245 1246 public LayoutParams() { 1247 super(FILL_PARENT, FILL_PARENT); 1248 } 1249 1250 public LayoutParams(int width, int height) { 1251 super(width, height); 1252 } 1253 1254 public LayoutParams(android.view.ViewGroup.LayoutParams source) { 1255 super(source); 1256 } 1257 1258 public LayoutParams(MarginLayoutParams source) { 1259 super(source); 1260 } 1261 1262 public LayoutParams(LayoutParams source) { 1263 super(source); 1264 this.weight = source.weight; 1265 } 1266 1267 public LayoutParams(Context c, AttributeSet attrs) { 1268 super(c, attrs); 1269 1270 final TypedArray a = c.obtainStyledAttributes(attrs, ATTRS); 1271 this.weight = a.getFloat(0, 0); 1272 a.recycle(); 1273 } 1274 1275 } 1276 1277 static class SavedState extends BaseSavedState { 1278 boolean canSlide; 1279 boolean isOpen; 1280 1281 SavedState(Parcelable superState) { 1282 super(superState); 1283 } 1284 1285 private SavedState(Parcel in) { 1286 super(in); 1287 canSlide = in.readInt() != 0; 1288 isOpen = in.readInt() != 0; 1289 } 1290 1291 @Override 1292 public void writeToParcel(Parcel out, int flags) { 1293 super.writeToParcel(out, flags); 1294 out.writeInt(canSlide ? 1 : 0); 1295 out.writeInt(isOpen ? 1 : 0); 1296 } 1297 1298 public static final Parcelable.Creator<SavedState> CREATOR = 1299 new Parcelable.Creator<SavedState>() { 1300 public SavedState createFromParcel(Parcel in) { 1301 return new SavedState(in); 1302 } 1303 1304 public SavedState[] newArray(int size) { 1305 return new SavedState[size]; 1306 } 1307 }; 1308 } 1309 1310 interface SlidingPanelLayoutImpl { 1311 void invalidateChildRegion(SlidingPaneLayout parent, View child); 1312 } 1313 1314 static class SlidingPanelLayoutImplBase implements SlidingPanelLayoutImpl { 1315 public void invalidateChildRegion(SlidingPaneLayout parent, View child) { 1316 ViewCompat.postInvalidateOnAnimation(parent, child.getLeft(), child.getTop(), 1317 child.getRight(), child.getBottom()); 1318 } 1319 } 1320 1321 static class SlidingPanelLayoutImplJB extends SlidingPanelLayoutImplBase { 1322 /* 1323 * Private API hacks! Nasty! Bad! 1324 * 1325 * In Jellybean, some optimizations in the hardware UI renderer 1326 * prevent a changed Paint on a View using a hardware layer from having 1327 * the intended effect. This twiddles some internal bits on the view to force 1328 * it to recreate the display list. 1329 */ 1330 private Method mGetDisplayList; 1331 private Field mRecreateDisplayList; 1332 1333 SlidingPanelLayoutImplJB() { 1334 try { 1335 mGetDisplayList = View.class.getDeclaredMethod("getDisplayList", (Class[]) null); 1336 } catch (NoSuchMethodException e) { 1337 Log.e(TAG, "Couldn't fetch getDisplayList method; dimming won't work right.", e); 1338 } 1339 try { 1340 mRecreateDisplayList = View.class.getDeclaredField("mRecreateDisplayList"); 1341 mRecreateDisplayList.setAccessible(true); 1342 } catch (NoSuchFieldException e) { 1343 Log.e(TAG, "Couldn't fetch mRecreateDisplayList field; dimming will be slow.", e); 1344 } 1345 } 1346 1347 @Override 1348 public void invalidateChildRegion(SlidingPaneLayout parent, View child) { 1349 if (mGetDisplayList != null && mRecreateDisplayList != null) { 1350 try { 1351 mRecreateDisplayList.setBoolean(child, true); 1352 mGetDisplayList.invoke(child, (Object[]) null); 1353 } catch (Exception e) { 1354 Log.e(TAG, "Error refreshing display list state", e); 1355 } 1356 } else { 1357 // Slow path. REALLY slow path. Let's hope we don't get here. 1358 child.invalidate(); 1359 return; 1360 } 1361 super.invalidateChildRegion(parent, child); 1362 } 1363 } 1364 1365 static class SlidingPanelLayoutImplJBMR1 extends SlidingPanelLayoutImplBase { 1366 @Override 1367 public void invalidateChildRegion(SlidingPaneLayout parent, View child) { 1368 ViewCompat.setLayerPaint(child, ((LayoutParams) child.getLayoutParams()).dimPaint); 1369 } 1370 } 1371} 1372