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