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