NotificationStackScrollLayout.java revision a5eaa6034dd48fab0f5a232c09ebed35f359963e
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; 21 22import android.graphics.Canvas; 23import android.graphics.Paint; 24 25import android.util.AttributeSet; 26import android.util.Log; 27 28import android.view.MotionEvent; 29import android.view.VelocityTracker; 30import android.view.View; 31import android.view.ViewConfiguration; 32import android.view.ViewGroup; 33import android.view.ViewTreeObserver; 34import android.view.animation.AnimationUtils; 35import android.widget.OverScroller; 36 37import com.android.systemui.ExpandHelper; 38import com.android.systemui.R; 39import com.android.systemui.SwipeHelper; 40import com.android.systemui.statusbar.ExpandableNotificationRow; 41import com.android.systemui.statusbar.ExpandableView; 42import com.android.systemui.statusbar.stack.StackScrollState.ViewState; 43import com.android.systemui.statusbar.policy.ScrollAdapter; 44 45import java.util.ArrayList; 46 47/** 48 * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack. 49 */ 50public class NotificationStackScrollLayout extends ViewGroup 51 implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter, 52 ExpandableView.OnHeightChangedListener { 53 54 private static final String TAG = "NotificationStackScrollLayout"; 55 private static final boolean DEBUG = false; 56 57 /** 58 * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}. 59 */ 60 private static final int INVALID_POINTER = -1; 61 62 private SwipeHelper mSwipeHelper; 63 private boolean mSwipingInProgress; 64 private int mCurrentStackHeight = Integer.MAX_VALUE; 65 private int mOwnScrollY; 66 private int mMaxLayoutHeight; 67 68 private VelocityTracker mVelocityTracker; 69 private OverScroller mScroller; 70 private int mTouchSlop; 71 private int mMinimumVelocity; 72 private int mMaximumVelocity; 73 private int mOverscrollDistance; 74 private int mOverflingDistance; 75 private boolean mIsBeingDragged; 76 private int mLastMotionY; 77 private int mActivePointerId; 78 79 private int mSidePaddings; 80 private Paint mDebugPaint; 81 private int mContentHeight; 82 private int mCollapsedSize; 83 private int mBottomStackSlowDownHeight; 84 private int mBottomStackPeekSize; 85 private int mEmptyMarginBottom; 86 private int mPaddingBetweenElements; 87 private int mPaddingBetweenElementsDimmed; 88 private int mPaddingBetweenElementsNormal; 89 private int mTopPadding; 90 91 /** 92 * The algorithm which calculates the properties for our children 93 */ 94 private StackScrollAlgorithm mStackScrollAlgorithm; 95 96 /** 97 * The current State this Layout is in 98 */ 99 private StackScrollState mCurrentStackScrollState = new StackScrollState(this); 100 private AmbientState mAmbientState = new AmbientState(); 101 private ArrayList<View> mChildrenToAddAnimated = new ArrayList<View>(); 102 private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<View>(); 103 private ArrayList<View> mSnappedBackChildren = new ArrayList<View>(); 104 private ArrayList<View> mDragAnimPendingChildren = new ArrayList<View>(); 105 private ArrayList<AnimationEvent> mAnimationEvents 106 = new ArrayList<AnimationEvent>(); 107 private ArrayList<View> mSwipedOutViews = new ArrayList<View>(); 108 private final StackStateAnimator mStateAnimator = new StackStateAnimator(this); 109 110 private OnChildLocationsChangedListener mListener; 111 private ExpandableView.OnHeightChangedListener mOnHeightChangedListener; 112 private boolean mNeedsAnimation; 113 private boolean mTopPaddingNeedsAnimation; 114 private boolean mDimmedNeedsAnimation; 115 private boolean mActivateNeedsAnimation; 116 private boolean mIsExpanded = true; 117 private boolean mChildrenUpdateRequested; 118 private ViewTreeObserver.OnPreDrawListener mChildrenUpdater 119 = new ViewTreeObserver.OnPreDrawListener() { 120 @Override 121 public boolean onPreDraw() { 122 updateChildren(); 123 mChildrenUpdateRequested = false; 124 getViewTreeObserver().removeOnPreDrawListener(this); 125 return true; 126 } 127 }; 128 129 public NotificationStackScrollLayout(Context context) { 130 this(context, null); 131 } 132 133 public NotificationStackScrollLayout(Context context, AttributeSet attrs) { 134 this(context, attrs, 0); 135 } 136 137 public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr) { 138 this(context, attrs, defStyleAttr, 0); 139 } 140 141 public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr, 142 int defStyleRes) { 143 super(context, attrs, defStyleAttr, defStyleRes); 144 initView(context); 145 if (DEBUG) { 146 setWillNotDraw(false); 147 mDebugPaint = new Paint(); 148 mDebugPaint.setColor(0xffff0000); 149 mDebugPaint.setStrokeWidth(2); 150 mDebugPaint.setStyle(Paint.Style.STROKE); 151 } 152 } 153 154 @Override 155 protected void onDraw(Canvas canvas) { 156 if (DEBUG) { 157 int y = mCollapsedSize; 158 canvas.drawLine(0, y, getWidth(), y, mDebugPaint); 159 y = (int) (getLayoutHeight() - mBottomStackPeekSize 160 - mBottomStackSlowDownHeight); 161 canvas.drawLine(0, y, getWidth(), y, mDebugPaint); 162 y = (int) (getLayoutHeight() - mBottomStackPeekSize); 163 canvas.drawLine(0, y, getWidth(), y, mDebugPaint); 164 y = (int) getLayoutHeight(); 165 canvas.drawLine(0, y, getWidth(), y, mDebugPaint); 166 } 167 } 168 169 private void initView(Context context) { 170 mScroller = new OverScroller(getContext()); 171 setFocusable(true); 172 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); 173 setClipChildren(false); 174 final ViewConfiguration configuration = ViewConfiguration.get(context); 175 mTouchSlop = configuration.getScaledTouchSlop(); 176 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); 177 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); 178 mOverscrollDistance = configuration.getScaledOverscrollDistance(); 179 mOverflingDistance = configuration.getScaledOverflingDistance(); 180 float densityScale = getResources().getDisplayMetrics().density; 181 float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop(); 182 mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, pagingTouchSlop); 183 184 mSidePaddings = context.getResources() 185 .getDimensionPixelSize(R.dimen.notification_side_padding); 186 mCollapsedSize = context.getResources() 187 .getDimensionPixelSize(R.dimen.notification_min_height); 188 mBottomStackPeekSize = context.getResources() 189 .getDimensionPixelSize(R.dimen.bottom_stack_peek_amount); 190 mEmptyMarginBottom = context.getResources().getDimensionPixelSize( 191 R.dimen.notification_stack_margin_bottom); 192 mStackScrollAlgorithm = new StackScrollAlgorithm(context); 193 mPaddingBetweenElementsDimmed = context.getResources() 194 .getDimensionPixelSize(R.dimen.notification_padding_dimmed); 195 mPaddingBetweenElementsNormal = context.getResources() 196 .getDimensionPixelSize(R.dimen.notification_padding); 197 updatePadding(false); 198 } 199 200 private void updatePadding(boolean dimmed) { 201 mPaddingBetweenElements = dimmed 202 ? mPaddingBetweenElementsDimmed 203 : mPaddingBetweenElementsNormal; 204 mBottomStackSlowDownHeight = mStackScrollAlgorithm.getBottomStackSlowDownLength(); 205 updateContentHeight(); 206 } 207 208 @Override 209 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 210 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 211 int mode = MeasureSpec.getMode(widthMeasureSpec); 212 int size = MeasureSpec.getSize(widthMeasureSpec); 213 int childMeasureSpec = MeasureSpec.makeMeasureSpec(size - 2 * mSidePaddings, mode); 214 measureChildren(childMeasureSpec, heightMeasureSpec); 215 } 216 217 @Override 218 protected void onLayout(boolean changed, int l, int t, int r, int b) { 219 220 // we layout all our children centered on the top 221 float centerX = getWidth() / 2.0f; 222 for (int i = 0; i < getChildCount(); i++) { 223 View child = getChildAt(i); 224 float width = child.getMeasuredWidth(); 225 float height = child.getMeasuredHeight(); 226 child.layout((int) (centerX - width / 2.0f), 227 0, 228 (int) (centerX + width / 2.0f), 229 (int) height); 230 } 231 setMaxLayoutHeight(getHeight() - mEmptyMarginBottom); 232 updateContentHeight(); 233 updateScrollPositionIfNecessary(); 234 requestChildrenUpdate(); 235 } 236 237 public void setChildLocationsChangedListener(OnChildLocationsChangedListener listener) { 238 mListener = listener; 239 } 240 241 /** 242 * Returns the location the given child is currently rendered at. 243 * 244 * @param child the child to get the location for 245 * @return one of {@link ViewState}'s <code>LOCATION_*</code> constants 246 */ 247 public int getChildLocation(View child) { 248 ViewState childViewState = mCurrentStackScrollState.getViewStateForView(child); 249 if (childViewState == null) { 250 return ViewState.LOCATION_UNKNOWN; 251 } 252 return childViewState.location; 253 } 254 255 private void setMaxLayoutHeight(int maxLayoutHeight) { 256 mMaxLayoutHeight = maxLayoutHeight; 257 updateAlgorithmHeightAndPadding(); 258 } 259 260 private void updateAlgorithmHeightAndPadding() { 261 mStackScrollAlgorithm.setLayoutHeight(getLayoutHeight()); 262 mStackScrollAlgorithm.setTopPadding(mTopPadding); 263 } 264 265 /** 266 * @return whether the height of the layout needs to be adapted, in order to ensure that the 267 * last child is not in the bottom stack. 268 */ 269 private boolean needsHeightAdaption() { 270 View lastChild = getLastChildNotGone(); 271 View firstChild = getFirstChildNotGone(); 272 boolean isLastChildExpanded = isViewExpanded(lastChild); 273 return isLastChildExpanded && lastChild != firstChild; 274 } 275 276 private boolean isViewExpanded(View view) { 277 if (view != null) { 278 ExpandableView expandView = (ExpandableView) view; 279 return expandView.getActualHeight() > mCollapsedSize; 280 } 281 return false; 282 } 283 284 /** 285 * Updates the children views according to the stack scroll algorithm. Call this whenever 286 * modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout. 287 */ 288 private void updateChildren() { 289 mAmbientState.setScrollY(mOwnScrollY); 290 mStackScrollAlgorithm.getStackScrollState(mAmbientState, mCurrentStackScrollState); 291 if (!isCurrentlyAnimating() && !mNeedsAnimation) { 292 applyCurrentState(); 293 } else { 294 startAnimationToState(); 295 } 296 } 297 298 private void requestChildrenUpdate() { 299 if (!mChildrenUpdateRequested) { 300 getViewTreeObserver().addOnPreDrawListener(mChildrenUpdater); 301 mChildrenUpdateRequested = true; 302 invalidate(); 303 } 304 } 305 306 private boolean isCurrentlyAnimating() { 307 return mStateAnimator.isRunning(); 308 } 309 310 private void updateScrollPositionIfNecessary() { 311 int scrollRange = getScrollRange(); 312 if (scrollRange < mOwnScrollY) { 313 mOwnScrollY = scrollRange; 314 } 315 } 316 317 public int getTopPadding() { 318 return mTopPadding; 319 } 320 321 public void setTopPadding(int topPadding, boolean animate) { 322 if (mTopPadding != topPadding) { 323 mTopPadding = topPadding; 324 updateAlgorithmHeightAndPadding(); 325 updateContentHeight(); 326 if (animate) { 327 mTopPaddingNeedsAnimation = true; 328 mNeedsAnimation = true; 329 } 330 requestChildrenUpdate(); 331 if (mOnHeightChangedListener != null) { 332 mOnHeightChangedListener.onHeightChanged(null); 333 } 334 } 335 } 336 337 /** 338 * Update the height of the stack to a new height. 339 * 340 * @param height the new height of the stack 341 */ 342 public void setStackHeight(float height) { 343 setIsExpanded(height > 0.0f); 344 int newStackHeight = (int) height; 345 int itemHeight = getItemHeight(); 346 int bottomStackPeekSize = mBottomStackPeekSize; 347 int minStackHeight = itemHeight + bottomStackPeekSize; 348 int stackHeight; 349 if (newStackHeight - mTopPadding >= minStackHeight) { 350 setTranslationY(0); 351 stackHeight = newStackHeight; 352 } else { 353 354 // We did not reach the position yet where we actually start growing, 355 // so we translate the stack upwards. 356 int translationY = (newStackHeight - minStackHeight); 357 // A slight parallax effect is introduced in order for the stack to catch up with 358 // the top card. 359 float partiallyThere = (float) (newStackHeight - mTopPadding) / minStackHeight; 360 partiallyThere = Math.max(0, partiallyThere); 361 translationY += (1 - partiallyThere) * bottomStackPeekSize; 362 setTranslationY(translationY - mTopPadding); 363 stackHeight = (int) (height - (translationY - mTopPadding)); 364 } 365 if (stackHeight != mCurrentStackHeight) { 366 mCurrentStackHeight = stackHeight; 367 updateAlgorithmHeightAndPadding(); 368 requestChildrenUpdate(); 369 } 370 } 371 372 /** 373 * Get the current height of the view. This is at most the msize of the view given by a the 374 * layout but it can also be made smaller by setting {@link #mCurrentStackHeight} 375 * 376 * @return either the layout height or the externally defined height, whichever is smaller 377 */ 378 private int getLayoutHeight() { 379 return Math.min(mMaxLayoutHeight, mCurrentStackHeight); 380 } 381 382 public int getItemHeight() { 383 return mCollapsedSize; 384 } 385 386 public int getBottomStackPeekSize() { 387 return mBottomStackPeekSize; 388 } 389 390 public void setLongPressListener(View.OnLongClickListener listener) { 391 mSwipeHelper.setLongPressListener(listener); 392 } 393 394 public void onChildDismissed(View v) { 395 if (DEBUG) Log.v(TAG, "onChildDismissed: " + v); 396 final View veto = v.findViewById(R.id.veto); 397 if (veto != null && veto.getVisibility() != View.GONE) { 398 veto.performClick(); 399 } 400 setSwipingInProgress(false); 401 if (mDragAnimPendingChildren.contains(v)) { 402 // We start the swipe and finish it in the same frame, we don't want any animation 403 // for the drag 404 mDragAnimPendingChildren.remove(v); 405 } 406 mSwipedOutViews.add(v); 407 mAmbientState.onDragFinished(v); 408 } 409 410 @Override 411 public void onChildSnappedBack(View animView) { 412 mAmbientState.onDragFinished(animView); 413 if (!mDragAnimPendingChildren.contains(animView)) { 414 mSnappedBackChildren.add(animView); 415 requestChildrenUpdate(); 416 mNeedsAnimation = true; 417 } else { 418 // We start the swipe and snap back in the same frame, we don't want any animation 419 mDragAnimPendingChildren.remove(animView); 420 } 421 } 422 423 public void onBeginDrag(View v) { 424 setSwipingInProgress(true); 425 mDragAnimPendingChildren.add(v); 426 mAmbientState.onBeginDrag(v); 427 requestChildrenUpdate(); 428 mNeedsAnimation = true; 429 } 430 431 public void onDragCancelled(View v) { 432 setSwipingInProgress(false); 433 } 434 435 public View getChildAtPosition(MotionEvent ev) { 436 return getChildAtPosition(ev.getX(), ev.getY()); 437 } 438 439 public ExpandableView getChildAtRawPosition(float touchX, float touchY) { 440 int[] location = new int[2]; 441 getLocationOnScreen(location); 442 return getChildAtPosition(touchX - location[0], touchY - location[1]); 443 } 444 445 public ExpandableView getChildAtPosition(float touchX, float touchY) { 446 // find the view under the pointer, accounting for GONE views 447 final int count = getChildCount(); 448 for (int childIdx = 0; childIdx < count; childIdx++) { 449 ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx); 450 if (slidingChild.getVisibility() == GONE) { 451 continue; 452 } 453 float top = slidingChild.getTranslationY(); 454 float bottom = top + slidingChild.getActualHeight(); 455 int left = slidingChild.getLeft(); 456 int right = slidingChild.getRight(); 457 458 if (touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) { 459 return slidingChild; 460 } 461 } 462 return null; 463 } 464 465 public boolean canChildBeExpanded(View v) { 466 return v instanceof ExpandableNotificationRow 467 && ((ExpandableNotificationRow) v).isExpandable(); 468 } 469 470 public void setUserExpandedChild(View v, boolean userExpanded) { 471 if (v instanceof ExpandableNotificationRow) { 472 ((ExpandableNotificationRow) v).setUserExpanded(userExpanded); 473 } 474 } 475 476 public void setUserLockedChild(View v, boolean userLocked) { 477 if (v instanceof ExpandableNotificationRow) { 478 ((ExpandableNotificationRow) v).setUserLocked(userLocked); 479 } 480 } 481 482 public View getChildContentView(View v) { 483 return v; 484 } 485 486 public boolean canChildBeDismissed(View v) { 487 final View veto = v.findViewById(R.id.veto); 488 return (veto != null && veto.getVisibility() != View.GONE); 489 } 490 491 private void setSwipingInProgress(boolean isSwiped) { 492 mSwipingInProgress = isSwiped; 493 if(isSwiped) { 494 requestDisallowInterceptTouchEvent(true); 495 } 496 } 497 498 @Override 499 protected void onConfigurationChanged(Configuration newConfig) { 500 super.onConfigurationChanged(newConfig); 501 float densityScale = getResources().getDisplayMetrics().density; 502 mSwipeHelper.setDensityScale(densityScale); 503 float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop(); 504 mSwipeHelper.setPagingTouchSlop(pagingTouchSlop); 505 initView(getContext()); 506 } 507 508 public void dismissRowAnimated(View child, int vel) { 509 mSwipeHelper.dismissChild(child, vel); 510 } 511 512 @Override 513 public boolean onTouchEvent(MotionEvent ev) { 514 if (!isEnabled()) { 515 return false; 516 } 517 boolean scrollerWantsIt = false; 518 if (!mSwipingInProgress) { 519 scrollerWantsIt = onScrollTouch(ev); 520 } 521 boolean horizontalSwipeWantsIt = false; 522 if (!mIsBeingDragged) { 523 horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev); 524 } 525 return horizontalSwipeWantsIt || scrollerWantsIt || super.onTouchEvent(ev); 526 } 527 528 private boolean onScrollTouch(MotionEvent ev) { 529 initVelocityTrackerIfNotExists(); 530 mVelocityTracker.addMovement(ev); 531 532 final int action = ev.getAction(); 533 534 switch (action & MotionEvent.ACTION_MASK) { 535 case MotionEvent.ACTION_DOWN: { 536 if (getChildCount() == 0 || !isInContentBounds(ev)) { 537 return false; 538 } 539 boolean isBeingDragged = !mScroller.isFinished(); 540 setIsBeingDragged(isBeingDragged); 541 542 /* 543 * If being flinged and user touches, stop the fling. isFinished 544 * will be false if being flinged. 545 */ 546 if (!mScroller.isFinished()) { 547 mScroller.abortAnimation(); 548 } 549 550 // Remember where the motion event started 551 mLastMotionY = (int) ev.getY(); 552 mActivePointerId = ev.getPointerId(0); 553 break; 554 } 555 case MotionEvent.ACTION_MOVE: 556 final int activePointerIndex = ev.findPointerIndex(mActivePointerId); 557 if (activePointerIndex == -1) { 558 Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent"); 559 break; 560 } 561 562 final int y = (int) ev.getY(activePointerIndex); 563 int deltaY = mLastMotionY - y; 564 if (!mIsBeingDragged && Math.abs(deltaY) > mTouchSlop) { 565 setIsBeingDragged(true); 566 if (deltaY > 0) { 567 deltaY -= mTouchSlop; 568 } else { 569 deltaY += mTouchSlop; 570 } 571 } 572 if (mIsBeingDragged) { 573 // Scroll to follow the motion event 574 mLastMotionY = y; 575 576 final int oldX = mScrollX; 577 final int oldY = mOwnScrollY; 578 final int range = getScrollRange(); 579 final int overscrollMode = getOverScrollMode(); 580 final boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS || 581 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0); 582 583 // Calling overScrollBy will call onOverScrolled, which 584 // calls onScrollChanged if applicable. 585 if (overScrollBy(0, deltaY, 0, mOwnScrollY, 586 0, range, 0, mOverscrollDistance, true)) { 587 // Break our velocity if we hit a scroll barrier. 588 mVelocityTracker.clear(); 589 } 590 // TODO: Overscroll 591// if (canOverscroll) { 592// final int pulledToY = oldY + deltaY; 593// if (pulledToY < 0) { 594// mEdgeGlowTop.onPull((float) deltaY / getHeight()); 595// if (!mEdgeGlowBottom.isFinished()) { 596// mEdgeGlowBottom.onRelease(); 597// } 598// } else if (pulledToY > range) { 599// mEdgeGlowBottom.onPull((float) deltaY / getHeight()); 600// if (!mEdgeGlowTop.isFinished()) { 601// mEdgeGlowTop.onRelease(); 602// } 603// } 604// if (mEdgeGlowTop != null 605// && (!mEdgeGlowTop.isFinished() || !mEdgeGlowBottom.isFinished())){ 606// postInvalidateOnAnimation(); 607// } 608// } 609 } 610 break; 611 case MotionEvent.ACTION_UP: 612 if (mIsBeingDragged) { 613 final VelocityTracker velocityTracker = mVelocityTracker; 614 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 615 int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId); 616 617 if (getChildCount() > 0) { 618 if ((Math.abs(initialVelocity) > mMinimumVelocity)) { 619 fling(-initialVelocity); 620 } else { 621 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, 622 getScrollRange())) { 623 postInvalidateOnAnimation(); 624 } 625 } 626 } 627 628 mActivePointerId = INVALID_POINTER; 629 endDrag(); 630 } 631 break; 632 case MotionEvent.ACTION_CANCEL: 633 if (mIsBeingDragged && getChildCount() > 0) { 634 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) { 635 postInvalidateOnAnimation(); 636 } 637 mActivePointerId = INVALID_POINTER; 638 endDrag(); 639 } 640 break; 641 case MotionEvent.ACTION_POINTER_DOWN: { 642 final int index = ev.getActionIndex(); 643 mLastMotionY = (int) ev.getY(index); 644 mActivePointerId = ev.getPointerId(index); 645 break; 646 } 647 case MotionEvent.ACTION_POINTER_UP: 648 onSecondaryPointerUp(ev); 649 mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId)); 650 break; 651 } 652 return true; 653 } 654 655 private void onSecondaryPointerUp(MotionEvent ev) { 656 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> 657 MotionEvent.ACTION_POINTER_INDEX_SHIFT; 658 final int pointerId = ev.getPointerId(pointerIndex); 659 if (pointerId == mActivePointerId) { 660 // This was our active pointer going up. Choose a new 661 // active pointer and adjust accordingly. 662 // TODO: Make this decision more intelligent. 663 final int newPointerIndex = pointerIndex == 0 ? 1 : 0; 664 mLastMotionY = (int) ev.getY(newPointerIndex); 665 mActivePointerId = ev.getPointerId(newPointerIndex); 666 if (mVelocityTracker != null) { 667 mVelocityTracker.clear(); 668 } 669 } 670 } 671 672 private void initVelocityTrackerIfNotExists() { 673 if (mVelocityTracker == null) { 674 mVelocityTracker = VelocityTracker.obtain(); 675 } 676 } 677 678 private void recycleVelocityTracker() { 679 if (mVelocityTracker != null) { 680 mVelocityTracker.recycle(); 681 mVelocityTracker = null; 682 } 683 } 684 685 private void initOrResetVelocityTracker() { 686 if (mVelocityTracker == null) { 687 mVelocityTracker = VelocityTracker.obtain(); 688 } else { 689 mVelocityTracker.clear(); 690 } 691 } 692 693 @Override 694 public void computeScroll() { 695 if (mScroller.computeScrollOffset()) { 696 // This is called at drawing time by ViewGroup. 697 int oldX = mScrollX; 698 int oldY = mOwnScrollY; 699 int x = mScroller.getCurrX(); 700 int y = mScroller.getCurrY(); 701 702 if (oldX != x || oldY != y) { 703 final int range = getScrollRange(); 704 final int overscrollMode = getOverScrollMode(); 705 final boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS || 706 (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0); 707 708 overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, range, 709 0, mOverflingDistance, false); 710 onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY); 711 712 if (canOverscroll) { 713 // TODO: Overscroll 714// if (y < 0 && oldY >= 0) { 715// mEdgeGlowTop.onAbsorb((int) mScroller.getCurrVelocity()); 716// } else if (y > range && oldY <= range) { 717// mEdgeGlowBottom.onAbsorb((int) mScroller.getCurrVelocity()); 718// } 719 } 720 updateChildren(); 721 } 722 723 // Keep on drawing until the animation has finished. 724 postInvalidateOnAnimation(); 725 } 726 } 727 728 private void customScrollTo(int y) { 729 mOwnScrollY = y; 730 updateChildren(); 731 } 732 733 @Override 734 protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) { 735 // Treat animating scrolls differently; see #computeScroll() for why. 736 if (!mScroller.isFinished()) { 737 final int oldX = mScrollX; 738 final int oldY = mOwnScrollY; 739 mScrollX = scrollX; 740 mOwnScrollY = scrollY; 741 invalidateParentIfNeeded(); 742 onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY); 743 if (clampedY) { 744 mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange()); 745 } 746 updateChildren(); 747 } else { 748 customScrollTo(scrollY); 749 scrollTo(scrollX, mScrollY); 750 } 751 } 752 753 private int getScrollRange() { 754 int scrollRange = 0; 755 ExpandableView firstChild = (ExpandableView) getFirstChildNotGone(); 756 if (firstChild != null) { 757 int contentHeight = getContentHeight(); 758 int firstChildMaxExpandHeight = getMaxExpandHeight(firstChild); 759 scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight + mBottomStackPeekSize 760 + mBottomStackSlowDownHeight); 761 if (scrollRange > 0) { 762 View lastChild = getLastChildNotGone(); 763 // We want to at least be able collapse the first item and not ending in a weird 764 // end state. 765 scrollRange = Math.max(scrollRange, firstChildMaxExpandHeight - mCollapsedSize); 766 } 767 } 768 return scrollRange; 769 } 770 771 /** 772 * @return the first child which has visibility unequal to GONE 773 */ 774 private View getFirstChildNotGone() { 775 int childCount = getChildCount(); 776 for (int i = 0; i < childCount; i++) { 777 View child = getChildAt(i); 778 if (child.getVisibility() != View.GONE) { 779 return child; 780 } 781 } 782 return null; 783 } 784 785 /** 786 * @return the last child which has visibility unequal to GONE 787 */ 788 private View getLastChildNotGone() { 789 int childCount = getChildCount(); 790 for (int i = childCount - 1; i >= 0; i--) { 791 View child = getChildAt(i); 792 if (child.getVisibility() != View.GONE) { 793 return child; 794 } 795 } 796 return null; 797 } 798 799 private int getMaxExpandHeight(View view) { 800 if (view instanceof ExpandableNotificationRow) { 801 ExpandableNotificationRow row = (ExpandableNotificationRow) view; 802 return row.getIntrinsicHeight(); 803 } 804 return view.getHeight(); 805 } 806 807 private int getContentHeight() { 808 return mContentHeight; 809 } 810 811 private void updateContentHeight() { 812 int height = 0; 813 for (int i = 0; i < getChildCount(); i++) { 814 View child = getChildAt(i); 815 if (child.getVisibility() != View.GONE) { 816 if (height != 0) { 817 // add the padding before this element 818 height += mPaddingBetweenElements; 819 } 820 if (child instanceof ExpandableNotificationRow) { 821 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 822 height += row.getIntrinsicHeight(); 823 } else if (child instanceof ExpandableView) { 824 ExpandableView expandableView = (ExpandableView) child; 825 height += expandableView.getActualHeight(); 826 } 827 } 828 } 829 mContentHeight = height + mTopPadding; 830 } 831 832 /** 833 * Fling the scroll view 834 * 835 * @param velocityY The initial velocity in the Y direction. Positive 836 * numbers mean that the finger/cursor is moving down the screen, 837 * which means we want to scroll towards the top. 838 */ 839 private void fling(int velocityY) { 840 if (getChildCount() > 0) { 841 int height = (int) getLayoutHeight(); 842 int bottom = getContentHeight(); 843 844 mScroller.fling(mScrollX, mOwnScrollY, 0, velocityY, 0, 0, 0, 845 Math.max(0, bottom - height), 0, height/2); 846 847 postInvalidateOnAnimation(); 848 } 849 } 850 851 private void endDrag() { 852 setIsBeingDragged(false); 853 854 recycleVelocityTracker(); 855 856 // TODO: Overscroll 857// if (mEdgeGlowTop != null) { 858// mEdgeGlowTop.onRelease(); 859// mEdgeGlowBottom.onRelease(); 860// } 861 } 862 863 @Override 864 public boolean onInterceptTouchEvent(MotionEvent ev) { 865 boolean scrollWantsIt = false; 866 if (!mSwipingInProgress) { 867 scrollWantsIt = onInterceptTouchEventScroll(ev); 868 } 869 boolean swipeWantsIt = false; 870 if (!mIsBeingDragged) { 871 swipeWantsIt = mSwipeHelper.onInterceptTouchEvent(ev); 872 } 873 return swipeWantsIt || scrollWantsIt || 874 super.onInterceptTouchEvent(ev); 875 } 876 877 @Override 878 protected void onViewRemoved(View child) { 879 super.onViewRemoved(child); 880 ((ExpandableView) child).setOnHeightChangedListener(null); 881 mCurrentStackScrollState.removeViewStateForView(child); 882 mStackScrollAlgorithm.notifyChildrenChanged(this); 883 updateScrollStateForRemovedChild(child); 884 if (mIsExpanded) { 885 886 if (!mChildrenToAddAnimated.contains(child)) { 887 // Generate Animations 888 mChildrenToRemoveAnimated.add(child); 889 mNeedsAnimation = true; 890 } else { 891 mChildrenToAddAnimated.remove(child); 892 } 893 } 894 } 895 896 /** 897 * Updates the scroll position when a child was removed 898 * 899 * @param removedChild the removed child 900 */ 901 private void updateScrollStateForRemovedChild(View removedChild) { 902 int startingPosition = getPositionInLinearLayout(removedChild); 903 int childHeight = removedChild.getHeight() + mPaddingBetweenElements; 904 int endPosition = startingPosition + childHeight; 905 if (endPosition <= mOwnScrollY) { 906 // This child is fully scrolled of the top, so we have to deduct its height from the 907 // scrollPosition 908 mOwnScrollY -= childHeight; 909 } else if (startingPosition < mOwnScrollY) { 910 // This child is currently being scrolled into, set the scroll position to the start of 911 // this child 912 mOwnScrollY = startingPosition; 913 } 914 } 915 916 private int getPositionInLinearLayout(View requestedChild) { 917 int position = 0; 918 for (int i = 0; i < getChildCount(); i++) { 919 View child = getChildAt(i); 920 if (child == requestedChild) { 921 return position; 922 } 923 if (child.getVisibility() != View.GONE) { 924 position += child.getHeight(); 925 if (i < getChildCount()-1) { 926 position += mPaddingBetweenElements; 927 } 928 } 929 } 930 return 0; 931 } 932 933 @Override 934 protected void onViewAdded(View child) { 935 super.onViewAdded(child); 936 mStackScrollAlgorithm.notifyChildrenChanged(this); 937 ((ExpandableView) child).setOnHeightChangedListener(this); 938 if (child.getVisibility() != View.GONE) { 939 generateAddAnimation(child); 940 } 941 } 942 943 public void generateAddAnimation(View child) { 944 if (mIsExpanded) { 945 946 // Generate Animations 947 mChildrenToAddAnimated.add(child); 948 mNeedsAnimation = true; 949 } 950 } 951 952 /** 953 * Change the position of child to a new location 954 * 955 * @param child the view to change the position for 956 * @param newIndex the new index 957 */ 958 public void changeViewPosition(View child, int newIndex) { 959 if (child != null && child.getParent() == this) { 960 // TODO: handle this 961 } 962 } 963 964 private void startAnimationToState() { 965 if (mNeedsAnimation) { 966 generateChildHierarchyEvents(); 967 mNeedsAnimation = false; 968 } 969 if (!mAnimationEvents.isEmpty()) { 970 mStateAnimator.startAnimationForEvents(mAnimationEvents, mCurrentStackScrollState); 971 } else { 972 applyCurrentState(); 973 } 974 } 975 976 private void generateChildHierarchyEvents() { 977 generateChildAdditionEvents(); 978 generateChildRemovalEvents(); 979 generateSnapBackEvents(); 980 generateDragEvents(); 981 generateTopPaddingEvent(); 982 generateActivateEvent(); 983 generateDimmedEvent(); 984 mNeedsAnimation = false; 985 } 986 987 private void generateSnapBackEvents() { 988 for (View child : mSnappedBackChildren) { 989 mAnimationEvents.add(new AnimationEvent(child, 990 AnimationEvent.ANIMATION_TYPE_SNAP_BACK)); 991 } 992 mSnappedBackChildren.clear(); 993 } 994 995 private void generateDragEvents() { 996 for (View child : mDragAnimPendingChildren) { 997 mAnimationEvents.add(new AnimationEvent(child, 998 AnimationEvent.ANIMATION_TYPE_START_DRAG)); 999 } 1000 mDragAnimPendingChildren.clear(); 1001 } 1002 1003 private void generateChildRemovalEvents() { 1004 for (View child : mChildrenToRemoveAnimated) { 1005 boolean childWasSwipedOut = mSwipedOutViews.contains(child); 1006 int animationType = childWasSwipedOut 1007 ? AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT 1008 : AnimationEvent.ANIMATION_TYPE_REMOVE; 1009 mAnimationEvents.add(new AnimationEvent(child, animationType)); 1010 } 1011 mSwipedOutViews.clear(); 1012 mChildrenToRemoveAnimated.clear(); 1013 } 1014 1015 private void generateChildAdditionEvents() { 1016 for (View child : mChildrenToAddAnimated) { 1017 mAnimationEvents.add(new AnimationEvent(child, 1018 AnimationEvent.ANIMATION_TYPE_ADD)); 1019 } 1020 mChildrenToAddAnimated.clear(); 1021 } 1022 1023 private void generateTopPaddingEvent() { 1024 if (mTopPaddingNeedsAnimation) { 1025 mAnimationEvents.add( 1026 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_TOP_PADDING_CHANGED)); 1027 } 1028 mTopPaddingNeedsAnimation = false; 1029 } 1030 1031 private void generateActivateEvent() { 1032 if (mActivateNeedsAnimation) { 1033 mAnimationEvents.add( 1034 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_ACTIVATED_CHILD)); 1035 } 1036 mActivateNeedsAnimation = false; 1037 } 1038 1039 private void generateDimmedEvent() { 1040 if (mDimmedNeedsAnimation) { 1041 mAnimationEvents.add( 1042 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DIMMED)); 1043 } 1044 mDimmedNeedsAnimation = false; 1045 } 1046 1047 private boolean onInterceptTouchEventScroll(MotionEvent ev) { 1048 /* 1049 * This method JUST determines whether we want to intercept the motion. 1050 * If we return true, onMotionEvent will be called and we do the actual 1051 * scrolling there. 1052 */ 1053 1054 /* 1055 * Shortcut the most recurring case: the user is in the dragging 1056 * state and he is moving his finger. We want to intercept this 1057 * motion. 1058 */ 1059 final int action = ev.getAction(); 1060 if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) { 1061 return true; 1062 } 1063 1064 /* 1065 * Don't try to intercept touch if we can't scroll anyway. 1066 */ 1067 if (mOwnScrollY == 0 && getScrollRange() == 0) { 1068 return false; 1069 } 1070 1071 switch (action & MotionEvent.ACTION_MASK) { 1072 case MotionEvent.ACTION_MOVE: { 1073 /* 1074 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check 1075 * whether the user has moved far enough from his original down touch. 1076 */ 1077 1078 /* 1079 * Locally do absolute value. mLastMotionY is set to the y value 1080 * of the down event. 1081 */ 1082 final int activePointerId = mActivePointerId; 1083 if (activePointerId == INVALID_POINTER) { 1084 // If we don't have a valid id, the touch down wasn't on content. 1085 break; 1086 } 1087 1088 final int pointerIndex = ev.findPointerIndex(activePointerId); 1089 if (pointerIndex == -1) { 1090 Log.e(TAG, "Invalid pointerId=" + activePointerId 1091 + " in onInterceptTouchEvent"); 1092 break; 1093 } 1094 1095 final int y = (int) ev.getY(pointerIndex); 1096 final int yDiff = Math.abs(y - mLastMotionY); 1097 if (yDiff > mTouchSlop) { 1098 setIsBeingDragged(true); 1099 mLastMotionY = y; 1100 initVelocityTrackerIfNotExists(); 1101 mVelocityTracker.addMovement(ev); 1102 } 1103 break; 1104 } 1105 1106 case MotionEvent.ACTION_DOWN: { 1107 final int y = (int) ev.getY(); 1108 if (getChildAtPosition(ev.getX(), y) == null) { 1109 setIsBeingDragged(false); 1110 recycleVelocityTracker(); 1111 break; 1112 } 1113 1114 /* 1115 * Remember location of down touch. 1116 * ACTION_DOWN always refers to pointer index 0. 1117 */ 1118 mLastMotionY = y; 1119 mActivePointerId = ev.getPointerId(0); 1120 1121 initOrResetVelocityTracker(); 1122 mVelocityTracker.addMovement(ev); 1123 /* 1124 * If being flinged and user touches the screen, initiate drag; 1125 * otherwise don't. mScroller.isFinished should be false when 1126 * being flinged. 1127 */ 1128 boolean isBeingDragged = !mScroller.isFinished(); 1129 setIsBeingDragged(isBeingDragged); 1130 break; 1131 } 1132 1133 case MotionEvent.ACTION_CANCEL: 1134 case MotionEvent.ACTION_UP: 1135 /* Release the drag */ 1136 setIsBeingDragged(false); 1137 mActivePointerId = INVALID_POINTER; 1138 recycleVelocityTracker(); 1139 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) { 1140 postInvalidateOnAnimation(); 1141 } 1142 break; 1143 case MotionEvent.ACTION_POINTER_UP: 1144 onSecondaryPointerUp(ev); 1145 break; 1146 } 1147 1148 /* 1149 * The only time we want to intercept motion events is if we are in the 1150 * drag mode. 1151 */ 1152 return mIsBeingDragged; 1153 } 1154 1155 /** 1156 * @return Whether the specified motion event is actually happening over the content. 1157 */ 1158 private boolean isInContentBounds(MotionEvent event) { 1159 return event.getY() < getHeight() - getEmptyBottomMargin(); 1160 } 1161 1162 private void setIsBeingDragged(boolean isDragged) { 1163 mIsBeingDragged = isDragged; 1164 if (isDragged) { 1165 requestDisallowInterceptTouchEvent(true); 1166 mSwipeHelper.removeLongPressCallback(); 1167 } 1168 } 1169 1170 @Override 1171 public void onWindowFocusChanged(boolean hasWindowFocus) { 1172 super.onWindowFocusChanged(hasWindowFocus); 1173 if (!hasWindowFocus) { 1174 mSwipeHelper.removeLongPressCallback(); 1175 } 1176 } 1177 1178 @Override 1179 public boolean isScrolledToTop() { 1180 return mOwnScrollY == 0; 1181 } 1182 1183 @Override 1184 public boolean isScrolledToBottom() { 1185 return mOwnScrollY >= getScrollRange(); 1186 } 1187 1188 @Override 1189 public View getHostView() { 1190 return this; 1191 } 1192 1193 public int getEmptyBottomMargin() { 1194 int emptyMargin = mMaxLayoutHeight - mContentHeight; 1195 if (needsHeightAdaption()) { 1196 emptyMargin = emptyMargin - mBottomStackSlowDownHeight - mBottomStackPeekSize; 1197 } 1198 return Math.max(emptyMargin, 0); 1199 } 1200 1201 public void onExpansionStarted() { 1202 mStackScrollAlgorithm.onExpansionStarted(mCurrentStackScrollState); 1203 } 1204 1205 public void onExpansionStopped() { 1206 mStackScrollAlgorithm.onExpansionStopped(); 1207 } 1208 1209 private void setIsExpanded(boolean isExpanded) { 1210 mIsExpanded = isExpanded; 1211 mStackScrollAlgorithm.setIsExpanded(isExpanded); 1212 if (!isExpanded) { 1213 mOwnScrollY = 0; 1214 } 1215 } 1216 1217 @Override 1218 public void onHeightChanged(ExpandableView view) { 1219 updateContentHeight(); 1220 updateScrollPositionIfNecessary(); 1221 if (mOnHeightChangedListener != null) { 1222 mOnHeightChangedListener.onHeightChanged(view); 1223 } 1224 requestChildrenUpdate(); 1225 } 1226 1227 public void setOnHeightChangedListener( 1228 ExpandableView.OnHeightChangedListener mOnHeightChangedListener) { 1229 this.mOnHeightChangedListener = mOnHeightChangedListener; 1230 } 1231 1232 public void onChildAnimationFinished() { 1233 requestChildrenUpdate(); 1234 mAnimationEvents.clear(); 1235 } 1236 1237 /** 1238 * See {@link AmbientState#setDimmed}. 1239 */ 1240 public void setDimmed(boolean dimmed, boolean animate) { 1241 mStackScrollAlgorithm.setDimmed(dimmed); 1242 mAmbientState.setDimmed(dimmed); 1243 updatePadding(dimmed); 1244 if (animate) { 1245 mDimmedNeedsAnimation = true; 1246 mNeedsAnimation = true; 1247 } 1248 requestChildrenUpdate(); 1249 } 1250 1251 /** 1252 * See {@link AmbientState#setActivatedChild}. 1253 */ 1254 public void setActivatedChild(View activatedChild) { 1255 mAmbientState.setActivatedChild(activatedChild); 1256 mActivateNeedsAnimation = true; 1257 mNeedsAnimation = true; 1258 requestChildrenUpdate(); 1259 } 1260 1261 public View getActivatedChild() { 1262 return mAmbientState.getActivatedChild(); 1263 } 1264 1265 private void applyCurrentState() { 1266 mCurrentStackScrollState.apply(); 1267 if (mListener != null) { 1268 mListener.onChildLocationsChanged(this); 1269 } 1270 } 1271 1272 /** 1273 * A listener that is notified when some child locations might have changed. 1274 */ 1275 public interface OnChildLocationsChangedListener { 1276 public void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout); 1277 } 1278 1279 static class AnimationEvent { 1280 1281 static AnimationFilter[] FILTERS = new AnimationFilter[] { 1282 1283 // ANIMATION_TYPE_ADD 1284 new AnimationFilter() 1285 .animateAlpha() 1286 .animateHeight() 1287 .animateY() 1288 .animateZ(), 1289 1290 // ANIMATION_TYPE_REMOVE 1291 new AnimationFilter() 1292 .animateAlpha() 1293 .animateHeight() 1294 .animateY() 1295 .animateZ(), 1296 1297 // ANIMATION_TYPE_REMOVE_SWIPED_OUT 1298 new AnimationFilter() 1299 .animateAlpha() 1300 .animateHeight() 1301 .animateY() 1302 .animateZ(), 1303 1304 // ANIMATION_TYPE_TOP_PADDING_CHANGED 1305 new AnimationFilter() 1306 .animateAlpha() 1307 .animateHeight() 1308 .animateY() 1309 .animateDimmed() 1310 .animateScale() 1311 .animateZ(), 1312 1313 // ANIMATION_TYPE_START_DRAG 1314 new AnimationFilter() 1315 .animateAlpha(), 1316 1317 // ANIMATION_TYPE_SNAP_BACK 1318 new AnimationFilter() 1319 .animateAlpha(), 1320 1321 // ANIMATION_TYPE_ACTIVATED_CHILD 1322 new AnimationFilter() 1323 .animateScale() 1324 .animateAlpha(), 1325 1326 // ANIMATION_TYPE_DIMMED 1327 new AnimationFilter() 1328 .animateY() 1329 .animateScale() 1330 .animateDimmed() 1331 }; 1332 1333 static int[] LENGTHS = new int[] { 1334 1335 // ANIMATION_TYPE_ADD 1336 StackStateAnimator.ANIMATION_DURATION_STANDARD, 1337 1338 // ANIMATION_TYPE_REMOVE 1339 StackStateAnimator.ANIMATION_DURATION_STANDARD, 1340 1341 // ANIMATION_TYPE_REMOVE_SWIPED_OUT 1342 StackStateAnimator.ANIMATION_DURATION_STANDARD, 1343 1344 // ANIMATION_TYPE_TOP_PADDING_CHANGED 1345 StackStateAnimator.ANIMATION_DURATION_STANDARD, 1346 1347 // ANIMATION_TYPE_START_DRAG 1348 StackStateAnimator.ANIMATION_DURATION_STANDARD, 1349 1350 // ANIMATION_TYPE_SNAP_BACK 1351 StackStateAnimator.ANIMATION_DURATION_STANDARD, 1352 1353 // ANIMATION_TYPE_ACTIVATED_CHILD 1354 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED, 1355 1356 // ANIMATION_TYPE_DIMMED 1357 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED, 1358 }; 1359 1360 static int ANIMATION_TYPE_ADD = 0; 1361 static int ANIMATION_TYPE_REMOVE = 1; 1362 static int ANIMATION_TYPE_REMOVE_SWIPED_OUT = 2; 1363 static int ANIMATION_TYPE_TOP_PADDING_CHANGED = 3; 1364 static int ANIMATION_TYPE_START_DRAG = 4; 1365 static int ANIMATION_TYPE_SNAP_BACK = 5; 1366 static int ANIMATION_TYPE_ACTIVATED_CHILD = 6; 1367 static int ANIMATION_TYPE_DIMMED = 7; 1368 1369 final long eventStartTime; 1370 final View changingView; 1371 final int animationType; 1372 final AnimationFilter filter; 1373 final long length; 1374 1375 AnimationEvent(View view, int type) { 1376 eventStartTime = AnimationUtils.currentAnimationTimeMillis(); 1377 changingView = view; 1378 animationType = type; 1379 filter = FILTERS[type]; 1380 length = LENGTHS[type]; 1381 } 1382 1383 /** 1384 * Combines the length of several animation events into a single value. 1385 * 1386 * @param events The events of the lengths to combine. 1387 * @return The combined length. This is just the maximum of all length. 1388 */ 1389 static long combineLength(ArrayList<AnimationEvent> events) { 1390 long length = 0; 1391 int size = events.size(); 1392 for (int i = 0; i < size; i++) { 1393 length = Math.max(length, events.get(i).length); 1394 } 1395 return length; 1396 } 1397 } 1398 1399} 1400