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