1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.dialer.widget; 18 19 import android.content.Context; 20 import android.content.res.TypedArray; 21 import android.graphics.Canvas; 22 import android.graphics.PixelFormat; 23 import android.graphics.Rect; 24 import android.graphics.drawable.Drawable; 25 import android.os.Build; 26 import android.os.Parcel; 27 import android.os.Parcelable; 28 import android.support.v4.view.AccessibilityDelegateCompat; 29 import android.support.v4.view.MotionEventCompat; 30 import android.support.v4.view.ViewCompat; 31 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; 32 import android.util.AttributeSet; 33 import android.util.Log; 34 import android.view.MotionEvent; 35 import android.view.View; 36 import android.view.ViewConfiguration; 37 import android.view.ViewGroup; 38 import android.view.ViewParent; 39 import android.view.accessibility.AccessibilityEvent; 40 41 /** 42 * A custom layout that aligns its child views vertically as two panes, and allows for the bottom 43 * pane to be dragged upwards to overlap and hide the top pane. This layout is adapted from 44 * {@link android.support.v4.widget.SlidingPaneLayout}. 45 */ 46 public class OverlappingPaneLayout extends ViewGroup { 47 private static final String TAG = "SlidingPaneLayout"; 48 private static final boolean DEBUG = false; 49 50 /** 51 * Default size of the overhang for a pane in the open state. 52 * At least this much of a sliding pane will remain visible. 53 * This indicates that there is more content available and provides 54 * a "physical" edge to grab to pull it closed. 55 */ 56 private static final int DEFAULT_OVERHANG_SIZE = 32; // dp; 57 58 /** 59 * If no fade color is given by default it will fade to 80% gray. 60 */ 61 private static final int DEFAULT_FADE_COLOR = 0xcccccccc; 62 63 /** 64 * Minimum velocity that will be detected as a fling 65 */ 66 private static final int MIN_FLING_VELOCITY = 400; // dips per second 67 68 /** 69 * The size of the overhang in pixels. 70 * This is the minimum section of the sliding panel that will 71 * be visible in the open state to allow for a closing drag. 72 */ 73 private final int mOverhangSize; 74 75 /** 76 * True if a panel can slide with the current measurements 77 */ 78 private boolean mCanSlide; 79 80 /** 81 * The child view that can slide, if any. 82 */ 83 private View mSlideableView; 84 85 /** 86 * The view that can be used to start the drag with. 87 */ 88 private View mCapturableView; 89 90 /** 91 * How far the panel is offset from its closed position. 92 * range [0, 1] where 0 = closed, 1 = open. 93 */ 94 private float mSlideOffset; 95 96 /** 97 * How far the panel is offset from its closed position, in pixels. 98 * range [0, {@link #mSlideRange}] where 0 is completely closed. 99 */ 100 private int mSlideOffsetPx; 101 102 /** 103 * How far in pixels the slideable panel may move. 104 */ 105 private int mSlideRange; 106 107 /** 108 * A panel view is locked into internal scrolling or another condition that 109 * is preventing a drag. 110 */ 111 private boolean mIsUnableToDrag; 112 113 /** 114 * Tracks whether or not a child view is in the process of a nested scroll. 115 */ 116 private boolean mIsInNestedScroll; 117 118 /** 119 * Indicates that the layout is currently in the process of a nested pre-scroll operation where 120 * the child scrolling view is being dragged downwards. 121 */ 122 private boolean mInNestedPreScrollDownwards; 123 124 /** 125 * Indicates that the layout is currently in the process of a nested pre-scroll operation where 126 * the child scrolling view is being dragged upwards. 127 */ 128 private boolean mInNestedPreScrollUpwards; 129 130 /** 131 * Indicates that the layout is currently in the process of a fling initiated by a pre-fling 132 * from the child scrolling view. 133 */ 134 private boolean mIsInNestedFling; 135 136 /** 137 * Indicates the direction of the pre fling. We need to store this information since 138 * OverScoller doesn't expose the direction of its velocity. 139 */ 140 private boolean mInUpwardsPreFling; 141 142 /** 143 * Stores an offset used to represent a point somewhere in between the panel's fully closed 144 * state and fully opened state where the panel can be temporarily pinned or opened up to 145 * during scrolling. 146 */ 147 private int mIntermediateOffset = 0; 148 149 private float mInitialMotionX; 150 private float mInitialMotionY; 151 152 private PanelSlideCallbacks mPanelSlideCallbacks; 153 154 private final ViewDragHelper mDragHelper; 155 156 /** 157 * Stores whether or not the pane was open the last time it was slideable. 158 * If open/close operations are invoked this state is modified. Used by 159 * instance state save/restore. 160 */ 161 private boolean mPreservedOpenState; 162 private boolean mFirstLayout = true; 163 164 private final Rect mTmpRect = new Rect(); 165 166 /** 167 * How many dips we need to scroll past a position before we can snap to the next position 168 * on release. Using this prevents accidentally snapping to positions. 169 * 170 * This is needed since vertical nested scrolling can be passed to this class even if the 171 * vertical scroll is less than the the nested list's touch slop. 172 */ 173 private final int mReleaseScrollSlop; 174 175 /** 176 * Callbacks for interacting with sliding panes. 177 */ 178 public interface PanelSlideCallbacks { 179 /** 180 * Called when a sliding pane's position changes. 181 * @param panel The child view that was moved 182 * @param slideOffset The new offset of this sliding pane within its range, from 0-1 183 */ onPanelSlide(View panel, float slideOffset)184 public void onPanelSlide(View panel, float slideOffset); 185 /** 186 * Called when a sliding pane becomes slid completely open. The pane may or may not 187 * be interactive at this point depending on how much of the pane is visible. 188 * @param panel The child view that was slid to an open position, revealing other panes 189 */ onPanelOpened(View panel)190 public void onPanelOpened(View panel); 191 192 /** 193 * Called when a sliding pane becomes slid completely closed. The pane is now guaranteed 194 * to be interactive. It may now obscure other views in the layout. 195 * @param panel The child view that was slid to a closed position 196 */ onPanelClosed(View panel)197 public void onPanelClosed(View panel); 198 199 /** 200 * Called when a sliding pane is flung as far open/closed as it can be. 201 * @param velocityY Velocity of the panel once its fling goes as far as it can. 202 */ onPanelFlingReachesEdge(int velocityY)203 public void onPanelFlingReachesEdge(int velocityY); 204 205 /** 206 * Returns true if the second panel's contents haven't been scrolled at all. This value is 207 * used to determine whether or not we can fully expand the header on downwards scrolls. 208 * 209 * Instead of using this callback, it would be preferable to instead fully expand the header 210 * on a View#onNestedFlingOver() callback. The behavior would be nicer. Unfortunately, 211 * no such callback exists yet (b/17547693). 212 */ isScrollableChildUnscrolled()213 public boolean isScrollableChildUnscrolled(); 214 } 215 OverlappingPaneLayout(Context context)216 public OverlappingPaneLayout(Context context) { 217 this(context, null); 218 } 219 OverlappingPaneLayout(Context context, AttributeSet attrs)220 public OverlappingPaneLayout(Context context, AttributeSet attrs) { 221 this(context, attrs, 0); 222 } 223 OverlappingPaneLayout(Context context, AttributeSet attrs, int defStyle)224 public OverlappingPaneLayout(Context context, AttributeSet attrs, int defStyle) { 225 super(context, attrs, defStyle); 226 227 final float density = context.getResources().getDisplayMetrics().density; 228 mOverhangSize = (int) (DEFAULT_OVERHANG_SIZE * density + 0.5f); 229 230 setWillNotDraw(false); 231 232 ViewCompat.setAccessibilityDelegate(this, new AccessibilityDelegate()); 233 ViewCompat.setImportantForAccessibility(this, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES); 234 235 mDragHelper = ViewDragHelper.create(this, 0.5f, new DragHelperCallback()); 236 mDragHelper.setMinVelocity(MIN_FLING_VELOCITY * density); 237 238 mReleaseScrollSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); 239 } 240 241 /** 242 * Set an offset, somewhere in between the panel's fully closed state and fully opened state, 243 * where the panel can be temporarily pinned or opened up to. 244 * 245 * @param offset Offset in pixels 246 */ setIntermediatePinnedOffset(int offset)247 public void setIntermediatePinnedOffset(int offset) { 248 mIntermediateOffset = offset; 249 } 250 251 /** 252 * Set the view that can be used to start dragging the sliding pane. 253 */ setCapturableView(View capturableView)254 public void setCapturableView(View capturableView) { 255 mCapturableView = capturableView; 256 } 257 setPanelSlideCallbacks(PanelSlideCallbacks listener)258 public void setPanelSlideCallbacks(PanelSlideCallbacks listener) { 259 mPanelSlideCallbacks = listener; 260 } 261 dispatchOnPanelSlide(View panel)262 void dispatchOnPanelSlide(View panel) { 263 mPanelSlideCallbacks.onPanelSlide(panel, mSlideOffset); 264 } 265 dispatchOnPanelOpened(View panel)266 void dispatchOnPanelOpened(View panel) { 267 mPanelSlideCallbacks.onPanelOpened(panel); 268 sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); 269 } 270 dispatchOnPanelClosed(View panel)271 void dispatchOnPanelClosed(View panel) { 272 mPanelSlideCallbacks.onPanelClosed(panel); 273 sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); 274 } 275 updateObscuredViewsVisibility(View panel)276 void updateObscuredViewsVisibility(View panel) { 277 final int startBound = getPaddingTop(); 278 final int endBound = getHeight() - getPaddingBottom(); 279 280 final int leftBound = getPaddingLeft(); 281 final int rightBound = getWidth() - getPaddingRight(); 282 final int left; 283 final int right; 284 final int top; 285 final int bottom; 286 if (panel != null && viewIsOpaque(panel)) { 287 left = panel.getLeft(); 288 right = panel.getRight(); 289 top = panel.getTop(); 290 bottom = panel.getBottom(); 291 } else { 292 left = right = top = bottom = 0; 293 } 294 295 for (int i = 0, childCount = getChildCount(); i < childCount; i++) { 296 final View child = getChildAt(i); 297 298 if (child == panel) { 299 // There are still more children above the panel but they won't be affected. 300 break; 301 } 302 303 final int clampedChildLeft = Math.max(leftBound, child.getLeft()); 304 final int clampedChildRight = Math.min(rightBound, child.getRight()); 305 final int clampedChildTop = Math.max(startBound, child.getTop()); 306 final int clampedChildBottom = Math.min(endBound, child.getBottom()); 307 308 final int vis; 309 if (clampedChildLeft >= left && clampedChildTop >= top && 310 clampedChildRight <= right && clampedChildBottom <= bottom) { 311 vis = INVISIBLE; 312 } else { 313 vis = VISIBLE; 314 } 315 child.setVisibility(vis); 316 } 317 } 318 setAllChildrenVisible()319 void setAllChildrenVisible() { 320 for (int i = 0, childCount = getChildCount(); i < childCount; i++) { 321 final View child = getChildAt(i); 322 if (child.getVisibility() == INVISIBLE) { 323 child.setVisibility(VISIBLE); 324 } 325 } 326 } 327 viewIsOpaque(View v)328 private static boolean viewIsOpaque(View v) { 329 if (ViewCompat.isOpaque(v)) return true; 330 331 final Drawable bg = v.getBackground(); 332 if (bg != null) { 333 return bg.getOpacity() == PixelFormat.OPAQUE; 334 } 335 return false; 336 } 337 338 @Override onAttachedToWindow()339 protected void onAttachedToWindow() { 340 super.onAttachedToWindow(); 341 mFirstLayout = true; 342 } 343 344 @Override onDetachedFromWindow()345 protected void onDetachedFromWindow() { 346 super.onDetachedFromWindow(); 347 mFirstLayout = true; 348 } 349 350 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)351 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 352 353 int widthMode = MeasureSpec.getMode(widthMeasureSpec); 354 int widthSize = MeasureSpec.getSize(widthMeasureSpec); 355 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 356 int heightSize = MeasureSpec.getSize(heightMeasureSpec); 357 358 if (widthMode != MeasureSpec.EXACTLY) { 359 if (isInEditMode()) { 360 // Don't crash the layout editor. Consume all of the space if specified 361 // or pick a magic number from thin air otherwise. 362 // TODO Better communication with tools of this bogus state. 363 // It will crash on a real device. 364 if (widthMode == MeasureSpec.AT_MOST) { 365 widthMode = MeasureSpec.EXACTLY; 366 } else if (widthMode == MeasureSpec.UNSPECIFIED) { 367 widthMode = MeasureSpec.EXACTLY; 368 widthSize = 300; 369 } 370 } else { 371 throw new IllegalStateException("Width must have an exact value or MATCH_PARENT"); 372 } 373 } else if (heightMode == MeasureSpec.UNSPECIFIED) { 374 if (isInEditMode()) { 375 // Don't crash the layout editor. Pick a magic number from thin air instead. 376 // TODO Better communication with tools of this bogus state. 377 // It will crash on a real device. 378 if (heightMode == MeasureSpec.UNSPECIFIED) { 379 heightMode = MeasureSpec.AT_MOST; 380 heightSize = 300; 381 } 382 } else { 383 throw new IllegalStateException("Height must not be UNSPECIFIED"); 384 } 385 } 386 387 int layoutWidth = 0; 388 int maxLayoutWidth = -1; 389 switch (widthMode) { 390 case MeasureSpec.EXACTLY: 391 layoutWidth = maxLayoutWidth = widthSize - getPaddingLeft() - getPaddingRight(); 392 break; 393 case MeasureSpec.AT_MOST: 394 maxLayoutWidth = widthSize - getPaddingLeft() - getPaddingRight(); 395 break; 396 } 397 398 float weightSum = 0; 399 boolean canSlide = false; 400 final int heightAvailable = heightSize - getPaddingTop() - getPaddingBottom(); 401 int heightRemaining = heightAvailable; 402 final int childCount = getChildCount(); 403 404 if (childCount > 2) { 405 Log.e(TAG, "onMeasure: More than two child views are not supported."); 406 } 407 408 // We'll find the current one below. 409 mSlideableView = null; 410 411 // First pass. Measure based on child LayoutParams width/height. 412 // Weight will incur a second pass. 413 for (int i = 0; i < childCount; i++) { 414 final View child = getChildAt(i); 415 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 416 417 if (child.getVisibility() == GONE) { 418 continue; 419 } 420 421 if (lp.weight > 0) { 422 weightSum += lp.weight; 423 424 // If we have no height, weight is the only contributor to the final size. 425 // Measure this view on the weight pass only. 426 if (lp.height == 0) continue; 427 } 428 429 int childHeightSpec; 430 final int verticalMargin = lp.topMargin + lp.bottomMargin; 431 if (lp.height == LayoutParams.WRAP_CONTENT) { 432 childHeightSpec = MeasureSpec.makeMeasureSpec(heightAvailable - verticalMargin, 433 MeasureSpec.AT_MOST); 434 } else if (lp.height == LayoutParams.MATCH_PARENT) { 435 childHeightSpec = MeasureSpec.makeMeasureSpec(heightAvailable - verticalMargin, 436 MeasureSpec.EXACTLY); 437 } else { 438 childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY); 439 } 440 441 int childWidthSpec; 442 if (lp.width == LayoutParams.WRAP_CONTENT) { 443 childWidthSpec = MeasureSpec.makeMeasureSpec(maxLayoutWidth, MeasureSpec.AT_MOST); 444 } else if (lp.width == LayoutParams.MATCH_PARENT) { 445 childWidthSpec = MeasureSpec.makeMeasureSpec(maxLayoutWidth, MeasureSpec.EXACTLY); 446 } else { 447 childWidthSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY); 448 } 449 450 child.measure(childWidthSpec, childHeightSpec); 451 final int childWidth = child.getMeasuredWidth(); 452 final int childHeight = child.getMeasuredHeight(); 453 454 if (widthMode == MeasureSpec.AT_MOST && childWidth > layoutWidth) { 455 layoutWidth = Math.min(childWidth, maxLayoutWidth); 456 } 457 458 heightRemaining -= childHeight; 459 canSlide |= lp.slideable = heightRemaining < 0; 460 if (lp.slideable) { 461 mSlideableView = child; 462 } 463 } 464 465 // Resolve weight and make sure non-sliding panels are smaller than the full screen. 466 if (canSlide || weightSum > 0) { 467 final int fixedPanelHeightLimit = heightAvailable - mOverhangSize; 468 469 for (int i = 0; i < childCount; i++) { 470 final View child = getChildAt(i); 471 472 if (child.getVisibility() == GONE) { 473 continue; 474 } 475 476 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 477 478 if (child.getVisibility() == GONE) { 479 continue; 480 } 481 482 final boolean skippedFirstPass = lp.height == 0 && lp.weight > 0; 483 final int measuredHeight = skippedFirstPass ? 0 : child.getMeasuredHeight(); 484 if (canSlide && child != mSlideableView) { 485 if (lp.height < 0 && (measuredHeight > fixedPanelHeightLimit || lp.weight > 0)) { 486 // Fixed panels in a sliding configuration should 487 // be clamped to the fixed panel limit. 488 final int childWidthSpec; 489 if (skippedFirstPass) { 490 // Do initial width measurement if we skipped measuring this view 491 // the first time around. 492 if (lp.width == LayoutParams.WRAP_CONTENT) { 493 childWidthSpec = MeasureSpec.makeMeasureSpec(maxLayoutWidth, 494 MeasureSpec.AT_MOST); 495 } else if (lp.height == LayoutParams.MATCH_PARENT) { 496 childWidthSpec = MeasureSpec.makeMeasureSpec(maxLayoutWidth, 497 MeasureSpec.EXACTLY); 498 } else { 499 childWidthSpec = MeasureSpec.makeMeasureSpec(lp.width, 500 MeasureSpec.EXACTLY); 501 } 502 } else { 503 childWidthSpec = MeasureSpec.makeMeasureSpec( 504 child.getMeasuredWidth(), MeasureSpec.EXACTLY); 505 } 506 final int childHeightSpec = MeasureSpec.makeMeasureSpec( 507 fixedPanelHeightLimit, MeasureSpec.EXACTLY); 508 child.measure(childWidthSpec, childHeightSpec); 509 } 510 } else if (lp.weight > 0) { 511 int childWidthSpec; 512 if (lp.height == 0) { 513 // This was skipped the first time; figure out a real width spec. 514 if (lp.width == LayoutParams.WRAP_CONTENT) { 515 childWidthSpec = MeasureSpec.makeMeasureSpec(maxLayoutWidth, 516 MeasureSpec.AT_MOST); 517 } else if (lp.width == LayoutParams.MATCH_PARENT) { 518 childWidthSpec = MeasureSpec.makeMeasureSpec(maxLayoutWidth, 519 MeasureSpec.EXACTLY); 520 } else { 521 childWidthSpec = MeasureSpec.makeMeasureSpec(lp.width, 522 MeasureSpec.EXACTLY); 523 } 524 } else { 525 childWidthSpec = MeasureSpec.makeMeasureSpec( 526 child.getMeasuredWidth(), MeasureSpec.EXACTLY); 527 } 528 529 if (canSlide) { 530 // Consume available space 531 final int verticalMargin = lp.topMargin + lp.bottomMargin; 532 final int newHeight = heightAvailable - verticalMargin; 533 final int childHeightSpec = MeasureSpec.makeMeasureSpec( 534 newHeight, MeasureSpec.EXACTLY); 535 if (measuredHeight != newHeight) { 536 child.measure(childWidthSpec, childHeightSpec); 537 } 538 } else { 539 // Distribute the extra width proportionally similar to LinearLayout 540 final int heightToDistribute = Math.max(0, heightRemaining); 541 final int addedHeight = (int) (lp.weight * heightToDistribute / weightSum); 542 final int childHeightSpec = MeasureSpec.makeMeasureSpec( 543 measuredHeight + addedHeight, MeasureSpec.EXACTLY); 544 child.measure(childWidthSpec, childHeightSpec); 545 } 546 } 547 } 548 } 549 550 final int measuredHeight = heightSize; 551 final int measuredWidth = layoutWidth + getPaddingLeft() + getPaddingRight(); 552 553 setMeasuredDimension(measuredWidth, measuredHeight); 554 mCanSlide = canSlide; 555 556 if (mDragHelper.getViewDragState() != ViewDragHelper.STATE_IDLE && !canSlide) { 557 // Cancel scrolling in progress, it's no longer relevant. 558 mDragHelper.abort(); 559 } 560 } 561 562 @Override onLayout(boolean changed, int l, int t, int r, int b)563 protected void onLayout(boolean changed, int l, int t, int r, int b) { 564 mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_TOP); 565 566 final int height = b - t; 567 final int paddingTop = getPaddingTop(); 568 final int paddingBottom = getPaddingBottom(); 569 final int paddingLeft = getPaddingLeft(); 570 571 final int childCount = getChildCount(); 572 int yStart = paddingTop; 573 int nextYStart = yStart; 574 575 if (mFirstLayout) { 576 mSlideOffset = mCanSlide && mPreservedOpenState ? 1.f : 0.f; 577 } 578 579 for (int i = 0; i < childCount; i++) { 580 final View child = getChildAt(i); 581 582 if (child.getVisibility() == GONE) { 583 continue; 584 } 585 586 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 587 588 final int childHeight = child.getMeasuredHeight(); 589 590 if (lp.slideable) { 591 final int margin = lp.topMargin + lp.bottomMargin; 592 final int range = Math.min(nextYStart, 593 height - paddingBottom - mOverhangSize) - yStart - margin; 594 mSlideRange = range; 595 final int lpMargin = lp.topMargin; 596 final int pos = (int) (range * mSlideOffset); 597 yStart += pos + lpMargin; 598 updateSlideOffset(pos); 599 } else { 600 yStart = nextYStart; 601 } 602 603 final int childTop = yStart; 604 final int childBottom = childTop + childHeight; 605 final int childLeft = paddingLeft; 606 final int childRight = childLeft + child.getMeasuredWidth(); 607 608 child.layout(childLeft, childTop, childRight, childBottom); 609 610 nextYStart += child.getHeight(); 611 } 612 613 if (mFirstLayout) { 614 updateObscuredViewsVisibility(mSlideableView); 615 } 616 617 mFirstLayout = false; 618 } 619 620 @Override onSizeChanged(int w, int h, int oldw, int oldh)621 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 622 super.onSizeChanged(w, h, oldw, oldh); 623 // Recalculate sliding panes and their details 624 if (h != oldh) { 625 mFirstLayout = true; 626 } 627 } 628 629 @Override requestChildFocus(View child, View focused)630 public void requestChildFocus(View child, View focused) { 631 super.requestChildFocus(child, focused); 632 if (!isInTouchMode() && !mCanSlide) { 633 mPreservedOpenState = child == mSlideableView; 634 } 635 } 636 637 @Override onInterceptTouchEvent(MotionEvent ev)638 public boolean onInterceptTouchEvent(MotionEvent ev) { 639 final int action = MotionEventCompat.getActionMasked(ev); 640 641 // Preserve the open state based on the last view that was touched. 642 if (!mCanSlide && action == MotionEvent.ACTION_DOWN && getChildCount() > 1) { 643 // After the first things will be slideable. 644 final View secondChild = getChildAt(1); 645 if (secondChild != null) { 646 mPreservedOpenState = !mDragHelper.isViewUnder(secondChild, 647 (int) ev.getX(), (int) ev.getY()); 648 } 649 } 650 651 if (!mCanSlide || (mIsUnableToDrag && action != MotionEvent.ACTION_DOWN)) { 652 if (!mIsInNestedScroll) { 653 mDragHelper.cancel(); 654 } 655 return super.onInterceptTouchEvent(ev); 656 } 657 658 if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { 659 if (!mIsInNestedScroll) { 660 mDragHelper.cancel(); 661 } 662 return false; 663 } 664 665 switch (action) { 666 case MotionEvent.ACTION_DOWN: { 667 mIsUnableToDrag = false; 668 final float x = ev.getX(); 669 final float y = ev.getY(); 670 mInitialMotionX = x; 671 mInitialMotionY = y; 672 673 break; 674 } 675 676 case MotionEvent.ACTION_MOVE: { 677 final float x = ev.getX(); 678 final float y = ev.getY(); 679 final float adx = Math.abs(x - mInitialMotionX); 680 final float ady = Math.abs(y - mInitialMotionY); 681 final int slop = mDragHelper.getTouchSlop(); 682 if (ady > slop && adx > ady || !isCapturableViewUnder((int) x, (int) y)) { 683 if (!mIsInNestedScroll) { 684 mDragHelper.cancel(); 685 } 686 mIsUnableToDrag = true; 687 return false; 688 } 689 } 690 } 691 692 final boolean interceptForDrag = mDragHelper.shouldInterceptTouchEvent(ev); 693 694 return interceptForDrag; 695 } 696 697 @Override onTouchEvent(MotionEvent ev)698 public boolean onTouchEvent(MotionEvent ev) { 699 if (!mCanSlide) { 700 return super.onTouchEvent(ev); 701 } 702 703 mDragHelper.processTouchEvent(ev); 704 705 final int action = ev.getAction(); 706 boolean wantTouchEvents = true; 707 708 switch (action & MotionEventCompat.ACTION_MASK) { 709 case MotionEvent.ACTION_DOWN: { 710 final float x = ev.getX(); 711 final float y = ev.getY(); 712 mInitialMotionX = x; 713 mInitialMotionY = y; 714 break; 715 } 716 } 717 718 return wantTouchEvents; 719 } 720 closePane(View pane, int initialVelocity)721 private boolean closePane(View pane, int initialVelocity) { 722 if (mFirstLayout || smoothSlideTo(0.f, initialVelocity)) { 723 mPreservedOpenState = false; 724 return true; 725 } 726 return false; 727 } 728 openPane(View pane, int initialVelocity)729 private boolean openPane(View pane, int initialVelocity) { 730 if (mFirstLayout || smoothSlideTo(1.f, initialVelocity)) { 731 mPreservedOpenState = true; 732 return true; 733 } 734 return false; 735 } 736 updateSlideOffset(int offsetPx)737 private void updateSlideOffset(int offsetPx) { 738 mSlideOffsetPx = offsetPx; 739 mSlideOffset = (float) mSlideOffsetPx / mSlideRange; 740 } 741 742 /** 743 * Open the sliding pane if it is currently slideable. If first layout 744 * has already completed this will animate. 745 * 746 * @return true if the pane was slideable and is now open/in the process of opening 747 */ openPane()748 public boolean openPane() { 749 return openPane(mSlideableView, 0); 750 } 751 752 /** 753 * Close the sliding pane if it is currently slideable. If first layout 754 * has already completed this will animate. 755 * 756 * @return true if the pane was slideable and is now closed/in the process of closing 757 */ closePane()758 public boolean closePane() { 759 return closePane(mSlideableView, 0); 760 } 761 762 /** 763 * Check if the layout is open. It can be open either because the slider 764 * itself is open revealing the left pane, or if all content fits without sliding. 765 * 766 * @return true if sliding panels are open 767 */ isOpen()768 public boolean isOpen() { 769 return !mCanSlide || mSlideOffset > 0; 770 } 771 772 /** 773 * Check if the content in this layout cannot fully fit side by side and therefore 774 * the content pane can be slid back and forth. 775 * 776 * @return true if content in this layout can be slid open and closed 777 */ isSlideable()778 public boolean isSlideable() { 779 return mCanSlide; 780 } 781 onPanelDragged(int newTop)782 private void onPanelDragged(int newTop) { 783 if (mSlideableView == null) { 784 // This can happen if we're aborting motion during layout because everything now fits. 785 mSlideOffset = 0; 786 return; 787 } 788 final LayoutParams lp = (LayoutParams) mSlideableView.getLayoutParams(); 789 790 final int lpMargin = lp.topMargin; 791 final int topBound = getPaddingTop() + lpMargin; 792 793 updateSlideOffset(newTop - topBound); 794 795 dispatchOnPanelSlide(mSlideableView); 796 } 797 798 @Override drawChild(Canvas canvas, View child, long drawingTime)799 protected boolean drawChild(Canvas canvas, View child, long drawingTime) { 800 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 801 boolean result; 802 final int save = canvas.save(Canvas.CLIP_SAVE_FLAG); 803 804 if (mCanSlide && !lp.slideable && mSlideableView != null) { 805 // Clip against the slider; no sense drawing what will immediately be covered. 806 canvas.getClipBounds(mTmpRect); 807 808 mTmpRect.bottom = Math.min(mTmpRect.bottom, mSlideableView.getTop()); 809 canvas.clipRect(mTmpRect); 810 } 811 812 if (Build.VERSION.SDK_INT >= 11) { // HC 813 result = super.drawChild(canvas, child, drawingTime); 814 } else { 815 if (child.isDrawingCacheEnabled()) { 816 child.setDrawingCacheEnabled(false); 817 } 818 result = super.drawChild(canvas, child, drawingTime); 819 } 820 821 canvas.restoreToCount(save); 822 823 return result; 824 } 825 826 /** 827 * Smoothly animate mDraggingPane to the target X position within its range. 828 * 829 * @param slideOffset position to animate to 830 * @param velocity initial velocity in case of fling, or 0. 831 */ smoothSlideTo(float slideOffset, int velocity)832 boolean smoothSlideTo(float slideOffset, int velocity) { 833 if (!mCanSlide) { 834 // Nothing to do. 835 return false; 836 } 837 838 final LayoutParams lp = (LayoutParams) mSlideableView.getLayoutParams(); 839 840 int y; 841 int topBound = getPaddingTop() + lp.topMargin; 842 y = (int) (topBound + slideOffset * mSlideRange); 843 844 if (mDragHelper.smoothSlideViewTo(mSlideableView, mSlideableView.getLeft(), y)) { 845 setAllChildrenVisible(); 846 ViewCompat.postInvalidateOnAnimation(this); 847 return true; 848 } 849 return false; 850 } 851 852 @Override computeScroll()853 public void computeScroll() { 854 if (mDragHelper.continueSettling(/* deferCallbacks = */ false)) { 855 if (!mCanSlide) { 856 mDragHelper.abort(); 857 return; 858 } 859 860 ViewCompat.postInvalidateOnAnimation(this); 861 } 862 } 863 isCapturableViewUnder(int x, int y)864 private boolean isCapturableViewUnder(int x, int y) { 865 View capturableView = mCapturableView != null ? mCapturableView : mSlideableView; 866 if (capturableView == null) { 867 return false; 868 } 869 int[] viewLocation = new int[2]; 870 capturableView.getLocationOnScreen(viewLocation); 871 int[] parentLocation = new int[2]; 872 this.getLocationOnScreen(parentLocation); 873 int screenX = parentLocation[0] + x; 874 int screenY = parentLocation[1] + y; 875 return screenX >= viewLocation[0] 876 && screenX < viewLocation[0] + capturableView.getWidth() 877 && screenY >= viewLocation[1] 878 && screenY < viewLocation[1] + capturableView.getHeight(); 879 } 880 881 @Override generateDefaultLayoutParams()882 protected ViewGroup.LayoutParams generateDefaultLayoutParams() { 883 return new LayoutParams(); 884 } 885 886 @Override generateLayoutParams(ViewGroup.LayoutParams p)887 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 888 return p instanceof MarginLayoutParams 889 ? new LayoutParams((MarginLayoutParams) p) 890 : new LayoutParams(p); 891 } 892 893 @Override checkLayoutParams(ViewGroup.LayoutParams p)894 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 895 return p instanceof LayoutParams && super.checkLayoutParams(p); 896 } 897 898 @Override generateLayoutParams(AttributeSet attrs)899 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { 900 return new LayoutParams(getContext(), attrs); 901 } 902 903 @Override onSaveInstanceState()904 protected Parcelable onSaveInstanceState() { 905 Parcelable superState = super.onSaveInstanceState(); 906 907 SavedState ss = new SavedState(superState); 908 ss.isOpen = isSlideable() ? isOpen() : mPreservedOpenState; 909 910 return ss; 911 } 912 913 @Override onRestoreInstanceState(Parcelable state)914 protected void onRestoreInstanceState(Parcelable state) { 915 SavedState ss = (SavedState) state; 916 super.onRestoreInstanceState(ss.getSuperState()); 917 918 if (ss.isOpen) { 919 openPane(); 920 } else { 921 closePane(); 922 } 923 mPreservedOpenState = ss.isOpen; 924 } 925 926 @Override onStartNestedScroll(View child, View target, int nestedScrollAxes)927 public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) { 928 final boolean startNestedScroll = (nestedScrollAxes & SCROLL_AXIS_VERTICAL) != 0; 929 if (startNestedScroll) { 930 mIsInNestedScroll = true; 931 mDragHelper.startNestedScroll(mSlideableView); 932 } 933 if (DEBUG) { 934 Log.d(TAG, "onStartNestedScroll: " + startNestedScroll); 935 } 936 return startNestedScroll; 937 } 938 939 @Override onNestedPreScroll(View target, int dx, int dy, int[] consumed)940 public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) { 941 if (dy == 0) { 942 // Nothing to do 943 return; 944 } 945 if (DEBUG) { 946 Log.d(TAG, "onNestedPreScroll: " + dy); 947 } 948 949 mInNestedPreScrollDownwards = dy < 0; 950 mInNestedPreScrollUpwards = dy > 0; 951 mIsInNestedFling = false; 952 mDragHelper.processNestedScroll(mSlideableView, 0, -dy, consumed); 953 } 954 955 @Override onNestedPreFling(View target, float velocityX, float velocityY)956 public boolean onNestedPreFling(View target, float velocityX, float velocityY) { 957 if (!(velocityY > 0 && mSlideOffsetPx != 0 958 || velocityY < 0 && mSlideOffsetPx < mIntermediateOffset 959 || velocityY < 0 && mSlideOffsetPx < mSlideRange 960 && mPanelSlideCallbacks.isScrollableChildUnscrolled())) { 961 // No need to consume the fling if the fling won't collapse or expand the header. 962 // How far we are willing to expand the header depends on isScrollableChildUnscrolled(). 963 return false; 964 } 965 966 if (DEBUG) { 967 Log.d(TAG, "onNestedPreFling: " + velocityY); 968 } 969 mInUpwardsPreFling = velocityY > 0; 970 mIsInNestedFling = true; 971 mIsInNestedScroll = false; 972 mDragHelper.processNestedFling(mSlideableView, (int) -velocityY); 973 return true; 974 } 975 976 @Override onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed)977 public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, 978 int dyUnconsumed) { 979 if (DEBUG) { 980 Log.d(TAG, "onNestedScroll: " + dyUnconsumed); 981 } 982 mIsInNestedFling = false; 983 mDragHelper.processNestedScroll(mSlideableView, 0, -dyUnconsumed, null); 984 } 985 986 @Override onStopNestedScroll(View child)987 public void onStopNestedScroll(View child) { 988 if (DEBUG) { 989 Log.d(TAG, "onStopNestedScroll"); 990 } 991 if (mIsInNestedScroll && !mIsInNestedFling) { 992 mDragHelper.stopNestedScroll(mSlideableView); 993 mInNestedPreScrollDownwards = false; 994 mInNestedPreScrollUpwards = false; 995 mIsInNestedScroll = false; 996 } 997 } 998 999 private class DragHelperCallback extends ViewDragHelper.Callback { 1000 1001 @Override tryCaptureView(View child, int pointerId)1002 public boolean tryCaptureView(View child, int pointerId) { 1003 if (mIsUnableToDrag) { 1004 return false; 1005 } 1006 1007 return ((LayoutParams) child.getLayoutParams()).slideable; 1008 } 1009 1010 @Override onViewDragStateChanged(int state)1011 public void onViewDragStateChanged(int state) { 1012 if (DEBUG) { 1013 Log.d(TAG, "onViewDragStateChanged: " + state); 1014 } 1015 1016 if (mDragHelper.getViewDragState() == ViewDragHelper.STATE_IDLE) { 1017 if (mSlideOffset == 0) { 1018 updateObscuredViewsVisibility(mSlideableView); 1019 dispatchOnPanelClosed(mSlideableView); 1020 mPreservedOpenState = false; 1021 } else { 1022 dispatchOnPanelOpened(mSlideableView); 1023 mPreservedOpenState = true; 1024 } 1025 } 1026 1027 if (state == ViewDragHelper.STATE_IDLE 1028 && mDragHelper.getVelocityMagnitude() > 0 1029 && mIsInNestedFling) { 1030 mIsInNestedFling = false; 1031 final int flingVelocity = !mInUpwardsPreFling ? 1032 -mDragHelper.getVelocityMagnitude() : mDragHelper.getVelocityMagnitude(); 1033 mPanelSlideCallbacks.onPanelFlingReachesEdge(flingVelocity); 1034 } 1035 } 1036 1037 @Override onViewCaptured(View capturedChild, int activePointerId)1038 public void onViewCaptured(View capturedChild, int activePointerId) { 1039 // Make all child views visible in preparation for sliding things around 1040 setAllChildrenVisible(); 1041 } 1042 1043 @Override onViewPositionChanged(View changedView, int left, int top, int dx, int dy)1044 public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { 1045 onPanelDragged(top); 1046 invalidate(); 1047 } 1048 1049 @Override onViewFling(View releasedChild, float xVelocity, float yVelocity)1050 public void onViewFling(View releasedChild, float xVelocity, float yVelocity) { 1051 if (releasedChild == null) { 1052 return; 1053 } 1054 if (DEBUG) { 1055 Log.d(TAG, "onViewFling: " + yVelocity); 1056 } 1057 1058 // Flings won't always fully expand or collapse the header. Instead of performing the 1059 // fling and then waiting for the fling to end before snapping into place, we 1060 // immediately snap into place if we predict the fling won't fully expand or collapse 1061 // the header. 1062 int yOffsetPx = mDragHelper.predictFlingYOffset((int) yVelocity); 1063 if (yVelocity < 0) { 1064 // Only perform a fling if we know the fling will fully compress the header. 1065 if (-yOffsetPx > mSlideOffsetPx) { 1066 mDragHelper.flingCapturedView(releasedChild.getLeft(), /* minTop = */ 0, 1067 mSlideRange, Integer.MAX_VALUE, (int) yVelocity); 1068 } else { 1069 mIsInNestedFling = false; 1070 onViewReleased(releasedChild, xVelocity, yVelocity); 1071 } 1072 } else { 1073 // Only perform a fling if we know the fling will expand the header as far 1074 // as it can possible be expanded, given the isScrollableChildUnscrolled() value. 1075 if (yOffsetPx + mSlideOffsetPx >= mSlideRange 1076 && mPanelSlideCallbacks.isScrollableChildUnscrolled()) { 1077 mDragHelper.flingCapturedView(releasedChild.getLeft(), /* minTop = */ 0, 1078 Integer.MAX_VALUE, mSlideRange, (int) yVelocity); 1079 } else if (yOffsetPx + mSlideOffsetPx >= mIntermediateOffset 1080 && mSlideOffsetPx <= mIntermediateOffset 1081 && !mPanelSlideCallbacks.isScrollableChildUnscrolled()) { 1082 mDragHelper.flingCapturedView(releasedChild.getLeft(), /* minTop = */ 0, 1083 Integer.MAX_VALUE, mIntermediateOffset, (int) yVelocity); 1084 } else { 1085 mIsInNestedFling = false; 1086 onViewReleased(releasedChild, xVelocity, yVelocity); 1087 } 1088 } 1089 1090 mInNestedPreScrollDownwards = false; 1091 mInNestedPreScrollUpwards = false; 1092 1093 // Without this invalidate, some calls to flingCapturedView can have no affect. 1094 invalidate(); 1095 } 1096 1097 @Override onViewReleased(View releasedChild, float xvel, float yvel)1098 public void onViewReleased(View releasedChild, float xvel, float yvel) { 1099 if (DEBUG) { 1100 Log.d(TAG, "onViewReleased: " 1101 + " mIsInNestedFling=" + mIsInNestedFling 1102 + " unscrolled=" + mPanelSlideCallbacks.isScrollableChildUnscrolled() 1103 + ", mInNestedPreScrollDownwards = " + mInNestedPreScrollDownwards 1104 + ", mInNestedPreScrollUpwards = " + mInNestedPreScrollUpwards 1105 + ", yvel=" + yvel); 1106 } 1107 if (releasedChild == null) { 1108 return; 1109 } 1110 1111 final LayoutParams lp = (LayoutParams) releasedChild.getLayoutParams(); 1112 int top = getPaddingTop() + lp.topMargin; 1113 1114 // Decide where to snap to according to the current direction of motion and the current 1115 // position. The velocity's magnitude has no bearing on this. 1116 if (mInNestedPreScrollDownwards || yvel > 0) { 1117 // Scrolling downwards 1118 if (mSlideOffsetPx > mIntermediateOffset + mReleaseScrollSlop) { 1119 top += mSlideRange; 1120 } else if (mSlideOffsetPx > mReleaseScrollSlop) { 1121 top += mIntermediateOffset; 1122 } else { 1123 // Offset is very close to 0 1124 } 1125 } else if (mInNestedPreScrollUpwards || yvel < 0) { 1126 // Scrolling upwards 1127 if (mSlideOffsetPx > mSlideRange - mReleaseScrollSlop) { 1128 // Offset is very close to mSlideRange 1129 top += mSlideRange; 1130 } else if (mSlideOffsetPx > mIntermediateOffset - mReleaseScrollSlop) { 1131 // Offset is between mIntermediateOffset and mSlideRange. 1132 top += mIntermediateOffset; 1133 } else { 1134 // Offset is between 0 and mIntermediateOffset. 1135 } 1136 } else { 1137 // Not moving upwards or downwards. This case can only be triggered when directly 1138 // dragging the tabs. We don't bother to remember previous scroll direction 1139 // when directly dragging the tabs. 1140 if (0 <= mSlideOffsetPx && mSlideOffsetPx <= mIntermediateOffset / 2) { 1141 // Offset is between 0 and mIntermediateOffset, but closer to 0 1142 // Leave top unchanged 1143 } else if (mIntermediateOffset / 2 <= mSlideOffsetPx 1144 && mSlideOffsetPx <= (mIntermediateOffset + mSlideRange) / 2) { 1145 // Offset is closest to mIntermediateOffset 1146 top += mIntermediateOffset; 1147 } else { 1148 // Offset is between mIntermediateOffset and mSlideRange, but closer to 1149 // mSlideRange 1150 top += mSlideRange; 1151 } 1152 } 1153 1154 mDragHelper.settleCapturedViewAt(releasedChild.getLeft(), top); 1155 invalidate(); 1156 } 1157 1158 @Override getViewVerticalDragRange(View child)1159 public int getViewVerticalDragRange(View child) { 1160 return mSlideRange; 1161 } 1162 1163 @Override clampViewPositionHorizontal(View child, int left, int dx)1164 public int clampViewPositionHorizontal(View child, int left, int dx) { 1165 // Make sure we never move views horizontally. 1166 return child.getLeft(); 1167 } 1168 1169 @Override clampViewPositionVertical(View child, int top, int dy)1170 public int clampViewPositionVertical(View child, int top, int dy) { 1171 final LayoutParams lp = (LayoutParams) mSlideableView.getLayoutParams(); 1172 1173 final int newTop; 1174 int previousTop = top - dy; 1175 int topBound = getPaddingTop() + lp.topMargin; 1176 int bottomBound = topBound + (mPanelSlideCallbacks.isScrollableChildUnscrolled() 1177 || !mIsInNestedScroll ? mSlideRange : mIntermediateOffset); 1178 if (previousTop > bottomBound) { 1179 // We were previously below the bottomBound, so loosen the bottomBound so that this 1180 // makes sense. This can occur after the view was directly dragged by the tabs. 1181 bottomBound = Math.max(bottomBound, mSlideRange); 1182 } 1183 newTop = Math.min(Math.max(top, topBound), bottomBound); 1184 1185 return newTop; 1186 } 1187 1188 @Override onEdgeDragStarted(int edgeFlags, int pointerId)1189 public void onEdgeDragStarted(int edgeFlags, int pointerId) { 1190 mDragHelper.captureChildView(mSlideableView, pointerId); 1191 } 1192 } 1193 1194 public static class LayoutParams extends ViewGroup.MarginLayoutParams { 1195 private static final int[] ATTRS = new int[] { 1196 android.R.attr.layout_weight 1197 }; 1198 1199 /** 1200 * The weighted proportion of how much of the leftover space 1201 * this child should consume after measurement. 1202 */ 1203 public float weight = 0; 1204 1205 /** 1206 * True if this pane is the slideable pane in the layout. 1207 */ 1208 boolean slideable; 1209 LayoutParams()1210 public LayoutParams() { 1211 super(FILL_PARENT, FILL_PARENT); 1212 } 1213 LayoutParams(int width, int height)1214 public LayoutParams(int width, int height) { 1215 super(width, height); 1216 } 1217 LayoutParams(android.view.ViewGroup.LayoutParams source)1218 public LayoutParams(android.view.ViewGroup.LayoutParams source) { 1219 super(source); 1220 } 1221 LayoutParams(MarginLayoutParams source)1222 public LayoutParams(MarginLayoutParams source) { 1223 super(source); 1224 } 1225 LayoutParams(LayoutParams source)1226 public LayoutParams(LayoutParams source) { 1227 super(source); 1228 this.weight = source.weight; 1229 } 1230 LayoutParams(Context c, AttributeSet attrs)1231 public LayoutParams(Context c, AttributeSet attrs) { 1232 super(c, attrs); 1233 1234 final TypedArray a = c.obtainStyledAttributes(attrs, ATTRS); 1235 this.weight = a.getFloat(0, 0); 1236 a.recycle(); 1237 } 1238 1239 } 1240 1241 static class SavedState extends BaseSavedState { 1242 boolean isOpen; 1243 SavedState(Parcelable superState)1244 SavedState(Parcelable superState) { 1245 super(superState); 1246 } 1247 SavedState(Parcel in)1248 private SavedState(Parcel in) { 1249 super(in); 1250 isOpen = in.readInt() != 0; 1251 } 1252 1253 @Override writeToParcel(Parcel out, int flags)1254 public void writeToParcel(Parcel out, int flags) { 1255 super.writeToParcel(out, flags); 1256 out.writeInt(isOpen ? 1 : 0); 1257 } 1258 1259 public static final Parcelable.Creator<SavedState> CREATOR = 1260 new Parcelable.Creator<SavedState>() { 1261 public SavedState createFromParcel(Parcel in) { 1262 return new SavedState(in); 1263 } 1264 1265 public SavedState[] newArray(int size) { 1266 return new SavedState[size]; 1267 } 1268 }; 1269 } 1270 1271 class AccessibilityDelegate extends AccessibilityDelegateCompat { 1272 private final Rect mTmpRect = new Rect(); 1273 1274 @Override onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info)1275 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) { 1276 final AccessibilityNodeInfoCompat superNode = AccessibilityNodeInfoCompat.obtain(info); 1277 super.onInitializeAccessibilityNodeInfo(host, superNode); 1278 copyNodeInfoNoChildren(info, superNode); 1279 superNode.recycle(); 1280 1281 info.setClassName(OverlappingPaneLayout.class.getName()); 1282 info.setSource(host); 1283 1284 final ViewParent parent = ViewCompat.getParentForAccessibility(host); 1285 if (parent instanceof View) { 1286 info.setParent((View) parent); 1287 } 1288 1289 // This is a best-approximation of addChildrenForAccessibility() 1290 // that accounts for filtering. 1291 final int childCount = getChildCount(); 1292 for (int i = 0; i < childCount; i++) { 1293 final View child = getChildAt(i); 1294 if (child.getVisibility() == View.VISIBLE) { 1295 // Force importance to "yes" since we can't read the value. 1296 ViewCompat.setImportantForAccessibility( 1297 child, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES); 1298 info.addChild(child); 1299 } 1300 } 1301 } 1302 1303 @Override onInitializeAccessibilityEvent(View host, AccessibilityEvent event)1304 public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) { 1305 super.onInitializeAccessibilityEvent(host, event); 1306 1307 event.setClassName(OverlappingPaneLayout.class.getName()); 1308 } 1309 1310 /** 1311 * This should really be in AccessibilityNodeInfoCompat, but there unfortunately 1312 * seem to be a few elements that are not easily cloneable using the underlying API. 1313 * Leave it private here as it's not general-purpose useful. 1314 */ copyNodeInfoNoChildren(AccessibilityNodeInfoCompat dest, AccessibilityNodeInfoCompat src)1315 private void copyNodeInfoNoChildren(AccessibilityNodeInfoCompat dest, 1316 AccessibilityNodeInfoCompat src) { 1317 final Rect rect = mTmpRect; 1318 1319 src.getBoundsInParent(rect); 1320 dest.setBoundsInParent(rect); 1321 1322 src.getBoundsInScreen(rect); 1323 dest.setBoundsInScreen(rect); 1324 1325 dest.setVisibleToUser(src.isVisibleToUser()); 1326 dest.setPackageName(src.getPackageName()); 1327 dest.setClassName(src.getClassName()); 1328 dest.setContentDescription(src.getContentDescription()); 1329 1330 dest.setEnabled(src.isEnabled()); 1331 dest.setClickable(src.isClickable()); 1332 dest.setFocusable(src.isFocusable()); 1333 dest.setFocused(src.isFocused()); 1334 dest.setAccessibilityFocused(src.isAccessibilityFocused()); 1335 dest.setSelected(src.isSelected()); 1336 dest.setLongClickable(src.isLongClickable()); 1337 1338 dest.addAction(src.getActions()); 1339 1340 dest.setMovementGranularities(src.getMovementGranularities()); 1341 } 1342 } 1343 } 1344