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