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