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