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