SlidingPaneLayout.java revision f993018935168724fd4972edcf418fae09d81680
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 265 mDragHelper = ViewDragHelper.create(this, 0.5f, new DragHelperCallback()); 266 mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT); 267 mDragHelper.setMinVelocity(MIN_FLING_VELOCITY * density); 268 } 269 270 /** 271 * Set a distance to parallax the lower pane by when the upper pane is in its 272 * fully closed state. The lower pane will scroll between this position and 273 * its fully open state. 274 * 275 * @param parallaxBy Distance to parallax by in pixels 276 */ 277 public void setParallaxDistance(int parallaxBy) { 278 mParallaxBy = parallaxBy; 279 requestLayout(); 280 } 281 282 /** 283 * @return The distance the lower pane will parallax by when the upper pane is fully closed. 284 * 285 * @see #setParallaxDistance(int) 286 */ 287 public int getParallaxDistance() { 288 return mParallaxBy; 289 } 290 291 /** 292 * Set the color used to fade the sliding pane out when it is slid most of the way offscreen. 293 * 294 * @param color An ARGB-packed color value 295 */ 296 public void setSliderFadeColor(int color) { 297 mSliderFadeColor = color; 298 } 299 300 /** 301 * @return The ARGB-packed color value used to fade the sliding pane 302 */ 303 public int getSliderFadeColor() { 304 return mSliderFadeColor; 305 } 306 307 /** 308 * Set the color used to fade the pane covered by the sliding pane out when the pane 309 * will become fully covered in the closed state. 310 * 311 * @param color An ARGB-packed color value 312 */ 313 public void setCoveredFadeColor(int color) { 314 mCoveredFadeColor = color; 315 } 316 317 /** 318 * @return The ARGB-packed color value used to fade the fixed pane 319 */ 320 public int getCoveredFadeColor() { 321 return mCoveredFadeColor; 322 } 323 324 public void setPanelSlideListener(PanelSlideListener listener) { 325 mPanelSlideListener = listener; 326 } 327 328 void dispatchOnPanelSlide(View panel) { 329 if (mPanelSlideListener != null) { 330 mPanelSlideListener.onPanelSlide(panel, mSlideOffset); 331 } 332 } 333 334 void dispatchOnPanelOpened(View panel) { 335 if (mPanelSlideListener != null) { 336 mPanelSlideListener.onPanelOpened(panel); 337 } 338 sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); 339 } 340 341 void dispatchOnPanelClosed(View panel) { 342 if (mPanelSlideListener != null) { 343 mPanelSlideListener.onPanelClosed(panel); 344 } 345 sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); 346 } 347 348 void updateObscuredViewsVisibility(View panel) { 349 final int leftBound = getPaddingLeft(); 350 final int rightBound = getWidth() - getPaddingRight(); 351 final int topBound = getPaddingTop(); 352 final int bottomBound = getHeight() - getPaddingBottom(); 353 final int left; 354 final int right; 355 final int top; 356 final int bottom; 357 if (panel != null && hasOpaqueBackground(panel)) { 358 left = panel.getLeft(); 359 right = panel.getRight(); 360 top = panel.getTop(); 361 bottom = panel.getBottom(); 362 } else { 363 left = right = top = bottom = 0; 364 } 365 366 for (int i = 0, childCount = getChildCount(); i < childCount; i++) { 367 final View child = getChildAt(i); 368 369 if (child == panel) { 370 // There are still more children above the panel but they won't be affected. 371 break; 372 } 373 374 final int clampedChildLeft = Math.max(leftBound, child.getLeft()); 375 final int clampedChildTop = Math.max(topBound, child.getTop()); 376 final int clampedChildRight = Math.min(rightBound, child.getRight()); 377 final int clampedChildBottom = Math.min(bottomBound, child.getBottom()); 378 final int vis; 379 if (clampedChildLeft >= left && clampedChildTop >= top && 380 clampedChildRight <= right && clampedChildBottom <= bottom) { 381 vis = INVISIBLE; 382 } else { 383 vis = VISIBLE; 384 } 385 child.setVisibility(vis); 386 } 387 } 388 389 void setAllChildrenVisible() { 390 for (int i = 0, childCount = getChildCount(); i < childCount; i++) { 391 final View child = getChildAt(i); 392 if (child.getVisibility() == INVISIBLE) { 393 child.setVisibility(VISIBLE); 394 } 395 } 396 } 397 398 private static boolean hasOpaqueBackground(View v) { 399 final Drawable bg = v.getBackground(); 400 if (bg != null) { 401 return bg.getOpacity() == PixelFormat.OPAQUE; 402 } 403 return false; 404 } 405 406 @Override 407 protected void onAttachedToWindow() { 408 super.onAttachedToWindow(); 409 mFirstLayout = true; 410 } 411 412 @Override 413 protected void onDetachedFromWindow() { 414 super.onDetachedFromWindow(); 415 mFirstLayout = true; 416 417 for (int i = 0, count = mPostedRunnables.size(); i < count; i++) { 418 final DisableLayerRunnable dlr = mPostedRunnables.get(i); 419 dlr.run(); 420 } 421 mPostedRunnables.clear(); 422 } 423 424 @Override 425 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 426 final int widthMode = MeasureSpec.getMode(widthMeasureSpec); 427 final int widthSize = MeasureSpec.getSize(widthMeasureSpec); 428 final int heightMode = MeasureSpec.getMode(heightMeasureSpec); 429 final int heightSize = MeasureSpec.getSize(heightMeasureSpec); 430 431 if (widthMode != MeasureSpec.EXACTLY) { 432 throw new IllegalStateException("Width must have an exact value or MATCH_PARENT"); 433 } else if (heightMode == MeasureSpec.UNSPECIFIED) { 434 throw new IllegalStateException("Height must not be UNSPECIFIED"); 435 } 436 437 int layoutHeight = 0; 438 int maxLayoutHeight = -1; 439 switch (heightMode) { 440 case MeasureSpec.EXACTLY: 441 layoutHeight = maxLayoutHeight = heightSize - getPaddingTop() - getPaddingBottom(); 442 break; 443 case MeasureSpec.AT_MOST: 444 maxLayoutHeight = heightSize - getPaddingTop() - getPaddingBottom(); 445 break; 446 } 447 448 float weightSum = 0; 449 boolean canSlide = false; 450 int widthRemaining = widthSize - getPaddingLeft() - getPaddingRight(); 451 final int childCount = getChildCount(); 452 453 if (childCount > 2) { 454 Log.e(TAG, "onMeasure: More than two child views are not supported."); 455 } 456 457 // We'll find the current one below. 458 mSlideableView = null; 459 460 // First pass. Measure based on child LayoutParams width/height. 461 // Weight will incur a second pass. 462 for (int i = 0; i < childCount; i++) { 463 final View child = getChildAt(i); 464 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 465 466 if (child.getVisibility() == GONE) { 467 lp.dimWhenOffset = false; 468 continue; 469 } 470 471 if (lp.weight > 0) { 472 weightSum += lp.weight; 473 474 // If we have no width, weight is the only contributor to the final size. 475 // Measure this view on the weight pass only. 476 if (lp.width == 0) continue; 477 } 478 479 int childWidthSpec; 480 final int horizontalMargin = lp.leftMargin + lp.rightMargin; 481 if (lp.width == LayoutParams.WRAP_CONTENT) { 482 childWidthSpec = MeasureSpec.makeMeasureSpec(widthSize - horizontalMargin, 483 MeasureSpec.AT_MOST); 484 } else if (lp.width == LayoutParams.FILL_PARENT) { 485 childWidthSpec = MeasureSpec.makeMeasureSpec(widthSize - horizontalMargin, 486 MeasureSpec.EXACTLY); 487 } else { 488 childWidthSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY); 489 } 490 491 int childHeightSpec; 492 if (lp.height == LayoutParams.WRAP_CONTENT) { 493 childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.AT_MOST); 494 } else if (lp.height == LayoutParams.FILL_PARENT) { 495 childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.EXACTLY); 496 } else { 497 childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY); 498 } 499 500 child.measure(childWidthSpec, childHeightSpec); 501 final int childWidth = child.getMeasuredWidth(); 502 final int childHeight = child.getMeasuredHeight(); 503 504 if (heightMode == MeasureSpec.AT_MOST && childHeight > layoutHeight) { 505 layoutHeight = Math.min(childHeight, maxLayoutHeight); 506 } 507 508 widthRemaining -= childWidth; 509 canSlide |= lp.slideable = widthRemaining < 0; 510 if (lp.slideable) { 511 mSlideableView = child; 512 } 513 } 514 515 // Resolve weight and make sure non-sliding panels are smaller than the full screen. 516 if (canSlide || weightSum > 0) { 517 final int fixedPanelWidthLimit = widthSize - mOverhangSize; 518 519 for (int i = 0; i < childCount; i++) { 520 final View child = getChildAt(i); 521 522 if (child.getVisibility() == GONE) { 523 continue; 524 } 525 526 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 527 528 final boolean skippedFirstPass = lp.width == 0 && lp.weight > 0; 529 final int measuredWidth = skippedFirstPass ? 0 : child.getMeasuredWidth(); 530 if (canSlide && child != mSlideableView) { 531 if (lp.width < 0 && (measuredWidth > fixedPanelWidthLimit || lp.weight > 0)) { 532 // Fixed panels in a sliding configuration should 533 // be clamped to the fixed panel limit. 534 final int childHeightSpec; 535 if (skippedFirstPass) { 536 // Do initial height measurement if we skipped measuring this view 537 // the first time around. 538 if (lp.height == LayoutParams.WRAP_CONTENT) { 539 childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, 540 MeasureSpec.AT_MOST); 541 } else if (lp.height == LayoutParams.FILL_PARENT) { 542 childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, 543 MeasureSpec.EXACTLY); 544 } else { 545 childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height, 546 MeasureSpec.EXACTLY); 547 } 548 } else { 549 childHeightSpec = MeasureSpec.makeMeasureSpec( 550 child.getMeasuredHeight(), MeasureSpec.EXACTLY); 551 } 552 final int childWidthSpec = MeasureSpec.makeMeasureSpec( 553 fixedPanelWidthLimit, MeasureSpec.EXACTLY); 554 child.measure(childWidthSpec, childHeightSpec); 555 } 556 } else if (lp.weight > 0) { 557 int childHeightSpec; 558 if (lp.width == 0) { 559 // This was skipped the first time; figure out a real height spec. 560 if (lp.height == LayoutParams.WRAP_CONTENT) { 561 childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, 562 MeasureSpec.AT_MOST); 563 } else if (lp.height == LayoutParams.FILL_PARENT) { 564 childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, 565 MeasureSpec.EXACTLY); 566 } else { 567 childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height, 568 MeasureSpec.EXACTLY); 569 } 570 } else { 571 childHeightSpec = MeasureSpec.makeMeasureSpec( 572 child.getMeasuredHeight(), MeasureSpec.EXACTLY); 573 } 574 575 if (canSlide) { 576 // Consume available space 577 final int horizontalMargin = lp.leftMargin + lp.rightMargin; 578 final int newWidth = widthSize - horizontalMargin; 579 final int childWidthSpec = MeasureSpec.makeMeasureSpec( 580 newWidth, MeasureSpec.EXACTLY); 581 if (measuredWidth != newWidth) { 582 child.measure(childWidthSpec, childHeightSpec); 583 } 584 } else { 585 // Distribute the extra width proportionally similar to LinearLayout 586 final int widthToDistribute = Math.max(0, widthRemaining); 587 final int addedWidth = (int) (lp.weight * widthToDistribute / weightSum); 588 final int childWidthSpec = MeasureSpec.makeMeasureSpec( 589 measuredWidth + addedWidth, MeasureSpec.EXACTLY); 590 child.measure(childWidthSpec, childHeightSpec); 591 } 592 } 593 } 594 } 595 596 setMeasuredDimension(widthSize, layoutHeight); 597 mCanSlide = canSlide; 598 if (mDragHelper.getViewDragState() != ViewDragHelper.STATE_IDLE && !canSlide) { 599 // Cancel scrolling in progress, it's no longer relevant. 600 mDragHelper.abort(); 601 } 602 } 603 604 @Override 605 protected void onLayout(boolean changed, int l, int t, int r, int b) { 606 final int width = r - l; 607 final int paddingLeft = getPaddingLeft(); 608 final int paddingRight = getPaddingRight(); 609 final int paddingTop = getPaddingTop(); 610 611 final int childCount = getChildCount(); 612 int xStart = paddingLeft; 613 int nextXStart = xStart; 614 615 if (mFirstLayout) { 616 mSlideOffset = mCanSlide && mPreservedOpenState ? 1.f : 0.f; 617 } 618 619 for (int i = 0; i < childCount; i++) { 620 final View child = getChildAt(i); 621 622 if (child.getVisibility() == GONE) { 623 continue; 624 } 625 626 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 627 628 final int childWidth = child.getMeasuredWidth(); 629 int offset = 0; 630 631 if (lp.slideable) { 632 final int margin = lp.leftMargin + lp.rightMargin; 633 final int range = Math.min(nextXStart, 634 width - paddingRight - mOverhangSize) - xStart - margin; 635 mSlideRange = range; 636 lp.dimWhenOffset = xStart + lp.leftMargin + range + childWidth / 2 > 637 width - paddingRight; 638 xStart += (int) (range * mSlideOffset) + lp.leftMargin; 639 } else if (mCanSlide && mParallaxBy != 0) { 640 offset = (int) ((1 - mSlideOffset) * mParallaxBy); 641 xStart = nextXStart; 642 } else { 643 xStart = nextXStart; 644 } 645 646 final int childLeft = xStart - offset; 647 final int childRight = childLeft + childWidth; 648 final int childTop = paddingTop; 649 final int childBottom = childTop + child.getMeasuredHeight(); 650 child.layout(childLeft, paddingTop, childRight, childBottom); 651 652 nextXStart += child.getWidth(); 653 } 654 655 if (mFirstLayout) { 656 if (mCanSlide) { 657 if (mParallaxBy != 0) { 658 parallaxOtherViews(mSlideOffset); 659 } 660 if (((LayoutParams) mSlideableView.getLayoutParams()).dimWhenOffset) { 661 dimChildView(mSlideableView, mSlideOffset, mSliderFadeColor); 662 } 663 } else { 664 // Reset the dim level of all children; it's irrelevant when nothing moves. 665 for (int i = 0; i < childCount; i++) { 666 dimChildView(getChildAt(i), 0, mSliderFadeColor); 667 } 668 } 669 updateObscuredViewsVisibility(mSlideableView); 670 } 671 672 mFirstLayout = false; 673 } 674 675 @Override 676 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 677 super.onSizeChanged(w, h, oldw, oldh); 678 // Recalculate sliding panes and their details 679 if (w != oldw) { 680 mFirstLayout = true; 681 } 682 } 683 684 @Override 685 public void requestChildFocus(View child, View focused) { 686 super.requestChildFocus(child, focused); 687 if (!isInTouchMode() && !mCanSlide) { 688 mPreservedOpenState = child == mSlideableView; 689 } 690 } 691 692 @Override 693 public boolean onInterceptTouchEvent(MotionEvent ev) { 694 final int action = MotionEventCompat.getActionMasked(ev); 695 696 // Preserve the open state based on the last view that was touched. 697 if (!mCanSlide && action == MotionEvent.ACTION_DOWN && getChildCount() > 1) { 698 // After the first things will be slideable. 699 final View secondChild = getChildAt(1); 700 if (secondChild != null) { 701 mPreservedOpenState = !mDragHelper.isViewUnder(secondChild, 702 (int) ev.getX(), (int) ev.getY()); 703 } 704 } 705 706 if (!mCanSlide || (mIsUnableToDrag && action != MotionEvent.ACTION_DOWN)) { 707 mDragHelper.cancel(); 708 return super.onInterceptTouchEvent(ev); 709 } 710 711 if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { 712 mDragHelper.cancel(); 713 return false; 714 } 715 716 boolean interceptTap = false; 717 718 switch (action) { 719 case MotionEvent.ACTION_DOWN: { 720 mIsUnableToDrag = false; 721 final float x = ev.getX(); 722 final float y = ev.getY(); 723 mInitialMotionX = x; 724 mInitialMotionY = y; 725 726 if (mDragHelper.isViewUnder(mSlideableView, (int) x, (int) y) && 727 isDimmed(mSlideableView)) { 728 interceptTap = true; 729 } 730 break; 731 } 732 733 case MotionEvent.ACTION_MOVE: { 734 final float x = ev.getX(); 735 final float y = ev.getY(); 736 final float adx = Math.abs(x - mInitialMotionX); 737 final float ady = Math.abs(y - mInitialMotionY); 738 final int slop = mDragHelper.getTouchSlop(); 739 if (adx > slop && ady > adx) { 740 mDragHelper.cancel(); 741 mIsUnableToDrag = true; 742 return false; 743 } 744 } 745 } 746 747 final boolean interceptForDrag = mDragHelper.shouldInterceptTouchEvent(ev); 748 749 return interceptForDrag || interceptTap; 750 } 751 752 @Override 753 public boolean onTouchEvent(MotionEvent ev) { 754 if (!mCanSlide) { 755 return super.onTouchEvent(ev); 756 } 757 758 mDragHelper.processTouchEvent(ev); 759 760 final int action = ev.getAction(); 761 boolean wantTouchEvents = true; 762 763 switch (action & MotionEventCompat.ACTION_MASK) { 764 case MotionEvent.ACTION_DOWN: { 765 final float x = ev.getX(); 766 final float y = ev.getY(); 767 mInitialMotionX = x; 768 mInitialMotionY = y; 769 break; 770 } 771 772 case MotionEvent.ACTION_UP: { 773 if (isDimmed(mSlideableView)) { 774 final float x = ev.getX(); 775 final float y = ev.getY(); 776 final float dx = x - mInitialMotionX; 777 final float dy = y - mInitialMotionY; 778 final int slop = mDragHelper.getTouchSlop(); 779 if (dx * dx + dy * dy < slop * slop && 780 mDragHelper.isViewUnder(mSlideableView, (int) x, (int) y)) { 781 // Taps close a dimmed open pane. 782 closePane(mSlideableView, 0); 783 break; 784 } 785 } 786 break; 787 } 788 } 789 790 return wantTouchEvents; 791 } 792 793 private boolean closePane(View pane, int initialVelocity) { 794 if (mFirstLayout || smoothSlideTo(0.f, initialVelocity)) { 795 mPreservedOpenState = false; 796 return true; 797 } 798 return false; 799 } 800 801 private boolean openPane(View pane, int initialVelocity) { 802 if (mFirstLayout || smoothSlideTo(1.f, initialVelocity)) { 803 mPreservedOpenState = true; 804 return true; 805 } 806 return false; 807 } 808 809 /** 810 * @deprecated Renamed to {@link #openPane()} - this method is going away soon! 811 */ 812 @Deprecated 813 public void smoothSlideOpen() { 814 openPane(); 815 } 816 817 /** 818 * Open the sliding pane if it is currently slideable. If first layout 819 * has already completed this will animate. 820 * 821 * @return true if the pane was slideable and is now open/in the process of opening 822 */ 823 public boolean openPane() { 824 return openPane(mSlideableView, 0); 825 } 826 827 /** 828 * @deprecated Renamed to {@link #closePane()} - this method is going away soon! 829 */ 830 @Deprecated 831 public void smoothSlideClosed() { 832 closePane(); 833 } 834 835 /** 836 * Close the sliding pane if it is currently slideable. If first layout 837 * has already completed this will animate. 838 * 839 * @return true if the pane was slideable and is now closed/in the process of closing 840 */ 841 public boolean closePane() { 842 return closePane(mSlideableView, 0); 843 } 844 845 /** 846 * Check if the layout is completely open. It can be open either because the slider 847 * itself is open revealing the left pane, or if all content fits without sliding. 848 * 849 * @return true if sliding panels are completely open 850 */ 851 public boolean isOpen() { 852 return !mCanSlide || mSlideOffset == 1; 853 } 854 855 /** 856 * @return true if content in this layout can be slid open and closed 857 * @deprecated Renamed to {@link #isSlideable()} - this method is going away soon! 858 */ 859 @Deprecated 860 public boolean canSlide() { 861 return mCanSlide; 862 } 863 864 /** 865 * Check if the content in this layout cannot fully fit side by side and therefore 866 * the content pane can be slid back and forth. 867 * 868 * @return true if content in this layout can be slid open and closed 869 */ 870 public boolean isSlideable() { 871 return mCanSlide; 872 } 873 874 private void onPanelDragged(int newLeft) { 875 final LayoutParams lp = (LayoutParams) mSlideableView.getLayoutParams(); 876 final int leftBound = getPaddingLeft() + lp.leftMargin; 877 878 mSlideOffset = (float) (newLeft - leftBound) / mSlideRange; 879 880 if (mParallaxBy != 0) { 881 parallaxOtherViews(mSlideOffset); 882 } 883 884 if (lp.dimWhenOffset) { 885 dimChildView(mSlideableView, mSlideOffset, mSliderFadeColor); 886 } 887 dispatchOnPanelSlide(mSlideableView); 888 } 889 890 private void dimChildView(View v, float mag, int fadeColor) { 891 final LayoutParams lp = (LayoutParams) v.getLayoutParams(); 892 893 if (mag > 0 && fadeColor != 0) { 894 final int baseAlpha = (fadeColor & 0xff000000) >>> 24; 895 int imag = (int) (baseAlpha * mag); 896 int color = imag << 24 | (fadeColor & 0xffffff); 897 if (lp.dimPaint == null) { 898 lp.dimPaint = new Paint(); 899 } 900 lp.dimPaint.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_OVER)); 901 if (ViewCompat.getLayerType(v) != ViewCompat.LAYER_TYPE_HARDWARE) { 902 ViewCompat.setLayerType(v, ViewCompat.LAYER_TYPE_HARDWARE, lp.dimPaint); 903 } 904 invalidateChildRegion(v); 905 } else if (ViewCompat.getLayerType(v) != ViewCompat.LAYER_TYPE_NONE) { 906 if (lp.dimPaint != null) { 907 lp.dimPaint.setColorFilter(null); 908 } 909 final DisableLayerRunnable dlr = new DisableLayerRunnable(v); 910 mPostedRunnables.add(dlr); 911 ViewCompat.postOnAnimation(this, dlr); 912 } 913 } 914 915 @Override 916 protected boolean drawChild(Canvas canvas, View child, long drawingTime) { 917 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 918 boolean result; 919 final int save = canvas.save(Canvas.CLIP_SAVE_FLAG); 920 921 if (mCanSlide && !lp.slideable && mSlideableView != null) { 922 // Clip against the slider; no sense drawing what will immediately be covered. 923 canvas.getClipBounds(mTmpRect); 924 mTmpRect.right = Math.min(mTmpRect.right, mSlideableView.getLeft()); 925 canvas.clipRect(mTmpRect); 926 } 927 928 if (Build.VERSION.SDK_INT >= 11) { // HC 929 result = super.drawChild(canvas, child, drawingTime); 930 } else { 931 if (lp.dimWhenOffset && mSlideOffset > 0) { 932 if (!child.isDrawingCacheEnabled()) { 933 child.setDrawingCacheEnabled(true); 934 } 935 final Bitmap cache = child.getDrawingCache(); 936 if (cache != null) { 937 canvas.drawBitmap(cache, child.getLeft(), child.getTop(), lp.dimPaint); 938 result = false; 939 } else { 940 Log.e(TAG, "drawChild: child view " + child + " returned null drawing cache"); 941 result = super.drawChild(canvas, child, drawingTime); 942 } 943 } else { 944 if (child.isDrawingCacheEnabled()) { 945 child.setDrawingCacheEnabled(false); 946 } 947 result = super.drawChild(canvas, child, drawingTime); 948 } 949 } 950 951 canvas.restoreToCount(save); 952 953 return result; 954 } 955 956 private void invalidateChildRegion(View v) { 957 IMPL.invalidateChildRegion(this, v); 958 } 959 960 /** 961 * Smoothly animate mDraggingPane to the target X position within its range. 962 * 963 * @param slideOffset position to animate to 964 * @param velocity initial velocity in case of fling, or 0. 965 */ 966 boolean smoothSlideTo(float slideOffset, int velocity) { 967 if (!mCanSlide) { 968 // Nothing to do. 969 return false; 970 } 971 972 final LayoutParams lp = (LayoutParams) mSlideableView.getLayoutParams(); 973 974 final int leftBound = getPaddingLeft() + lp.leftMargin; 975 int x = (int) (leftBound + slideOffset * mSlideRange); 976 977 if (mDragHelper.smoothSlideViewTo(mSlideableView, x, mSlideableView.getTop())) { 978 setAllChildrenVisible(); 979 ViewCompat.postInvalidateOnAnimation(this); 980 return true; 981 } 982 return false; 983 } 984 985 @Override 986 public void computeScroll() { 987 if (mDragHelper.continueSettling(true)) { 988 if (!mCanSlide) { 989 mDragHelper.abort(); 990 return; 991 } 992 993 ViewCompat.postInvalidateOnAnimation(this); 994 } 995 } 996 997 /** 998 * Set a drawable to use as a shadow cast by the right pane onto the left pane 999 * during opening/closing. 1000 * 1001 * @param d drawable to use as a shadow 1002 */ 1003 public void setShadowDrawable(Drawable d) { 1004 mShadowDrawable = d; 1005 } 1006 1007 /** 1008 * Set a drawable to use as a shadow cast by the right pane onto the left pane 1009 * during opening/closing. 1010 * 1011 * @param resId Resource ID of a drawable to use 1012 */ 1013 public void setShadowResource(int resId) { 1014 setShadowDrawable(getResources().getDrawable(resId)); 1015 } 1016 1017 @Override 1018 public void draw(Canvas c) { 1019 super.draw(c); 1020 1021 final View shadowView = getChildCount() > 1 ? getChildAt(1) : null; 1022 if (shadowView == null || mShadowDrawable == null) { 1023 // No need to draw a shadow if we don't have one. 1024 return; 1025 } 1026 1027 final int shadowWidth = mShadowDrawable.getIntrinsicWidth(); 1028 final int right = shadowView.getLeft(); 1029 final int top = shadowView.getTop(); 1030 final int bottom = shadowView.getBottom(); 1031 final int left = right - shadowWidth; 1032 mShadowDrawable.setBounds(left, top, right, bottom); 1033 mShadowDrawable.draw(c); 1034 } 1035 1036 private void parallaxOtherViews(float slideOffset) { 1037 final LayoutParams slideLp = (LayoutParams) mSlideableView.getLayoutParams(); 1038 final boolean dimViews = slideLp.dimWhenOffset && slideLp.leftMargin <= 0; 1039 final int childCount = getChildCount(); 1040 for (int i = 0; i < childCount; i++) { 1041 final View v = getChildAt(i); 1042 if (v == mSlideableView) continue; 1043 1044 final int oldOffset = (int) ((1 - mParallaxOffset) * mParallaxBy); 1045 mParallaxOffset = slideOffset; 1046 final int newOffset = (int) ((1 - slideOffset) * mParallaxBy); 1047 final int dx = oldOffset - newOffset; 1048 1049 v.offsetLeftAndRight(dx); 1050 1051 if (dimViews) { 1052 dimChildView(v, 1 - mParallaxOffset, mCoveredFadeColor); 1053 } 1054 } 1055 } 1056 1057 /** 1058 * Tests scrollability within child views of v given a delta of dx. 1059 * 1060 * @param v View to test for horizontal scrollability 1061 * @param checkV Whether the view v passed should itself be checked for scrollability (true), 1062 * or just its children (false). 1063 * @param dx Delta scrolled in pixels 1064 * @param x X coordinate of the active touch point 1065 * @param y Y coordinate of the active touch point 1066 * @return true if child views of v can be scrolled by delta of dx. 1067 */ 1068 protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) { 1069 if (v instanceof ViewGroup) { 1070 final ViewGroup group = (ViewGroup) v; 1071 final int scrollX = v.getScrollX(); 1072 final int scrollY = v.getScrollY(); 1073 final int count = group.getChildCount(); 1074 // Count backwards - let topmost views consume scroll distance first. 1075 for (int i = count - 1; i >= 0; i--) { 1076 // TODO: Add versioned support here for transformed views. 1077 // This will not work for transformed views in Honeycomb+ 1078 final View child = group.getChildAt(i); 1079 if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() && 1080 y + scrollY >= child.getTop() && y + scrollY < child.getBottom() && 1081 canScroll(child, true, dx, x + scrollX - child.getLeft(), 1082 y + scrollY - child.getTop())) { 1083 return true; 1084 } 1085 } 1086 } 1087 1088 return checkV && ViewCompat.canScrollHorizontally(v, -dx); 1089 } 1090 1091 boolean isDimmed(View child) { 1092 if (child == null) { 1093 return false; 1094 } 1095 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 1096 return mCanSlide && lp.dimWhenOffset && mSlideOffset > 0; 1097 } 1098 1099 @Override 1100 protected ViewGroup.LayoutParams generateDefaultLayoutParams() { 1101 return new LayoutParams(); 1102 } 1103 1104 @Override 1105 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 1106 return p instanceof MarginLayoutParams 1107 ? new LayoutParams((MarginLayoutParams) p) 1108 : new LayoutParams(p); 1109 } 1110 1111 @Override 1112 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 1113 return p instanceof LayoutParams && super.checkLayoutParams(p); 1114 } 1115 1116 @Override 1117 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { 1118 return new LayoutParams(getContext(), attrs); 1119 } 1120 1121 @Override 1122 protected Parcelable onSaveInstanceState() { 1123 Parcelable superState = super.onSaveInstanceState(); 1124 1125 SavedState ss = new SavedState(superState); 1126 ss.isOpen = isSlideable() ? isOpen() : mPreservedOpenState; 1127 1128 return ss; 1129 } 1130 1131 @Override 1132 protected void onRestoreInstanceState(Parcelable state) { 1133 SavedState ss = (SavedState) state; 1134 super.onRestoreInstanceState(ss.getSuperState()); 1135 1136 if (ss.isOpen) { 1137 openPane(); 1138 } else { 1139 closePane(); 1140 } 1141 mPreservedOpenState = ss.isOpen; 1142 } 1143 1144 private class DragHelperCallback extends ViewDragHelper.Callback { 1145 1146 @Override 1147 public boolean tryCaptureView(View child, int pointerId) { 1148 if (mIsUnableToDrag) { 1149 return false; 1150 } 1151 1152 return ((LayoutParams) child.getLayoutParams()).slideable; 1153 } 1154 1155 @Override 1156 public void onViewDragStateChanged(int state) { 1157 if (mDragHelper.getViewDragState() == ViewDragHelper.STATE_IDLE) { 1158 if (mSlideOffset == 0) { 1159 updateObscuredViewsVisibility(mSlideableView); 1160 dispatchOnPanelClosed(mSlideableView); 1161 mPreservedOpenState = false; 1162 } else { 1163 dispatchOnPanelOpened(mSlideableView); 1164 mPreservedOpenState = true; 1165 } 1166 } 1167 } 1168 1169 @Override 1170 public void onViewCaptured(View capturedChild, int activePointerId) { 1171 // Make all child views visible in preparation for sliding things around 1172 setAllChildrenVisible(); 1173 } 1174 1175 @Override 1176 public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { 1177 onPanelDragged(left); 1178 invalidate(); 1179 } 1180 1181 @Override 1182 public void onViewReleased(View releasedChild, float xvel, float yvel) { 1183 final LayoutParams lp = (LayoutParams) releasedChild.getLayoutParams(); 1184 int left = getPaddingLeft() + lp.leftMargin; 1185 if (xvel > 0 || (xvel == 0 && mSlideOffset > 0.5f)) { 1186 left += mSlideRange; 1187 } 1188 mDragHelper.settleCapturedViewAt(left, releasedChild.getTop()); 1189 invalidate(); 1190 } 1191 1192 @Override 1193 public int getViewHorizontalDragRange(View child) { 1194 return mSlideRange; 1195 } 1196 1197 @Override 1198 public int clampViewPositionHorizontal(View child, int left, int dx) { 1199 final LayoutParams lp = (LayoutParams) mSlideableView.getLayoutParams(); 1200 final int leftBound = getPaddingLeft() + lp.leftMargin; 1201 final int rightBound = leftBound + mSlideRange; 1202 1203 final int newLeft = Math.min(Math.max(left, leftBound), rightBound); 1204 1205 return newLeft; 1206 } 1207 1208 @Override 1209 public void onEdgeDragStarted(int edgeFlags, int pointerId) { 1210 mDragHelper.captureChildView(mSlideableView, pointerId); 1211 } 1212 } 1213 1214 public static class LayoutParams extends ViewGroup.MarginLayoutParams { 1215 private static final int[] ATTRS = new int[] { 1216 android.R.attr.layout_weight 1217 }; 1218 1219 /** 1220 * The weighted proportion of how much of the leftover space 1221 * this child should consume after measurement. 1222 */ 1223 public float weight = 0; 1224 1225 /** 1226 * True if this pane is the slideable pane in the layout. 1227 */ 1228 boolean slideable; 1229 1230 /** 1231 * True if this view should be drawn dimmed 1232 * when it's been offset from its default position. 1233 */ 1234 boolean dimWhenOffset; 1235 1236 Paint dimPaint; 1237 1238 public LayoutParams() { 1239 super(FILL_PARENT, FILL_PARENT); 1240 } 1241 1242 public LayoutParams(int width, int height) { 1243 super(width, height); 1244 } 1245 1246 public LayoutParams(android.view.ViewGroup.LayoutParams source) { 1247 super(source); 1248 } 1249 1250 public LayoutParams(MarginLayoutParams source) { 1251 super(source); 1252 } 1253 1254 public LayoutParams(LayoutParams source) { 1255 super(source); 1256 this.weight = source.weight; 1257 } 1258 1259 public LayoutParams(Context c, AttributeSet attrs) { 1260 super(c, attrs); 1261 1262 final TypedArray a = c.obtainStyledAttributes(attrs, ATTRS); 1263 this.weight = a.getFloat(0, 0); 1264 a.recycle(); 1265 } 1266 1267 } 1268 1269 static class SavedState extends BaseSavedState { 1270 boolean isOpen; 1271 1272 SavedState(Parcelable superState) { 1273 super(superState); 1274 } 1275 1276 private SavedState(Parcel in) { 1277 super(in); 1278 isOpen = in.readInt() != 0; 1279 } 1280 1281 @Override 1282 public void writeToParcel(Parcel out, int flags) { 1283 super.writeToParcel(out, flags); 1284 out.writeInt(isOpen ? 1 : 0); 1285 } 1286 1287 public static final Parcelable.Creator<SavedState> CREATOR = 1288 new Parcelable.Creator<SavedState>() { 1289 public SavedState createFromParcel(Parcel in) { 1290 return new SavedState(in); 1291 } 1292 1293 public SavedState[] newArray(int size) { 1294 return new SavedState[size]; 1295 } 1296 }; 1297 } 1298 1299 interface SlidingPanelLayoutImpl { 1300 void invalidateChildRegion(SlidingPaneLayout parent, View child); 1301 } 1302 1303 static class SlidingPanelLayoutImplBase implements SlidingPanelLayoutImpl { 1304 public void invalidateChildRegion(SlidingPaneLayout parent, View child) { 1305 ViewCompat.postInvalidateOnAnimation(parent, child.getLeft(), child.getTop(), 1306 child.getRight(), child.getBottom()); 1307 } 1308 } 1309 1310 static class SlidingPanelLayoutImplJB extends SlidingPanelLayoutImplBase { 1311 /* 1312 * Private API hacks! Nasty! Bad! 1313 * 1314 * In Jellybean, some optimizations in the hardware UI renderer 1315 * prevent a changed Paint on a View using a hardware layer from having 1316 * the intended effect. This twiddles some internal bits on the view to force 1317 * it to recreate the display list. 1318 */ 1319 private Method mGetDisplayList; 1320 private Field mRecreateDisplayList; 1321 1322 SlidingPanelLayoutImplJB() { 1323 try { 1324 mGetDisplayList = View.class.getDeclaredMethod("getDisplayList", (Class[]) null); 1325 } catch (NoSuchMethodException e) { 1326 Log.e(TAG, "Couldn't fetch getDisplayList method; dimming won't work right.", e); 1327 } 1328 try { 1329 mRecreateDisplayList = View.class.getDeclaredField("mRecreateDisplayList"); 1330 mRecreateDisplayList.setAccessible(true); 1331 } catch (NoSuchFieldException e) { 1332 Log.e(TAG, "Couldn't fetch mRecreateDisplayList field; dimming will be slow.", e); 1333 } 1334 } 1335 1336 @Override 1337 public void invalidateChildRegion(SlidingPaneLayout parent, View child) { 1338 if (mGetDisplayList != null && mRecreateDisplayList != null) { 1339 try { 1340 mRecreateDisplayList.setBoolean(child, true); 1341 mGetDisplayList.invoke(child, (Object[]) null); 1342 } catch (Exception e) { 1343 Log.e(TAG, "Error refreshing display list state", e); 1344 } 1345 } else { 1346 // Slow path. REALLY slow path. Let's hope we don't get here. 1347 child.invalidate(); 1348 return; 1349 } 1350 super.invalidateChildRegion(parent, child); 1351 } 1352 } 1353 1354 static class SlidingPanelLayoutImplJBMR1 extends SlidingPanelLayoutImplBase { 1355 @Override 1356 public void invalidateChildRegion(SlidingPaneLayout parent, View child) { 1357 ViewCompat.setLayerPaint(child, ((LayoutParams) child.getLayoutParams()).dimPaint); 1358 } 1359 } 1360 1361 class AccessibilityDelegate extends AccessibilityDelegateCompat { 1362 private final Rect mTmpRect = new Rect(); 1363 1364 @Override 1365 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) { 1366 final AccessibilityNodeInfoCompat superNode = AccessibilityNodeInfoCompat.obtain(info); 1367 super.onInitializeAccessibilityNodeInfo(host, superNode); 1368 1369 info.setSource(host); 1370 final ViewParent parent = ViewCompat.getParentForAccessibility(host); 1371 if (parent instanceof View) { 1372 info.setParent((View) parent); 1373 } 1374 copyNodeInfoNoChildren(info, superNode); 1375 1376 superNode.recycle(); 1377 1378 final int childCount = getChildCount(); 1379 for (int i = 0; i < childCount; i++) { 1380 final View child = getChildAt(i); 1381 if (!filter(child)) { 1382 info.addChild(child); 1383 } 1384 } 1385 } 1386 1387 @Override 1388 public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child, 1389 AccessibilityEvent event) { 1390 if (!filter(child)) { 1391 return super.onRequestSendAccessibilityEvent(host, child, event); 1392 } 1393 return false; 1394 } 1395 1396 public boolean filter(View child) { 1397 return isDimmed(child); 1398 } 1399 1400 /** 1401 * This should really be in AccessibilityNodeInfoCompat, but there unfortunately 1402 * seem to be a few elements that are not easily cloneable using the underlying API. 1403 * Leave it private here as it's not general-purpose useful. 1404 */ 1405 private void copyNodeInfoNoChildren(AccessibilityNodeInfoCompat dest, 1406 AccessibilityNodeInfoCompat src) { 1407 final Rect rect = mTmpRect; 1408 1409 src.getBoundsInParent(rect); 1410 dest.setBoundsInParent(rect); 1411 1412 src.getBoundsInScreen(rect); 1413 dest.setBoundsInScreen(rect); 1414 1415 dest.setVisibleToUser(src.isVisibleToUser()); 1416 dest.setPackageName(src.getPackageName()); 1417 dest.setClassName(src.getClassName()); 1418 dest.setContentDescription(src.getContentDescription()); 1419 1420 dest.setEnabled(src.isEnabled()); 1421 dest.setClickable(src.isClickable()); 1422 dest.setFocusable(src.isFocusable()); 1423 dest.setFocused(src.isFocused()); 1424 dest.setAccessibilityFocused(src.isAccessibilityFocused()); 1425 dest.setSelected(src.isSelected()); 1426 dest.setLongClickable(src.isLongClickable()); 1427 1428 dest.addAction(src.getActions()); 1429 } 1430 } 1431 1432 private class DisableLayerRunnable implements Runnable { 1433 final View mChildView; 1434 1435 DisableLayerRunnable(View childView) { 1436 mChildView = childView; 1437 } 1438 1439 @Override 1440 public void run() { 1441 if (mChildView.getParent() == SlidingPaneLayout.this) { 1442 ViewCompat.setLayerType(mChildView, ViewCompat.LAYER_TYPE_NONE, null); 1443 invalidateChildRegion(mChildView); 1444 } 1445 mPostedRunnables.remove(this); 1446 } 1447 } 1448} 1449