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