NotificationStackScrollLayout.java revision 341d182100fcf6e44bb9b96ade3dead5ae82a439
1/* 2 * Copyright (C) 2014 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 com.android.systemui.statusbar.stack; 18 19import android.content.Context; 20import android.content.res.Configuration; 21import android.graphics.Canvas; 22import android.graphics.Paint; 23import android.util.AttributeSet; 24import android.util.Log; 25import android.view.MotionEvent; 26import android.view.VelocityTracker; 27import android.view.View; 28import android.view.ViewConfiguration; 29import android.view.ViewGroup; 30import android.view.ViewTreeObserver; 31import android.view.animation.AnimationUtils; 32import android.widget.OverScroller; 33import android.widget.ScrollView; 34 35import com.android.systemui.ExpandHelper; 36import com.android.systemui.R; 37import com.android.systemui.SwipeHelper; 38import com.android.systemui.statusbar.ActivatableNotificationView; 39import com.android.systemui.statusbar.ExpandableNotificationRow; 40import com.android.systemui.statusbar.ExpandableView; 41import com.android.systemui.statusbar.SpeedBumpView; 42import com.android.systemui.statusbar.policy.ScrollAdapter; 43import com.android.systemui.statusbar.stack.StackScrollState.ViewState; 44 45import java.util.ArrayList; 46 47/** 48 * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack. 49 */ 50public class NotificationStackScrollLayout extends ViewGroup 51 implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter, 52 ExpandableView.OnHeightChangedListener { 53 54 private static final String TAG = "NotificationStackScrollLayout"; 55 private static final boolean DEBUG = false; 56 private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f; 57 private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f; 58 private static final float RUBBER_BAND_FACTOR_ON_PANEL_EXPAND = 0.21f; 59 60 /** 61 * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}. 62 */ 63 private static final int INVALID_POINTER = -1; 64 65 private ExpandHelper mExpandHelper; 66 private SwipeHelper mSwipeHelper; 67 private boolean mSwipingInProgress; 68 private int mCurrentStackHeight = Integer.MAX_VALUE; 69 private int mOwnScrollY; 70 private int mMaxLayoutHeight; 71 72 private VelocityTracker mVelocityTracker; 73 private OverScroller mScroller; 74 private int mTouchSlop; 75 private int mMinimumVelocity; 76 private int mMaximumVelocity; 77 private int mOverflingDistance; 78 private float mMaxOverScroll; 79 private boolean mIsBeingDragged; 80 private int mLastMotionY; 81 private int mDownX; 82 private int mActivePointerId; 83 84 private int mSidePaddings; 85 private Paint mDebugPaint; 86 private int mContentHeight; 87 private int mCollapsedSize; 88 private int mBottomStackSlowDownHeight; 89 private int mBottomStackPeekSize; 90 private int mPaddingBetweenElements; 91 private int mPaddingBetweenElementsDimmed; 92 private int mPaddingBetweenElementsNormal; 93 private int mTopPadding; 94 95 /** 96 * The algorithm which calculates the properties for our children 97 */ 98 private StackScrollAlgorithm mStackScrollAlgorithm; 99 100 /** 101 * The current State this Layout is in 102 */ 103 private StackScrollState mCurrentStackScrollState = new StackScrollState(this); 104 private AmbientState mAmbientState = new AmbientState(); 105 private ArrayList<View> mChildrenToAddAnimated = new ArrayList<View>(); 106 private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<View>(); 107 private ArrayList<View> mSnappedBackChildren = new ArrayList<View>(); 108 private ArrayList<View> mDragAnimPendingChildren = new ArrayList<View>(); 109 private ArrayList<View> mChildrenChangingPositions = new ArrayList<View>(); 110 private ArrayList<AnimationEvent> mAnimationEvents 111 = new ArrayList<AnimationEvent>(); 112 private ArrayList<View> mSwipedOutViews = new ArrayList<View>(); 113 private final StackStateAnimator mStateAnimator = new StackStateAnimator(this); 114 private boolean mAnimationsEnabled; 115 private boolean mChangePositionInProgress; 116 117 /** 118 * The raw amount of the overScroll on the top, which is not rubber-banded. 119 */ 120 private float mOverScrolledTopPixels; 121 122 /** 123 * The raw amount of the overScroll on the bottom, which is not rubber-banded. 124 */ 125 private float mOverScrolledBottomPixels; 126 127 private OnChildLocationsChangedListener mListener; 128 private OnOverscrollTopChangedListener mOverscrollTopChangedListener; 129 private ExpandableView.OnHeightChangedListener mOnHeightChangedListener; 130 private boolean mNeedsAnimation; 131 private boolean mTopPaddingNeedsAnimation; 132 private boolean mDimmedNeedsAnimation; 133 private boolean mDarkNeedsAnimation; 134 private boolean mActivateNeedsAnimation; 135 private boolean mIsExpanded = true; 136 private boolean mChildrenUpdateRequested; 137 private SpeedBumpView mSpeedBumpView; 138 private boolean mIsExpansionChanging; 139 private boolean mExpandingNotification; 140 private boolean mExpandedInThisMotion; 141 private boolean mScrollingEnabled; 142 143 /** 144 * Was the scroller scrolled to the top when the down motion was observed? 145 */ 146 private boolean mScrolledToTopOnFirstDown; 147 148 /** 149 * The minimal amount of over scroll which is needed in order to switch to the quick settings 150 * when over scrolling on a expanded card. 151 */ 152 private float mMinTopOverScrollToEscape; 153 private int mIntrinsicPadding; 154 private int mNotificationTopPadding; 155 private float mTopPaddingOverflow; 156 private boolean mDontReportNextOverScroll; 157 158 /** 159 * The maximum scrollPosition which we are allowed to reach when a notification was expanded. 160 * This is needed to avoid scrolling too far after the notification was collapsed in the same 161 * motion. 162 */ 163 private int mMaxScrollAfterExpand; 164 private OnLongClickListener mLongClickListener; 165 166 /** 167 * Should in this touch motion only be scrolling allowed? It's true when the scroller was 168 * animating. 169 */ 170 private boolean mOnlyScrollingInThisMotion; 171 private ViewGroup mScrollView; 172 private boolean mInterceptDelegateEnabled; 173 private boolean mDelegateToScrollView; 174 175 private ViewTreeObserver.OnPreDrawListener mChildrenUpdater 176 = new ViewTreeObserver.OnPreDrawListener() { 177 @Override 178 public boolean onPreDraw() { 179 updateChildren(); 180 mChildrenUpdateRequested = false; 181 getViewTreeObserver().removeOnPreDrawListener(this); 182 return true; 183 } 184 }; 185 186 public NotificationStackScrollLayout(Context context) { 187 this(context, null); 188 } 189 190 public NotificationStackScrollLayout(Context context, AttributeSet attrs) { 191 this(context, attrs, 0); 192 } 193 194 public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr) { 195 this(context, attrs, defStyleAttr, 0); 196 } 197 198 public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr, 199 int defStyleRes) { 200 super(context, attrs, defStyleAttr, defStyleRes); 201 initView(context); 202 if (DEBUG) { 203 setWillNotDraw(false); 204 mDebugPaint = new Paint(); 205 mDebugPaint.setColor(0xffff0000); 206 mDebugPaint.setStrokeWidth(2); 207 mDebugPaint.setStyle(Paint.Style.STROKE); 208 } 209 } 210 211 @Override 212 protected void onDraw(Canvas canvas) { 213 if (DEBUG) { 214 int y = mCollapsedSize; 215 canvas.drawLine(0, y, getWidth(), y, mDebugPaint); 216 y = (int) (getLayoutHeight() - mBottomStackPeekSize 217 - mBottomStackSlowDownHeight); 218 canvas.drawLine(0, y, getWidth(), y, mDebugPaint); 219 y = (int) (getLayoutHeight() - mBottomStackPeekSize); 220 canvas.drawLine(0, y, getWidth(), y, mDebugPaint); 221 y = (int) getLayoutHeight(); 222 canvas.drawLine(0, y, getWidth(), y, mDebugPaint); 223 y = getHeight() - getEmptyBottomMargin(); 224 canvas.drawLine(0, y, getWidth(), y, mDebugPaint); 225 } 226 } 227 228 private void initView(Context context) { 229 mScroller = new OverScroller(getContext()); 230 setFocusable(true); 231 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); 232 setClipChildren(false); 233 final ViewConfiguration configuration = ViewConfiguration.get(context); 234 mTouchSlop = configuration.getScaledTouchSlop(); 235 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); 236 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); 237 mOverflingDistance = configuration.getScaledOverflingDistance(); 238 float densityScale = getResources().getDisplayMetrics().density; 239 float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop(); 240 mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, pagingTouchSlop); 241 mSwipeHelper.setLongPressListener(mLongClickListener); 242 243 mSidePaddings = context.getResources() 244 .getDimensionPixelSize(R.dimen.notification_side_padding); 245 mCollapsedSize = context.getResources() 246 .getDimensionPixelSize(R.dimen.notification_min_height); 247 mBottomStackPeekSize = context.getResources() 248 .getDimensionPixelSize(R.dimen.bottom_stack_peek_amount); 249 mStackScrollAlgorithm = new StackScrollAlgorithm(context); 250 mStackScrollAlgorithm.setDimmed(mAmbientState.isDimmed()); 251 mPaddingBetweenElementsDimmed = context.getResources() 252 .getDimensionPixelSize(R.dimen.notification_padding_dimmed); 253 mPaddingBetweenElementsNormal = context.getResources() 254 .getDimensionPixelSize(R.dimen.notification_padding); 255 updatePadding(mAmbientState.isDimmed()); 256 int minHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height); 257 int maxHeight = getResources().getDimensionPixelSize(R.dimen.notification_max_height); 258 mExpandHelper = new ExpandHelper(getContext(), this, 259 minHeight, maxHeight); 260 mExpandHelper.setEventSource(this); 261 mExpandHelper.setScrollAdapter(this); 262 mMinTopOverScrollToEscape = getResources().getDimensionPixelSize( 263 R.dimen.min_top_overscroll_to_qs); 264 mNotificationTopPadding = getResources().getDimensionPixelSize( 265 R.dimen.notifications_top_padding); 266 } 267 268 private void updatePadding(boolean dimmed) { 269 mPaddingBetweenElements = dimmed 270 ? mPaddingBetweenElementsDimmed 271 : mPaddingBetweenElementsNormal; 272 mBottomStackSlowDownHeight = mStackScrollAlgorithm.getBottomStackSlowDownLength(); 273 updateContentHeight(); 274 notifyHeightChangeListener(null); 275 } 276 277 private void notifyHeightChangeListener(ExpandableView view) { 278 if (mOnHeightChangedListener != null) { 279 mOnHeightChangedListener.onHeightChanged(view); 280 } 281 } 282 283 @Override 284 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 285 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 286 int mode = MeasureSpec.getMode(widthMeasureSpec); 287 int size = MeasureSpec.getSize(widthMeasureSpec); 288 int childMeasureSpec = MeasureSpec.makeMeasureSpec(size - 2 * mSidePaddings, mode); 289 measureChildren(childMeasureSpec, heightMeasureSpec); 290 } 291 292 @Override 293 protected void onLayout(boolean changed, int l, int t, int r, int b) { 294 295 // we layout all our children centered on the top 296 float centerX = getWidth() / 2.0f; 297 for (int i = 0; i < getChildCount(); i++) { 298 View child = getChildAt(i); 299 float width = child.getMeasuredWidth(); 300 float height = child.getMeasuredHeight(); 301 child.layout((int) (centerX - width / 2.0f), 302 0, 303 (int) (centerX + width / 2.0f), 304 (int) height); 305 } 306 setMaxLayoutHeight(getHeight()); 307 updateContentHeight(); 308 updateScrollPositionIfNecessary(); 309 requestChildrenUpdate(); 310 } 311 312 public void updateSpeedBumpIndex(int newIndex) { 313 int currentIndex = indexOfChild(mSpeedBumpView); 314 315 // If we are currently layouted before the new speed bump index, we have to decrease it. 316 boolean validIndex = newIndex > 0; 317 if (newIndex > getChildCount() - 1) { 318 validIndex = false; 319 newIndex = -1; 320 } 321 if (validIndex && currentIndex != newIndex) { 322 changeViewPosition(mSpeedBumpView, newIndex); 323 } 324 updateSpeedBump(validIndex); 325 mAmbientState.setSpeedBumpIndex(newIndex); 326 } 327 328 public void setChildLocationsChangedListener(OnChildLocationsChangedListener listener) { 329 mListener = listener; 330 } 331 332 /** 333 * Returns the location the given child is currently rendered at. 334 * 335 * @param child the child to get the location for 336 * @return one of {@link ViewState}'s <code>LOCATION_*</code> constants 337 */ 338 public int getChildLocation(View child) { 339 ViewState childViewState = mCurrentStackScrollState.getViewStateForView(child); 340 if (childViewState == null) { 341 return ViewState.LOCATION_UNKNOWN; 342 } 343 return childViewState.location; 344 } 345 346 private void setMaxLayoutHeight(int maxLayoutHeight) { 347 mMaxLayoutHeight = maxLayoutHeight; 348 updateAlgorithmHeightAndPadding(); 349 } 350 351 private void updateAlgorithmHeightAndPadding() { 352 mStackScrollAlgorithm.setLayoutHeight(getLayoutHeight()); 353 mStackScrollAlgorithm.setTopPadding(mTopPadding); 354 } 355 356 /** 357 * @return whether the height of the layout needs to be adapted, in order to ensure that the 358 * last child is not in the bottom stack. 359 */ 360 private boolean needsHeightAdaption() { 361 return getNotGoneChildCount() > 1; 362 } 363 364 /** 365 * Updates the children views according to the stack scroll algorithm. Call this whenever 366 * modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout. 367 */ 368 private void updateChildren() { 369 mAmbientState.setScrollY(mOwnScrollY); 370 mStackScrollAlgorithm.getStackScrollState(mAmbientState, mCurrentStackScrollState); 371 if (!isCurrentlyAnimating() && !mNeedsAnimation) { 372 applyCurrentState(); 373 } else { 374 startAnimationToState(); 375 } 376 } 377 378 private void requestChildrenUpdate() { 379 if (!mChildrenUpdateRequested) { 380 getViewTreeObserver().addOnPreDrawListener(mChildrenUpdater); 381 mChildrenUpdateRequested = true; 382 invalidate(); 383 } 384 } 385 386 private boolean isCurrentlyAnimating() { 387 return mStateAnimator.isRunning(); 388 } 389 390 private void updateScrollPositionIfNecessary() { 391 int scrollRange = getScrollRange(); 392 if (scrollRange < mOwnScrollY) { 393 mOwnScrollY = scrollRange; 394 } 395 } 396 397 public int getTopPadding() { 398 return mTopPadding; 399 } 400 401 private void setTopPadding(int topPadding, boolean animate) { 402 if (mTopPadding != topPadding) { 403 mTopPadding = topPadding; 404 updateAlgorithmHeightAndPadding(); 405 updateContentHeight(); 406 if (animate && mAnimationsEnabled && mIsExpanded) { 407 mTopPaddingNeedsAnimation = true; 408 mNeedsAnimation = true; 409 } 410 requestChildrenUpdate(); 411 notifyHeightChangeListener(null); 412 } 413 } 414 415 /** 416 * Update the height of the stack to a new height. 417 * 418 * @param height the new height of the stack 419 */ 420 public void setStackHeight(float height) { 421 setIsExpanded(height > 0.0f); 422 int newStackHeight = (int) height; 423 int itemHeight = getItemHeight(); 424 int bottomStackPeekSize = mBottomStackPeekSize; 425 int minStackHeight = itemHeight + bottomStackPeekSize; 426 int stackHeight; 427 if (newStackHeight - mTopPadding >= minStackHeight || getNotGoneChildCount() == 0) { 428 setTranslationY(mTopPaddingOverflow); 429 stackHeight = newStackHeight; 430 } else { 431 432 // We did not reach the position yet where we actually start growing, 433 // so we translate the stack upwards. 434 int translationY = (newStackHeight - minStackHeight); 435 // A slight parallax effect is introduced in order for the stack to catch up with 436 // the top card. 437 float partiallyThere = (float) (newStackHeight - mTopPadding) / minStackHeight; 438 partiallyThere = Math.max(0, partiallyThere); 439 translationY += (1 - partiallyThere) * bottomStackPeekSize; 440 setTranslationY(translationY - mTopPadding); 441 stackHeight = (int) (height - (translationY - mTopPadding)); 442 } 443 if (stackHeight != mCurrentStackHeight) { 444 mCurrentStackHeight = stackHeight; 445 updateAlgorithmHeightAndPadding(); 446 requestChildrenUpdate(); 447 } 448 } 449 450 /** 451 * Get the current height of the view. This is at most the msize of the view given by a the 452 * layout but it can also be made smaller by setting {@link #mCurrentStackHeight} 453 * 454 * @return either the layout height or the externally defined height, whichever is smaller 455 */ 456 private int getLayoutHeight() { 457 return Math.min(mMaxLayoutHeight, mCurrentStackHeight); 458 } 459 460 public int getItemHeight() { 461 return mCollapsedSize; 462 } 463 464 public int getBottomStackPeekSize() { 465 return mBottomStackPeekSize; 466 } 467 468 public void setLongPressListener(View.OnLongClickListener listener) { 469 mSwipeHelper.setLongPressListener(listener); 470 mLongClickListener = listener; 471 } 472 473 public void setScrollView(ViewGroup scrollView) { 474 mScrollView = scrollView; 475 } 476 477 public void setInterceptDelegateEnabled(boolean interceptDelegateEnabled) { 478 mInterceptDelegateEnabled = interceptDelegateEnabled; 479 } 480 481 public void onChildDismissed(View v) { 482 if (DEBUG) Log.v(TAG, "onChildDismissed: " + v); 483 final View veto = v.findViewById(R.id.veto); 484 if (veto != null && veto.getVisibility() != View.GONE) { 485 veto.performClick(); 486 } 487 setSwipingInProgress(false); 488 if (mDragAnimPendingChildren.contains(v)) { 489 // We start the swipe and finish it in the same frame, we don't want any animation 490 // for the drag 491 mDragAnimPendingChildren.remove(v); 492 } 493 mSwipedOutViews.add(v); 494 mAmbientState.onDragFinished(v); 495 } 496 497 @Override 498 public void onChildSnappedBack(View animView) { 499 mAmbientState.onDragFinished(animView); 500 if (!mDragAnimPendingChildren.contains(animView)) { 501 if (mAnimationsEnabled) { 502 mSnappedBackChildren.add(animView); 503 mNeedsAnimation = true; 504 } 505 requestChildrenUpdate(); 506 } else { 507 // We start the swipe and snap back in the same frame, we don't want any animation 508 mDragAnimPendingChildren.remove(animView); 509 } 510 } 511 512 @Override 513 public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) { 514 return false; 515 } 516 517 public void onBeginDrag(View v) { 518 setSwipingInProgress(true); 519 mAmbientState.onBeginDrag(v); 520 if (mAnimationsEnabled) { 521 mDragAnimPendingChildren.add(v); 522 mNeedsAnimation = true; 523 } 524 requestChildrenUpdate(); 525 } 526 527 public void onDragCancelled(View v) { 528 setSwipingInProgress(false); 529 } 530 531 public View getChildAtPosition(MotionEvent ev) { 532 return getChildAtPosition(ev.getX(), ev.getY()); 533 } 534 535 public ExpandableView getChildAtRawPosition(float touchX, float touchY) { 536 int[] location = new int[2]; 537 getLocationOnScreen(location); 538 return getChildAtPosition(touchX - location[0], touchY - location[1]); 539 } 540 541 public ExpandableView getChildAtPosition(float touchX, float touchY) { 542 // find the view under the pointer, accounting for GONE views 543 final int count = getChildCount(); 544 for (int childIdx = 0; childIdx < count; childIdx++) { 545 ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx); 546 if (slidingChild.getVisibility() == GONE) { 547 continue; 548 } 549 float childTop = slidingChild.getTranslationY(); 550 float top = childTop + slidingChild.getClipTopAmount(); 551 float bottom = top + slidingChild.getActualHeight(); 552 int left = slidingChild.getLeft(); 553 int right = slidingChild.getRight(); 554 555 if (touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) { 556 return slidingChild; 557 } 558 } 559 return null; 560 } 561 562 public boolean canChildBeExpanded(View v) { 563 return v instanceof ExpandableNotificationRow 564 && ((ExpandableNotificationRow) v).isExpandable(); 565 } 566 567 public void setUserExpandedChild(View v, boolean userExpanded) { 568 if (v instanceof ExpandableNotificationRow) { 569 ((ExpandableNotificationRow) v).setUserExpanded(userExpanded); 570 } 571 } 572 573 public void setUserLockedChild(View v, boolean userLocked) { 574 if (v instanceof ExpandableNotificationRow) { 575 ((ExpandableNotificationRow) v).setUserLocked(userLocked); 576 } 577 removeLongPressCallback(); 578 requestDisallowInterceptTouchEvent(true); 579 } 580 581 @Override 582 public void expansionStateChanged(boolean isExpanding) { 583 mExpandingNotification = isExpanding; 584 if (!mExpandedInThisMotion) { 585 mMaxScrollAfterExpand = mOwnScrollY; 586 mExpandedInThisMotion = true; 587 } 588 } 589 590 public void setScrollingEnabled(boolean enable) { 591 mScrollingEnabled = enable; 592 } 593 594 public void setExpandingEnabled(boolean enable) { 595 mExpandHelper.setEnabled(enable); 596 } 597 598 private boolean isScrollingEnabled() { 599 return mScrollingEnabled; 600 } 601 602 public View getChildContentView(View v) { 603 return v; 604 } 605 606 public boolean canChildBeDismissed(View v) { 607 final View veto = v.findViewById(R.id.veto); 608 return (veto != null && veto.getVisibility() != View.GONE); 609 } 610 611 private void setSwipingInProgress(boolean isSwiped) { 612 mSwipingInProgress = isSwiped; 613 if(isSwiped) { 614 requestDisallowInterceptTouchEvent(true); 615 } 616 } 617 618 @Override 619 protected void onConfigurationChanged(Configuration newConfig) { 620 super.onConfigurationChanged(newConfig); 621 float densityScale = getResources().getDisplayMetrics().density; 622 mSwipeHelper.setDensityScale(densityScale); 623 float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop(); 624 mSwipeHelper.setPagingTouchSlop(pagingTouchSlop); 625 initView(getContext()); 626 } 627 628 public void dismissRowAnimated(View child, int vel) { 629 mSwipeHelper.dismissChild(child, vel); 630 } 631 632 @Override 633 public boolean onTouchEvent(MotionEvent ev) { 634 boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL 635 || ev.getActionMasked()== MotionEvent.ACTION_UP; 636 if (mDelegateToScrollView) { 637 if (isCancelOrUp) { 638 mDelegateToScrollView = false; 639 } 640 transformTouchEvent(ev, this, mScrollView); 641 return mScrollView.onTouchEvent(ev); 642 } 643 boolean expandWantsIt = false; 644 if (!mSwipingInProgress && !mOnlyScrollingInThisMotion && isScrollingEnabled()) { 645 if (isCancelOrUp) { 646 mExpandHelper.onlyObserveMovements(false); 647 } 648 boolean wasExpandingBefore = mExpandingNotification; 649 expandWantsIt = mExpandHelper.onTouchEvent(ev); 650 if (mExpandedInThisMotion && !mExpandingNotification && wasExpandingBefore) { 651 dispatchDownEventToScroller(ev); 652 } 653 } 654 boolean scrollerWantsIt = false; 655 if (!mSwipingInProgress && !mExpandingNotification) { 656 scrollerWantsIt = onScrollTouch(ev); 657 } 658 boolean horizontalSwipeWantsIt = false; 659 if (!mIsBeingDragged 660 && !mExpandingNotification 661 && !mExpandedInThisMotion 662 && !mOnlyScrollingInThisMotion) { 663 horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev); 664 } 665 return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt || super.onTouchEvent(ev); 666 } 667 668 private void dispatchDownEventToScroller(MotionEvent ev) { 669 MotionEvent downEvent = MotionEvent.obtain(ev); 670 downEvent.setAction(MotionEvent.ACTION_DOWN); 671 onScrollTouch(downEvent); 672 downEvent.recycle(); 673 } 674 675 private boolean onScrollTouch(MotionEvent ev) { 676 if (!isScrollingEnabled()) { 677 return false; 678 } 679 initVelocityTrackerIfNotExists(); 680 mVelocityTracker.addMovement(ev); 681 682 final int action = ev.getAction(); 683 684 switch (action & MotionEvent.ACTION_MASK) { 685 case MotionEvent.ACTION_DOWN: { 686 if (getChildCount() == 0 || !isInContentBounds(ev)) { 687 return false; 688 } 689 boolean isBeingDragged = !mScroller.isFinished(); 690 setIsBeingDragged(isBeingDragged); 691 692 /* 693 * If being flinged and user touches, stop the fling. isFinished 694 * will be false if being flinged. 695 */ 696 if (!mScroller.isFinished()) { 697 mScroller.forceFinished(true); 698 } 699 700 // Remember where the motion event started 701 mLastMotionY = (int) ev.getY(); 702 mDownX = (int) ev.getX(); 703 mActivePointerId = ev.getPointerId(0); 704 break; 705 } 706 case MotionEvent.ACTION_MOVE: 707 final int activePointerIndex = ev.findPointerIndex(mActivePointerId); 708 if (activePointerIndex == -1) { 709 Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent"); 710 break; 711 } 712 713 final int y = (int) ev.getY(activePointerIndex); 714 final int x = (int) ev.getX(activePointerIndex); 715 int deltaY = mLastMotionY - y; 716 final int xDiff = Math.abs(x - mDownX); 717 final int yDiff = Math.abs(deltaY); 718 if (!mIsBeingDragged && yDiff > mTouchSlop && yDiff > xDiff) { 719 setIsBeingDragged(true); 720 if (deltaY > 0) { 721 deltaY -= mTouchSlop; 722 } else { 723 deltaY += mTouchSlop; 724 } 725 } 726 if (mIsBeingDragged) { 727 // Scroll to follow the motion event 728 mLastMotionY = y; 729 int range = getScrollRange(); 730 if (mExpandedInThisMotion) { 731 range = Math.min(range, mMaxScrollAfterExpand); 732 } 733 734 float scrollAmount; 735 if (deltaY < 0) { 736 scrollAmount = overScrollDown(deltaY); 737 } else { 738 scrollAmount = overScrollUp(deltaY, range); 739 } 740 741 // Calling overScrollBy will call onOverScrolled, which 742 // calls onScrollChanged if applicable. 743 if (scrollAmount != 0.0f) { 744 // The scrolling motion could not be compensated with the 745 // existing overScroll, we have to scroll the view 746 overScrollBy(0, (int) scrollAmount, 0, mOwnScrollY, 747 0, range, 0, getHeight() / 2, true); 748 } 749 } 750 break; 751 case MotionEvent.ACTION_UP: 752 if (mIsBeingDragged) { 753 final VelocityTracker velocityTracker = mVelocityTracker; 754 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 755 int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId); 756 757 if (shouldOverScrollFling(initialVelocity)) { 758 onOverScrollFling(true, initialVelocity); 759 } else { 760 if (getChildCount() > 0) { 761 if ((Math.abs(initialVelocity) > mMinimumVelocity)) { 762 float currentOverScrollTop = getCurrentOverScrollAmount(true); 763 if (currentOverScrollTop == 0.0f || initialVelocity > 0) { 764 fling(-initialVelocity); 765 } else { 766 onOverScrollFling(false, initialVelocity); 767 } 768 } else { 769 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, 770 getScrollRange())) { 771 postInvalidateOnAnimation(); 772 } 773 } 774 } 775 } 776 777 mActivePointerId = INVALID_POINTER; 778 endDrag(); 779 } 780 781 break; 782 case MotionEvent.ACTION_CANCEL: 783 if (mIsBeingDragged && getChildCount() > 0) { 784 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) { 785 postInvalidateOnAnimation(); 786 } 787 mActivePointerId = INVALID_POINTER; 788 endDrag(); 789 } 790 break; 791 case MotionEvent.ACTION_POINTER_DOWN: { 792 final int index = ev.getActionIndex(); 793 mLastMotionY = (int) ev.getY(index); 794 mDownX = (int) ev.getX(index); 795 mActivePointerId = ev.getPointerId(index); 796 break; 797 } 798 case MotionEvent.ACTION_POINTER_UP: 799 onSecondaryPointerUp(ev); 800 mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId)); 801 mDownX = (int) ev.getX(ev.findPointerIndex(mActivePointerId)); 802 break; 803 } 804 return true; 805 } 806 807 private void onOverScrollFling(boolean open, int initialVelocity) { 808 if (mOverscrollTopChangedListener != null) { 809 mOverscrollTopChangedListener.flingTopOverscroll(initialVelocity, open); 810 } 811 mDontReportNextOverScroll = true; 812 setOverScrollAmount(0.0f, true, false); 813 } 814 815 /** 816 * Perform a scroll upwards and adapt the overscroll amounts accordingly 817 * 818 * @param deltaY The amount to scroll upwards, has to be positive. 819 * @return The amount of scrolling to be performed by the scroller, 820 * not handled by the overScroll amount. 821 */ 822 private float overScrollUp(int deltaY, int range) { 823 deltaY = Math.max(deltaY, 0); 824 float currentTopAmount = getCurrentOverScrollAmount(true); 825 float newTopAmount = currentTopAmount - deltaY; 826 if (currentTopAmount > 0) { 827 setOverScrollAmount(newTopAmount, true /* onTop */, 828 false /* animate */); 829 } 830 // Top overScroll might not grab all scrolling motion, 831 // we have to scroll as well. 832 float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f; 833 float newScrollY = mOwnScrollY + scrollAmount; 834 if (newScrollY > range) { 835 if (!mExpandedInThisMotion) { 836 float currentBottomPixels = getCurrentOverScrolledPixels(false); 837 // We overScroll on the top 838 setOverScrolledPixels(currentBottomPixels + newScrollY - range, 839 false /* onTop */, 840 false /* animate */); 841 } 842 mOwnScrollY = range; 843 scrollAmount = 0.0f; 844 } 845 return scrollAmount; 846 } 847 848 /** 849 * Perform a scroll downward and adapt the overscroll amounts accordingly 850 * 851 * @param deltaY The amount to scroll downwards, has to be negative. 852 * @return The amount of scrolling to be performed by the scroller, 853 * not handled by the overScroll amount. 854 */ 855 private float overScrollDown(int deltaY) { 856 deltaY = Math.min(deltaY, 0); 857 float currentBottomAmount = getCurrentOverScrollAmount(false); 858 float newBottomAmount = currentBottomAmount + deltaY; 859 if (currentBottomAmount > 0) { 860 setOverScrollAmount(newBottomAmount, false /* onTop */, 861 false /* animate */); 862 } 863 // Bottom overScroll might not grab all scrolling motion, 864 // we have to scroll as well. 865 float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f; 866 float newScrollY = mOwnScrollY + scrollAmount; 867 if (newScrollY < 0) { 868 float currentTopPixels = getCurrentOverScrolledPixels(true); 869 // We overScroll on the top 870 setOverScrolledPixels(currentTopPixels - newScrollY, 871 true /* onTop */, 872 false /* animate */); 873 mOwnScrollY = 0; 874 scrollAmount = 0.0f; 875 } 876 return scrollAmount; 877 } 878 879 private void onSecondaryPointerUp(MotionEvent ev) { 880 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> 881 MotionEvent.ACTION_POINTER_INDEX_SHIFT; 882 final int pointerId = ev.getPointerId(pointerIndex); 883 if (pointerId == mActivePointerId) { 884 // This was our active pointer going up. Choose a new 885 // active pointer and adjust accordingly. 886 // TODO: Make this decision more intelligent. 887 final int newPointerIndex = pointerIndex == 0 ? 1 : 0; 888 mLastMotionY = (int) ev.getY(newPointerIndex); 889 mActivePointerId = ev.getPointerId(newPointerIndex); 890 if (mVelocityTracker != null) { 891 mVelocityTracker.clear(); 892 } 893 } 894 } 895 896 private void initVelocityTrackerIfNotExists() { 897 if (mVelocityTracker == null) { 898 mVelocityTracker = VelocityTracker.obtain(); 899 } 900 } 901 902 private void recycleVelocityTracker() { 903 if (mVelocityTracker != null) { 904 mVelocityTracker.recycle(); 905 mVelocityTracker = null; 906 } 907 } 908 909 private void initOrResetVelocityTracker() { 910 if (mVelocityTracker == null) { 911 mVelocityTracker = VelocityTracker.obtain(); 912 } else { 913 mVelocityTracker.clear(); 914 } 915 } 916 917 @Override 918 public void computeScroll() { 919 if (mScroller.computeScrollOffset()) { 920 // This is called at drawing time by ViewGroup. 921 int oldX = mScrollX; 922 int oldY = mOwnScrollY; 923 int x = mScroller.getCurrX(); 924 int y = mScroller.getCurrY(); 925 926 if (oldX != x || oldY != y) { 927 final int range = getScrollRange(); 928 if (y < 0 && oldY >= 0 || y > range && oldY <= range) { 929 float currVelocity = mScroller.getCurrVelocity(); 930 if (currVelocity >= mMinimumVelocity) { 931 mMaxOverScroll = Math.abs(currVelocity) / 1000 * mOverflingDistance; 932 } 933 } 934 935 overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, range, 936 0, (int) (mMaxOverScroll), false); 937 onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY); 938 } 939 940 // Keep on drawing until the animation has finished. 941 postInvalidateOnAnimation(); 942 } 943 } 944 945 @Override 946 protected boolean overScrollBy(int deltaX, int deltaY, 947 int scrollX, int scrollY, 948 int scrollRangeX, int scrollRangeY, 949 int maxOverScrollX, int maxOverScrollY, 950 boolean isTouchEvent) { 951 952 int newScrollY = scrollY + deltaY; 953 954 final int top = -maxOverScrollY; 955 final int bottom = maxOverScrollY + scrollRangeY; 956 957 boolean clampedY = false; 958 if (newScrollY > bottom) { 959 newScrollY = bottom; 960 clampedY = true; 961 } else if (newScrollY < top) { 962 newScrollY = top; 963 clampedY = true; 964 } 965 966 onOverScrolled(0, newScrollY, false, clampedY); 967 968 return clampedY; 969 } 970 971 /** 972 * Set the amount of overScrolled pixels which will force the view to apply a rubber-banded 973 * overscroll effect based on numPixels. By default this will also cancel animations on the 974 * same overScroll edge. 975 * 976 * @param numPixels The amount of pixels to overScroll by. These will be scaled according to 977 * the rubber-banding logic. 978 * @param onTop Should the effect be applied on top of the scroller. 979 * @param animate Should an animation be performed. 980 */ 981 public void setOverScrolledPixels(float numPixels, boolean onTop, boolean animate) { 982 setOverScrollAmount(numPixels * getRubberBandFactor(onTop), onTop, animate, true); 983 } 984 985 /** 986 * Set the effective overScroll amount which will be directly reflected in the layout. 987 * By default this will also cancel animations on the same overScroll edge. 988 * 989 * @param amount The amount to overScroll by. 990 * @param onTop Should the effect be applied on top of the scroller. 991 * @param animate Should an animation be performed. 992 */ 993 public void setOverScrollAmount(float amount, boolean onTop, boolean animate) { 994 setOverScrollAmount(amount, onTop, animate, true); 995 } 996 997 /** 998 * Set the effective overScroll amount which will be directly reflected in the layout. 999 * 1000 * @param amount The amount to overScroll by. 1001 * @param onTop Should the effect be applied on top of the scroller. 1002 * @param animate Should an animation be performed. 1003 * @param cancelAnimators Should running animations be cancelled. 1004 */ 1005 public void setOverScrollAmount(float amount, boolean onTop, boolean animate, 1006 boolean cancelAnimators) { 1007 setOverScrollAmount(amount, onTop, animate, cancelAnimators, isRubberbanded(onTop)); 1008 } 1009 1010 /** 1011 * Set the effective overScroll amount which will be directly reflected in the layout. 1012 * 1013 * @param amount The amount to overScroll by. 1014 * @param onTop Should the effect be applied on top of the scroller. 1015 * @param animate Should an animation be performed. 1016 * @param cancelAnimators Should running animations be cancelled. 1017 * @param isRubberbanded The value which will be passed to 1018 * {@link OnOverscrollTopChangedListener#onOverscrollTopChanged} 1019 */ 1020 public void setOverScrollAmount(float amount, boolean onTop, boolean animate, 1021 boolean cancelAnimators, boolean isRubberbanded) { 1022 if (cancelAnimators) { 1023 mStateAnimator.cancelOverScrollAnimators(onTop); 1024 } 1025 setOverScrollAmountInternal(amount, onTop, animate, isRubberbanded); 1026 } 1027 1028 private void setOverScrollAmountInternal(float amount, boolean onTop, boolean animate, 1029 boolean isRubberbanded) { 1030 amount = Math.max(0, amount); 1031 if (animate) { 1032 mStateAnimator.animateOverScrollToAmount(amount, onTop, isRubberbanded); 1033 } else { 1034 setOverScrolledPixels(amount / getRubberBandFactor(onTop), onTop); 1035 mAmbientState.setOverScrollAmount(amount, onTop); 1036 if (onTop) { 1037 notifyOverscrollTopListener(amount, isRubberbanded); 1038 } 1039 requestChildrenUpdate(); 1040 } 1041 } 1042 1043 private void notifyOverscrollTopListener(float amount, boolean isRubberbanded) { 1044 mExpandHelper.onlyObserveMovements(amount > 1.0f); 1045 if (mDontReportNextOverScroll) { 1046 mDontReportNextOverScroll = false; 1047 return; 1048 } 1049 if (mOverscrollTopChangedListener != null) { 1050 mOverscrollTopChangedListener.onOverscrollTopChanged(amount, isRubberbanded); 1051 } 1052 } 1053 1054 public void setOverscrollTopChangedListener( 1055 OnOverscrollTopChangedListener overscrollTopChangedListener) { 1056 mOverscrollTopChangedListener = overscrollTopChangedListener; 1057 } 1058 1059 public float getCurrentOverScrollAmount(boolean top) { 1060 return mAmbientState.getOverScrollAmount(top); 1061 } 1062 1063 public float getCurrentOverScrolledPixels(boolean top) { 1064 return top? mOverScrolledTopPixels : mOverScrolledBottomPixels; 1065 } 1066 1067 private void setOverScrolledPixels(float amount, boolean onTop) { 1068 if (onTop) { 1069 mOverScrolledTopPixels = amount; 1070 } else { 1071 mOverScrolledBottomPixels = amount; 1072 } 1073 } 1074 1075 private void customScrollTo(int y) { 1076 mOwnScrollY = y; 1077 updateChildren(); 1078 } 1079 1080 @Override 1081 protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) { 1082 // Treat animating scrolls differently; see #computeScroll() for why. 1083 if (!mScroller.isFinished()) { 1084 final int oldX = mScrollX; 1085 final int oldY = mOwnScrollY; 1086 mScrollX = scrollX; 1087 mOwnScrollY = scrollY; 1088 if (clampedY) { 1089 springBack(); 1090 } else { 1091 onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY); 1092 invalidateParentIfNeeded(); 1093 updateChildren(); 1094 float overScrollTop = getCurrentOverScrollAmount(true); 1095 if (mOwnScrollY < 0) { 1096 notifyOverscrollTopListener(-mOwnScrollY, isRubberbanded(true)); 1097 } else { 1098 notifyOverscrollTopListener(overScrollTop, isRubberbanded(true)); 1099 } 1100 } 1101 } else { 1102 customScrollTo(scrollY); 1103 scrollTo(scrollX, mScrollY); 1104 } 1105 } 1106 1107 private void springBack() { 1108 int scrollRange = getScrollRange(); 1109 boolean overScrolledTop = mOwnScrollY <= 0; 1110 boolean overScrolledBottom = mOwnScrollY >= scrollRange; 1111 if (overScrolledTop || overScrolledBottom) { 1112 boolean onTop; 1113 float newAmount; 1114 if (overScrolledTop) { 1115 onTop = true; 1116 newAmount = -mOwnScrollY; 1117 mOwnScrollY = 0; 1118 mDontReportNextOverScroll = true; 1119 } else { 1120 onTop = false; 1121 newAmount = mOwnScrollY - scrollRange; 1122 mOwnScrollY = scrollRange; 1123 } 1124 setOverScrollAmount(newAmount, onTop, false); 1125 setOverScrollAmount(0.0f, onTop, true); 1126 mScroller.forceFinished(true); 1127 } 1128 } 1129 1130 private int getScrollRange() { 1131 int scrollRange = 0; 1132 ExpandableView firstChild = (ExpandableView) getFirstChildNotGone(); 1133 if (firstChild != null) { 1134 int contentHeight = getContentHeight(); 1135 int firstChildMaxExpandHeight = getMaxExpandHeight(firstChild); 1136 scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight + mBottomStackPeekSize 1137 + mBottomStackSlowDownHeight); 1138 if (scrollRange > 0) { 1139 View lastChild = getLastChildNotGone(); 1140 // We want to at least be able collapse the first item and not ending in a weird 1141 // end state. 1142 scrollRange = Math.max(scrollRange, firstChildMaxExpandHeight - mCollapsedSize); 1143 } 1144 } 1145 return scrollRange; 1146 } 1147 1148 /** 1149 * @return the first child which has visibility unequal to GONE 1150 */ 1151 private View getFirstChildNotGone() { 1152 int childCount = getChildCount(); 1153 for (int i = 0; i < childCount; i++) { 1154 View child = getChildAt(i); 1155 if (child.getVisibility() != View.GONE) { 1156 return child; 1157 } 1158 } 1159 return null; 1160 } 1161 1162 /** 1163 * @return The first child which has visibility unequal to GONE which is currently below the 1164 * given translationY or equal to it. 1165 */ 1166 private View getFirstChildBelowTranlsationY(float translationY) { 1167 int childCount = getChildCount(); 1168 for (int i = 0; i < childCount; i++) { 1169 View child = getChildAt(i); 1170 if (child.getVisibility() != View.GONE && child.getTranslationY() >= translationY) { 1171 return child; 1172 } 1173 } 1174 return null; 1175 } 1176 1177 /** 1178 * @return the last child which has visibility unequal to GONE 1179 */ 1180 public View getLastChildNotGone() { 1181 int childCount = getChildCount(); 1182 for (int i = childCount - 1; i >= 0; i--) { 1183 View child = getChildAt(i); 1184 if (child.getVisibility() != View.GONE) { 1185 return child; 1186 } 1187 } 1188 return null; 1189 } 1190 1191 /** 1192 * @return the number of children which have visibility unequal to GONE 1193 */ 1194 public int getNotGoneChildCount() { 1195 int childCount = getChildCount(); 1196 int count = 0; 1197 for (int i = 0; i < childCount; i++) { 1198 View child = getChildAt(i); 1199 if (child.getVisibility() != View.GONE) { 1200 count++; 1201 } 1202 } 1203 return count; 1204 } 1205 1206 private int getMaxExpandHeight(View view) { 1207 if (view instanceof ExpandableNotificationRow) { 1208 ExpandableNotificationRow row = (ExpandableNotificationRow) view; 1209 return row.getIntrinsicHeight(); 1210 } 1211 return view.getHeight(); 1212 } 1213 1214 public int getContentHeight() { 1215 return mContentHeight; 1216 } 1217 1218 private void updateContentHeight() { 1219 int height = 0; 1220 for (int i = 0; i < getChildCount(); i++) { 1221 View child = getChildAt(i); 1222 if (child.getVisibility() != View.GONE) { 1223 if (height != 0) { 1224 // add the padding before this element 1225 height += mPaddingBetweenElements; 1226 } 1227 if (child instanceof ExpandableNotificationRow) { 1228 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 1229 height += row.getIntrinsicHeight(); 1230 } else if (child instanceof ExpandableView) { 1231 ExpandableView expandableView = (ExpandableView) child; 1232 height += expandableView.getActualHeight(); 1233 } 1234 } 1235 } 1236 mContentHeight = height + mTopPadding; 1237 } 1238 1239 /** 1240 * Fling the scroll view 1241 * 1242 * @param velocityY The initial velocity in the Y direction. Positive 1243 * numbers mean that the finger/cursor is moving down the screen, 1244 * which means we want to scroll towards the top. 1245 */ 1246 private void fling(int velocityY) { 1247 if (getChildCount() > 0) { 1248 int scrollRange = getScrollRange(); 1249 1250 float topAmount = getCurrentOverScrollAmount(true); 1251 float bottomAmount = getCurrentOverScrollAmount(false); 1252 if (velocityY < 0 && topAmount > 0) { 1253 mOwnScrollY -= (int) topAmount; 1254 mDontReportNextOverScroll = true; 1255 setOverScrollAmount(0, true, false); 1256 mMaxOverScroll = Math.abs(velocityY) / 1000f * getRubberBandFactor(true /* onTop */) 1257 * mOverflingDistance + topAmount; 1258 } else if (velocityY > 0 && bottomAmount > 0) { 1259 mOwnScrollY += bottomAmount; 1260 setOverScrollAmount(0, false, false); 1261 mMaxOverScroll = Math.abs(velocityY) / 1000f 1262 * getRubberBandFactor(false /* onTop */) * mOverflingDistance 1263 + bottomAmount; 1264 } else { 1265 // it will be set once we reach the boundary 1266 mMaxOverScroll = 0.0f; 1267 } 1268 mScroller.fling(mScrollX, mOwnScrollY, 1, velocityY, 0, 0, 0, 1269 Math.max(0, scrollRange), 0, Integer.MAX_VALUE / 2); 1270 1271 postInvalidateOnAnimation(); 1272 } 1273 } 1274 1275 /** 1276 * @return Whether a fling performed on the top overscroll edge lead to the expanded 1277 * overScroll view (i.e QS). 1278 */ 1279 private boolean shouldOverScrollFling(int initialVelocity) { 1280 float topOverScroll = getCurrentOverScrollAmount(true); 1281 return mScrolledToTopOnFirstDown 1282 && !mExpandedInThisMotion 1283 && topOverScroll > mMinTopOverScrollToEscape 1284 && initialVelocity > 0; 1285 } 1286 1287 public void updateTopPadding(float qsHeight, int scrollY, boolean animate) { 1288 float start = qsHeight - scrollY + mNotificationTopPadding; 1289 float stackHeight = getHeight() - start; 1290 int minStackHeight = getMinStackHeight(); 1291 if (stackHeight <= minStackHeight) { 1292 float overflow = minStackHeight - stackHeight; 1293 stackHeight = minStackHeight; 1294 start = getHeight() - stackHeight; 1295 setTranslationY(overflow); 1296 mTopPaddingOverflow = overflow; 1297 } else { 1298 setTranslationY(0); 1299 mTopPaddingOverflow = 0; 1300 } 1301 setTopPadding(clampPadding((int) start), animate); 1302 } 1303 1304 public int getNotificationTopPadding() { 1305 return mNotificationTopPadding; 1306 } 1307 1308 public int getMinStackHeight() { 1309 return mCollapsedSize + mBottomStackPeekSize; 1310 } 1311 1312 public float getTopPaddingOverflow() { 1313 return mTopPaddingOverflow; 1314 } 1315 1316 public int getPeekHeight() { 1317 return mIntrinsicPadding + mCollapsedSize + mBottomStackPeekSize; 1318 } 1319 1320 private int clampPadding(int desiredPadding) { 1321 return Math.max(desiredPadding, mIntrinsicPadding); 1322 } 1323 1324 private float getRubberBandFactor(boolean onTop) { 1325 if (!onTop) { 1326 return RUBBER_BAND_FACTOR_NORMAL; 1327 } 1328 if (mExpandedInThisMotion) { 1329 return RUBBER_BAND_FACTOR_AFTER_EXPAND; 1330 } else if (mIsExpansionChanging) { 1331 return RUBBER_BAND_FACTOR_ON_PANEL_EXPAND; 1332 } else if (mScrolledToTopOnFirstDown) { 1333 return 1.0f; 1334 } 1335 return RUBBER_BAND_FACTOR_NORMAL; 1336 } 1337 1338 /** 1339 * Accompanying function for {@link #getRubberBandFactor}: Returns true if the overscroll is 1340 * rubberbanded, false if it is technically an overscroll but rather a motion to expand the 1341 * overscroll view (e.g. expand QS). 1342 */ 1343 private boolean isRubberbanded(boolean onTop) { 1344 return !onTop || mExpandedInThisMotion || mIsExpansionChanging 1345 || !mScrolledToTopOnFirstDown; 1346 } 1347 1348 private void endDrag() { 1349 setIsBeingDragged(false); 1350 1351 recycleVelocityTracker(); 1352 1353 if (getCurrentOverScrollAmount(true /* onTop */) > 0) { 1354 setOverScrollAmount(0, true /* onTop */, true /* animate */); 1355 } 1356 if (getCurrentOverScrollAmount(false /* onTop */) > 0) { 1357 setOverScrollAmount(0, false /* onTop */, true /* animate */); 1358 } 1359 } 1360 1361 private void transformTouchEvent(MotionEvent ev, View sourceView, View targetView) { 1362 ev.offsetLocation(sourceView.getX(), sourceView.getY()); 1363 ev.offsetLocation(-targetView.getX(), -targetView.getY()); 1364 } 1365 1366 @Override 1367 public boolean onInterceptTouchEvent(MotionEvent ev) { 1368 if (mInterceptDelegateEnabled) { 1369 transformTouchEvent(ev, this, mScrollView); 1370 if (mScrollView.onInterceptTouchEvent(ev)) { 1371 mDelegateToScrollView = true; 1372 removeLongPressCallback(); 1373 return true; 1374 } 1375 transformTouchEvent(ev, mScrollView, this); 1376 } 1377 initDownStates(ev); 1378 boolean expandWantsIt = false; 1379 if (!mSwipingInProgress && !mOnlyScrollingInThisMotion && isScrollingEnabled()) { 1380 expandWantsIt = mExpandHelper.onInterceptTouchEvent(ev); 1381 } 1382 boolean scrollWantsIt = false; 1383 if (!mSwipingInProgress && !mExpandingNotification) { 1384 scrollWantsIt = onInterceptTouchEventScroll(ev); 1385 } 1386 boolean swipeWantsIt = false; 1387 if (!mIsBeingDragged 1388 && !mExpandingNotification 1389 && !mExpandedInThisMotion 1390 && !mOnlyScrollingInThisMotion) { 1391 swipeWantsIt = mSwipeHelper.onInterceptTouchEvent(ev); 1392 } 1393 return swipeWantsIt || scrollWantsIt || expandWantsIt || super.onInterceptTouchEvent(ev); 1394 } 1395 1396 private void initDownStates(MotionEvent ev) { 1397 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 1398 mExpandedInThisMotion = false; 1399 mOnlyScrollingInThisMotion = !mScroller.isFinished(); 1400 } 1401 } 1402 1403 @Override 1404 protected void onViewRemoved(View child) { 1405 super.onViewRemoved(child); 1406 mStackScrollAlgorithm.notifyChildrenChanged(this); 1407 if (mChangePositionInProgress) { 1408 // This is only a position change, don't do anything special 1409 return; 1410 } 1411 ((ExpandableView) child).setOnHeightChangedListener(null); 1412 mCurrentStackScrollState.removeViewStateForView(child); 1413 updateScrollStateForRemovedChild(child); 1414 boolean animationGenerated = generateRemoveAnimation(child); 1415 if (animationGenerated && !mSwipedOutViews.contains(child)) { 1416 // Add this view to an overlay in order to ensure that it will still be temporary 1417 // drawn when removed 1418 getOverlay().add(child); 1419 } 1420 } 1421 1422 /** 1423 * Generate a remove animation for a child view. 1424 * 1425 * @param child The view to generate the remove animation for. 1426 * @return Whether an animation was generated. 1427 */ 1428 private boolean generateRemoveAnimation(View child) { 1429 if (mIsExpanded && mAnimationsEnabled) { 1430 if (!mChildrenToAddAnimated.contains(child)) { 1431 // Generate Animations 1432 mChildrenToRemoveAnimated.add(child); 1433 mNeedsAnimation = true; 1434 return true; 1435 } else { 1436 mChildrenToAddAnimated.remove(child); 1437 return false; 1438 } 1439 } 1440 return false; 1441 } 1442 1443 /** 1444 * Updates the scroll position when a child was removed 1445 * 1446 * @param removedChild the removed child 1447 */ 1448 private void updateScrollStateForRemovedChild(View removedChild) { 1449 int startingPosition = getPositionInLinearLayout(removedChild); 1450 int childHeight = removedChild.getHeight() + mPaddingBetweenElements; 1451 int endPosition = startingPosition + childHeight; 1452 if (endPosition <= mOwnScrollY) { 1453 // This child is fully scrolled of the top, so we have to deduct its height from the 1454 // scrollPosition 1455 mOwnScrollY -= childHeight; 1456 } else if (startingPosition < mOwnScrollY) { 1457 // This child is currently being scrolled into, set the scroll position to the start of 1458 // this child 1459 mOwnScrollY = startingPosition; 1460 } 1461 } 1462 1463 private int getPositionInLinearLayout(View requestedChild) { 1464 int position = 0; 1465 for (int i = 0; i < getChildCount(); i++) { 1466 View child = getChildAt(i); 1467 if (child == requestedChild) { 1468 return position; 1469 } 1470 if (child.getVisibility() != View.GONE) { 1471 position += child.getHeight(); 1472 if (i < getChildCount()-1) { 1473 position += mPaddingBetweenElements; 1474 } 1475 } 1476 } 1477 return 0; 1478 } 1479 1480 @Override 1481 protected void onViewAdded(View child) { 1482 super.onViewAdded(child); 1483 mStackScrollAlgorithm.notifyChildrenChanged(this); 1484 ((ExpandableView) child).setOnHeightChangedListener(this); 1485 generateAddAnimation(child); 1486 } 1487 1488 public void setAnimationsEnabled(boolean animationsEnabled) { 1489 mAnimationsEnabled = animationsEnabled; 1490 } 1491 1492 public boolean isAddOrRemoveAnimationPending() { 1493 return mNeedsAnimation 1494 && (!mChildrenToAddAnimated.isEmpty() || !mChildrenToRemoveAnimated.isEmpty()); 1495 } 1496 /** 1497 * Generate an animation for an added child view. 1498 * 1499 * @param child The view to be added. 1500 */ 1501 public void generateAddAnimation(View child) { 1502 if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress) { 1503 // Generate Animations 1504 mChildrenToAddAnimated.add(child); 1505 mNeedsAnimation = true; 1506 } 1507 } 1508 1509 /** 1510 * Change the position of child to a new location 1511 * 1512 * @param child the view to change the position for 1513 * @param newIndex the new index 1514 */ 1515 public void changeViewPosition(View child, int newIndex) { 1516 if (child != null && child.getParent() == this) { 1517 mChangePositionInProgress = true; 1518 removeView(child); 1519 addView(child, newIndex); 1520 mChangePositionInProgress = false; 1521 if (mIsExpanded && mAnimationsEnabled) { 1522 mChildrenChangingPositions.add(child); 1523 mNeedsAnimation = true; 1524 } 1525 } 1526 } 1527 1528 private void startAnimationToState() { 1529 if (mNeedsAnimation) { 1530 generateChildHierarchyEvents(); 1531 mNeedsAnimation = false; 1532 } 1533 if (!mAnimationEvents.isEmpty() || isCurrentlyAnimating()) { 1534 mStateAnimator.startAnimationForEvents(mAnimationEvents, mCurrentStackScrollState); 1535 mAnimationEvents.clear(); 1536 } else { 1537 applyCurrentState(); 1538 } 1539 } 1540 1541 private void generateChildHierarchyEvents() { 1542 generateChildRemovalEvents(); 1543 generateChildAdditionEvents(); 1544 generatePositionChangeEvents(); 1545 generateSnapBackEvents(); 1546 generateDragEvents(); 1547 generateTopPaddingEvent(); 1548 generateActivateEvent(); 1549 generateDimmedEvent(); 1550 generateDarkEvent(); 1551 mNeedsAnimation = false; 1552 } 1553 1554 private void generateSnapBackEvents() { 1555 for (View child : mSnappedBackChildren) { 1556 mAnimationEvents.add(new AnimationEvent(child, 1557 AnimationEvent.ANIMATION_TYPE_SNAP_BACK)); 1558 } 1559 mSnappedBackChildren.clear(); 1560 } 1561 1562 private void generateDragEvents() { 1563 for (View child : mDragAnimPendingChildren) { 1564 mAnimationEvents.add(new AnimationEvent(child, 1565 AnimationEvent.ANIMATION_TYPE_START_DRAG)); 1566 } 1567 mDragAnimPendingChildren.clear(); 1568 } 1569 1570 private void generateChildRemovalEvents() { 1571 for (View child : mChildrenToRemoveAnimated) { 1572 boolean childWasSwipedOut = mSwipedOutViews.contains(child); 1573 int animationType = childWasSwipedOut 1574 ? AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT 1575 : AnimationEvent.ANIMATION_TYPE_REMOVE; 1576 AnimationEvent event = new AnimationEvent(child, animationType); 1577 1578 // we need to know the view after this one 1579 event.viewAfterChangingView = getFirstChildBelowTranlsationY(child.getTranslationY()); 1580 mAnimationEvents.add(event); 1581 } 1582 mSwipedOutViews.clear(); 1583 mChildrenToRemoveAnimated.clear(); 1584 } 1585 1586 private void generatePositionChangeEvents() { 1587 for (View child : mChildrenChangingPositions) { 1588 mAnimationEvents.add(new AnimationEvent(child, 1589 AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION)); 1590 } 1591 mChildrenChangingPositions.clear(); 1592 } 1593 1594 private void generateChildAdditionEvents() { 1595 for (View child : mChildrenToAddAnimated) { 1596 mAnimationEvents.add(new AnimationEvent(child, 1597 AnimationEvent.ANIMATION_TYPE_ADD)); 1598 } 1599 mChildrenToAddAnimated.clear(); 1600 } 1601 1602 private void generateTopPaddingEvent() { 1603 if (mTopPaddingNeedsAnimation) { 1604 mAnimationEvents.add( 1605 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_TOP_PADDING_CHANGED)); 1606 } 1607 mTopPaddingNeedsAnimation = false; 1608 } 1609 1610 private void generateActivateEvent() { 1611 if (mActivateNeedsAnimation) { 1612 mAnimationEvents.add( 1613 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_ACTIVATED_CHILD)); 1614 } 1615 mActivateNeedsAnimation = false; 1616 } 1617 1618 private void generateDimmedEvent() { 1619 if (mDimmedNeedsAnimation) { 1620 mAnimationEvents.add( 1621 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DIMMED)); 1622 } 1623 mDimmedNeedsAnimation = false; 1624 } 1625 1626 private void generateDarkEvent() { 1627 if (mDarkNeedsAnimation) { 1628 mAnimationEvents.add( 1629 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DARK)); 1630 } 1631 mDarkNeedsAnimation = false; 1632 } 1633 1634 private boolean onInterceptTouchEventScroll(MotionEvent ev) { 1635 if (!isScrollingEnabled()) { 1636 return false; 1637 } 1638 /* 1639 * This method JUST determines whether we want to intercept the motion. 1640 * If we return true, onMotionEvent will be called and we do the actual 1641 * scrolling there. 1642 */ 1643 1644 /* 1645 * Shortcut the most recurring case: the user is in the dragging 1646 * state and he is moving his finger. We want to intercept this 1647 * motion. 1648 */ 1649 final int action = ev.getAction(); 1650 if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) { 1651 return true; 1652 } 1653 1654 switch (action & MotionEvent.ACTION_MASK) { 1655 case MotionEvent.ACTION_MOVE: { 1656 /* 1657 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check 1658 * whether the user has moved far enough from his original down touch. 1659 */ 1660 1661 /* 1662 * Locally do absolute value. mLastMotionY is set to the y value 1663 * of the down event. 1664 */ 1665 final int activePointerId = mActivePointerId; 1666 if (activePointerId == INVALID_POINTER) { 1667 // If we don't have a valid id, the touch down wasn't on content. 1668 break; 1669 } 1670 1671 final int pointerIndex = ev.findPointerIndex(activePointerId); 1672 if (pointerIndex == -1) { 1673 Log.e(TAG, "Invalid pointerId=" + activePointerId 1674 + " in onInterceptTouchEvent"); 1675 break; 1676 } 1677 1678 final int y = (int) ev.getY(pointerIndex); 1679 final int x = (int) ev.getX(pointerIndex); 1680 final int yDiff = Math.abs(y - mLastMotionY); 1681 final int xDiff = Math.abs(x - mDownX); 1682 if (yDiff > mTouchSlop && yDiff > xDiff) { 1683 setIsBeingDragged(true); 1684 mLastMotionY = y; 1685 mDownX = x; 1686 initVelocityTrackerIfNotExists(); 1687 mVelocityTracker.addMovement(ev); 1688 } 1689 break; 1690 } 1691 1692 case MotionEvent.ACTION_DOWN: { 1693 final int y = (int) ev.getY(); 1694 if (getChildAtPosition(ev.getX(), y) == null) { 1695 setIsBeingDragged(false); 1696 recycleVelocityTracker(); 1697 break; 1698 } 1699 1700 /* 1701 * Remember location of down touch. 1702 * ACTION_DOWN always refers to pointer index 0. 1703 */ 1704 mLastMotionY = y; 1705 mDownX = (int) ev.getX(); 1706 mActivePointerId = ev.getPointerId(0); 1707 mScrolledToTopOnFirstDown = isScrolledToTop(); 1708 1709 initOrResetVelocityTracker(); 1710 mVelocityTracker.addMovement(ev); 1711 /* 1712 * If being flinged and user touches the screen, initiate drag; 1713 * otherwise don't. mScroller.isFinished should be false when 1714 * being flinged. 1715 */ 1716 boolean isBeingDragged = !mScroller.isFinished(); 1717 setIsBeingDragged(isBeingDragged); 1718 break; 1719 } 1720 1721 case MotionEvent.ACTION_CANCEL: 1722 case MotionEvent.ACTION_UP: 1723 /* Release the drag */ 1724 setIsBeingDragged(false); 1725 mActivePointerId = INVALID_POINTER; 1726 recycleVelocityTracker(); 1727 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) { 1728 postInvalidateOnAnimation(); 1729 } 1730 break; 1731 case MotionEvent.ACTION_POINTER_UP: 1732 onSecondaryPointerUp(ev); 1733 break; 1734 } 1735 1736 /* 1737 * The only time we want to intercept motion events is if we are in the 1738 * drag mode. 1739 */ 1740 return mIsBeingDragged; 1741 } 1742 1743 /** 1744 * @return Whether the specified motion event is actually happening over the content. 1745 */ 1746 private boolean isInContentBounds(MotionEvent event) { 1747 return event.getY() < getHeight() - getEmptyBottomMargin(); 1748 } 1749 1750 private void setIsBeingDragged(boolean isDragged) { 1751 mIsBeingDragged = isDragged; 1752 if (isDragged) { 1753 requestDisallowInterceptTouchEvent(true); 1754 removeLongPressCallback(); 1755 } 1756 } 1757 1758 @Override 1759 public void onWindowFocusChanged(boolean hasWindowFocus) { 1760 super.onWindowFocusChanged(hasWindowFocus); 1761 if (!hasWindowFocus) { 1762 removeLongPressCallback(); 1763 } 1764 } 1765 1766 public void removeLongPressCallback() { 1767 mSwipeHelper.removeLongPressCallback(); 1768 } 1769 1770 @Override 1771 public boolean isScrolledToTop() { 1772 return mOwnScrollY == 0; 1773 } 1774 1775 @Override 1776 public boolean isScrolledToBottom() { 1777 return mOwnScrollY >= getScrollRange(); 1778 } 1779 1780 @Override 1781 public View getHostView() { 1782 return this; 1783 } 1784 1785 public int getEmptyBottomMargin() { 1786 int emptyMargin = mMaxLayoutHeight - mContentHeight; 1787 if (needsHeightAdaption()) { 1788 emptyMargin = emptyMargin - mBottomStackSlowDownHeight - mBottomStackPeekSize; 1789 } else { 1790 emptyMargin = emptyMargin - mBottomStackPeekSize; 1791 } 1792 return Math.max(emptyMargin, 0); 1793 } 1794 1795 public void onExpansionStarted() { 1796 mIsExpansionChanging = true; 1797 mStackScrollAlgorithm.onExpansionStarted(mCurrentStackScrollState); 1798 } 1799 1800 public void onExpansionStopped() { 1801 mIsExpansionChanging = false; 1802 mStackScrollAlgorithm.onExpansionStopped(); 1803 } 1804 1805 private void setIsExpanded(boolean isExpanded) { 1806 mIsExpanded = isExpanded; 1807 mStackScrollAlgorithm.setIsExpanded(isExpanded); 1808 if (!isExpanded) { 1809 mOwnScrollY = 0; 1810 } 1811 } 1812 1813 @Override 1814 public void onHeightChanged(ExpandableView view) { 1815 updateContentHeight(); 1816 updateScrollPositionIfNecessary(); 1817 notifyHeightChangeListener(view); 1818 requestChildrenUpdate(); 1819 } 1820 1821 public void setOnHeightChangedListener( 1822 ExpandableView.OnHeightChangedListener mOnHeightChangedListener) { 1823 this.mOnHeightChangedListener = mOnHeightChangedListener; 1824 } 1825 1826 public void onChildAnimationFinished() { 1827 requestChildrenUpdate(); 1828 } 1829 1830 /** 1831 * See {@link AmbientState#setDimmed}. 1832 */ 1833 public void setDimmed(boolean dimmed, boolean animate) { 1834 mStackScrollAlgorithm.setDimmed(dimmed); 1835 mAmbientState.setDimmed(dimmed); 1836 updatePadding(dimmed); 1837 if (animate && mAnimationsEnabled) { 1838 mDimmedNeedsAnimation = true; 1839 mNeedsAnimation = true; 1840 } 1841 requestChildrenUpdate(); 1842 } 1843 1844 /** 1845 * See {@link AmbientState#setActivatedChild}. 1846 */ 1847 public void setActivatedChild(ActivatableNotificationView activatedChild) { 1848 mAmbientState.setActivatedChild(activatedChild); 1849 if (mAnimationsEnabled) { 1850 mActivateNeedsAnimation = true; 1851 mNeedsAnimation = true; 1852 } 1853 requestChildrenUpdate(); 1854 } 1855 1856 public ActivatableNotificationView getActivatedChild() { 1857 return mAmbientState.getActivatedChild(); 1858 } 1859 1860 private void applyCurrentState() { 1861 mCurrentStackScrollState.apply(); 1862 if (mListener != null) { 1863 mListener.onChildLocationsChanged(this); 1864 } 1865 } 1866 1867 public void setSpeedBumpView(SpeedBumpView speedBumpView) { 1868 mSpeedBumpView = speedBumpView; 1869 addView(speedBumpView); 1870 } 1871 1872 private void updateSpeedBump(boolean visible) { 1873 boolean notGoneBefore = mSpeedBumpView.getVisibility() != GONE; 1874 if (visible != notGoneBefore) { 1875 int newVisibility = visible ? VISIBLE : GONE; 1876 mSpeedBumpView.setVisibility(newVisibility); 1877 if (visible) { 1878 // Make invisible to ensure that the appear animation is played. 1879 mSpeedBumpView.setInvisible(); 1880 if (!mIsExpansionChanging) { 1881 generateAddAnimation(mSpeedBumpView); 1882 } 1883 } else { 1884 mSpeedBumpView.performVisibilityAnimation(false); 1885 generateRemoveAnimation(mSpeedBumpView); 1886 } 1887 } 1888 } 1889 1890 public void goToFullShade() { 1891 updateSpeedBump(true); 1892 } 1893 1894 public void cancelExpandHelper() { 1895 mExpandHelper.cancel(); 1896 } 1897 1898 public void setIntrinsicPadding(int intrinsicPadding) { 1899 mIntrinsicPadding = intrinsicPadding; 1900 } 1901 1902 public int getIntrinsicPadding() { 1903 return mIntrinsicPadding; 1904 } 1905 1906 /** 1907 * @return the y position of the first notification 1908 */ 1909 public float getNotificationsTopY() { 1910 return mTopPadding + getTranslationY(); 1911 } 1912 1913 @Override 1914 public boolean shouldDelayChildPressedState() { 1915 return true; 1916 } 1917 1918 public void setScrimAlpha(float progress) { 1919 mAmbientState.setScrimAmount(progress); 1920 requestChildrenUpdate(); 1921 } 1922 1923 /** 1924 * See {@link AmbientState#setDark}. 1925 */ 1926 public void setDark(boolean dark, boolean animate) { 1927 mAmbientState.setDark(dark); 1928 if (animate && mAnimationsEnabled) { 1929 mDarkNeedsAnimation = true; 1930 mNeedsAnimation = true; 1931 } 1932 requestChildrenUpdate(); 1933 } 1934 1935 /** 1936 * A listener that is notified when some child locations might have changed. 1937 */ 1938 public interface OnChildLocationsChangedListener { 1939 public void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout); 1940 } 1941 1942 /** 1943 * A listener that gets notified when the overscroll at the top has changed. 1944 */ 1945 public interface OnOverscrollTopChangedListener { 1946 1947 /** 1948 * Notifies a listener that the overscroll has changed. 1949 * 1950 * @param amount the amount of overscroll, in pixels 1951 * @param isRubberbanded if true, this is a rubberbanded overscroll; if false, this is an 1952 * unrubberbanded motion to directly expand overscroll view (e.g expand 1953 * QS) 1954 */ 1955 public void onOverscrollTopChanged(float amount, boolean isRubberbanded); 1956 1957 /** 1958 * Notify a listener that the scroller wants to escape from the scrolling motion and 1959 * start a fling animation to the expanded or collapsed overscroll view (e.g expand the QS) 1960 * 1961 * @param velocity The velocity that the Scroller had when over flinging 1962 * @param open Should the fling open or close the overscroll view. 1963 */ 1964 public void flingTopOverscroll(float velocity, boolean open); 1965 } 1966 1967 static class AnimationEvent { 1968 1969 static AnimationFilter[] FILTERS = new AnimationFilter[] { 1970 1971 // ANIMATION_TYPE_ADD 1972 new AnimationFilter() 1973 .animateAlpha() 1974 .animateHeight() 1975 .animateTopInset() 1976 .animateY() 1977 .animateZ() 1978 .hasDelays(), 1979 1980 // ANIMATION_TYPE_REMOVE 1981 new AnimationFilter() 1982 .animateAlpha() 1983 .animateHeight() 1984 .animateTopInset() 1985 .animateY() 1986 .animateZ() 1987 .hasDelays(), 1988 1989 // ANIMATION_TYPE_REMOVE_SWIPED_OUT 1990 new AnimationFilter() 1991 .animateAlpha() 1992 .animateHeight() 1993 .animateTopInset() 1994 .animateY() 1995 .animateZ() 1996 .hasDelays(), 1997 1998 // ANIMATION_TYPE_TOP_PADDING_CHANGED 1999 new AnimationFilter() 2000 .animateAlpha() 2001 .animateHeight() 2002 .animateTopInset() 2003 .animateY() 2004 .animateDimmed() 2005 .animateScale() 2006 .animateZ(), 2007 2008 // ANIMATION_TYPE_START_DRAG 2009 new AnimationFilter() 2010 .animateAlpha(), 2011 2012 // ANIMATION_TYPE_SNAP_BACK 2013 new AnimationFilter() 2014 .animateAlpha(), 2015 2016 // ANIMATION_TYPE_ACTIVATED_CHILD 2017 new AnimationFilter() 2018 .animateScale() 2019 .animateAlpha(), 2020 2021 // ANIMATION_TYPE_DIMMED 2022 new AnimationFilter() 2023 .animateY() 2024 .animateScale() 2025 .animateDimmed(), 2026 2027 // ANIMATION_TYPE_CHANGE_POSITION 2028 new AnimationFilter() 2029 .animateAlpha() 2030 .animateHeight() 2031 .animateTopInset() 2032 .animateY() 2033 .animateZ(), 2034 2035 // ANIMATION_TYPE_DARK 2036 new AnimationFilter() 2037 .animateDark(), 2038 }; 2039 2040 static int[] LENGTHS = new int[] { 2041 2042 // ANIMATION_TYPE_ADD 2043 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR, 2044 2045 // ANIMATION_TYPE_REMOVE 2046 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR, 2047 2048 // ANIMATION_TYPE_REMOVE_SWIPED_OUT 2049 StackStateAnimator.ANIMATION_DURATION_STANDARD, 2050 2051 // ANIMATION_TYPE_TOP_PADDING_CHANGED 2052 StackStateAnimator.ANIMATION_DURATION_STANDARD, 2053 2054 // ANIMATION_TYPE_START_DRAG 2055 StackStateAnimator.ANIMATION_DURATION_STANDARD, 2056 2057 // ANIMATION_TYPE_SNAP_BACK 2058 StackStateAnimator.ANIMATION_DURATION_STANDARD, 2059 2060 // ANIMATION_TYPE_ACTIVATED_CHILD 2061 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED, 2062 2063 // ANIMATION_TYPE_DIMMED 2064 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED, 2065 2066 // ANIMATION_TYPE_CHANGE_POSITION 2067 StackStateAnimator.ANIMATION_DURATION_STANDARD, 2068 2069 // ANIMATION_TYPE_DARK 2070 StackStateAnimator.ANIMATION_DURATION_STANDARD, 2071 }; 2072 2073 static final int ANIMATION_TYPE_ADD = 0; 2074 static final int ANIMATION_TYPE_REMOVE = 1; 2075 static final int ANIMATION_TYPE_REMOVE_SWIPED_OUT = 2; 2076 static final int ANIMATION_TYPE_TOP_PADDING_CHANGED = 3; 2077 static final int ANIMATION_TYPE_START_DRAG = 4; 2078 static final int ANIMATION_TYPE_SNAP_BACK = 5; 2079 static final int ANIMATION_TYPE_ACTIVATED_CHILD = 6; 2080 static final int ANIMATION_TYPE_DIMMED = 7; 2081 static final int ANIMATION_TYPE_CHANGE_POSITION = 8; 2082 static final int ANIMATION_TYPE_DARK = 9; 2083 2084 final long eventStartTime; 2085 final View changingView; 2086 final int animationType; 2087 final AnimationFilter filter; 2088 final long length; 2089 View viewAfterChangingView; 2090 2091 AnimationEvent(View view, int type) { 2092 eventStartTime = AnimationUtils.currentAnimationTimeMillis(); 2093 changingView = view; 2094 animationType = type; 2095 filter = FILTERS[type]; 2096 length = LENGTHS[type]; 2097 } 2098 2099 /** 2100 * Combines the length of several animation events into a single value. 2101 * 2102 * @param events The events of the lengths to combine. 2103 * @return The combined length. This is just the maximum of all length. 2104 */ 2105 static long combineLength(ArrayList<AnimationEvent> events) { 2106 long length = 0; 2107 int size = events.size(); 2108 for (int i = 0; i < size; i++) { 2109 length = Math.max(length, events.get(i).length); 2110 } 2111 return length; 2112 } 2113 } 2114 2115} 2116