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