/// <summary>To be called when the critical span is complete (i.e.</summary> /// <remarks> /// To be called when the critical span is complete (i.e. the /// animation is done animating). This can be called on any /// thread (even a different one from where the animation was /// taking place), but that's only a defensive implementation /// measure. It really makes no sense for you to call this on /// thread other than that where you created it. /// </remarks> /// <hide></hide> public virtual void finish() { android.os.StrictMode.ThreadSpanState state = mContainerState; lock (state) { if (mName == null) { return; } if (mPrev != null) { mPrev.mNext = mNext; } if (mNext != null) { mNext.mPrev = mPrev; } if (state.mActiveHead == this) { state.mActiveHead = mNext; } state.mActiveSize--; if (LOG_V) { android.util.Log.d(TAG, "Span finished=" + mName + "; size=" + state.mActiveSize); } this.mCreateMillis = -1; this.mName = null; this.mPrev = null; this.mNext = null; if (state.mFreeListSize < 5) { this.mNext = state.mFreeListHead; state.mFreeListHead = this; state.mFreeListSize++; } } }
public override bool onInterceptTouchEvent(android.view.MotionEvent ev) { int action = ev.getAction(); if ((action == android.view.MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) { return true; } switch (action & android.view.MotionEvent.ACTION_MASK) { case android.view.MotionEvent.ACTION_MOVE: { int activePointerId = mActivePointerId; if (activePointerId == INVALID_POINTER) { // If we don't have a valid id, the touch down wasn't on content. break; } int pointerIndex = ev.findPointerIndex(activePointerId); float y = ev.getY(pointerIndex); int yDiff = (int)System.Math.Abs(y - mLastMotionY); if (yDiff > mTouchSlop) { mIsBeingDragged = true; mLastMotionY = y; initVelocityTrackerIfNotExists(); mVelocityTracker.addMovement(ev); if (mScrollStrictSpan == null) { mScrollStrictSpan = android.os.StrictMode.enterCriticalSpan("ScrollView-scroll"); } } break; } case android.view.MotionEvent.ACTION_DOWN: { float y = ev.getY(); if (!inChild((int)ev.getX(), (int)y)) { mIsBeingDragged = false; recycleVelocityTracker(); break; } mLastMotionY = y; mActivePointerId = ev.getPointerId(0); initOrResetVelocityTracker(); mVelocityTracker.addMovement(ev); mIsBeingDragged = !mScroller.isFinished(); if (mIsBeingDragged && mScrollStrictSpan == null) { mScrollStrictSpan = android.os.StrictMode.enterCriticalSpan("ScrollView-scroll"); } break; } case android.view.MotionEvent.ACTION_CANCEL: case android.view.MotionEvent.ACTION_UP: { mIsBeingDragged = false; mActivePointerId = INVALID_POINTER; recycleVelocityTracker(); if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) { invalidate(); } break; } case android.view.MotionEvent.ACTION_POINTER_UP: { onSecondaryPointerUp(ev); break; } } return mIsBeingDragged; }
public override bool onTouchEvent(android.view.MotionEvent ev) { initVelocityTrackerIfNotExists(); mVelocityTracker.addMovement(ev); int action = ev.getAction(); switch (action & android.view.MotionEvent.ACTION_MASK) { case android.view.MotionEvent.ACTION_DOWN: { mIsBeingDragged = getChildCount() != 0; if (!mIsBeingDragged) { return false; } if (!mScroller.isFinished()) { mScroller.abortAnimation(); if (mFlingStrictSpan != null) { mFlingStrictSpan.finish(); mFlingStrictSpan = null; } } // Remember where the motion event started mLastMotionY = ev.getY(); mActivePointerId = ev.getPointerId(0); break; } case android.view.MotionEvent.ACTION_MOVE: { if (mIsBeingDragged) { // Scroll to follow the motion event int activePointerIndex = ev.findPointerIndex(mActivePointerId); float y = ev.getY(activePointerIndex); int deltaY = (int)(mLastMotionY - y); mLastMotionY = y; int oldX = mScrollX; int oldY = mScrollY; int range = getScrollRange(); int overscrollMode = getOverScrollMode(); bool canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS || (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0); if (overScrollBy(0, deltaY, 0, mScrollY, 0, range, 0, mOverscrollDistance, true)) { // Break our velocity if we hit a scroll barrier. mVelocityTracker.clear(); } onScrollChanged(mScrollX, mScrollY, oldX, oldY); if (canOverscroll) { int pulledToY = oldY + deltaY; if (pulledToY < 0) { mEdgeGlowTop.onPull((float)deltaY / getHeight()); if (!mEdgeGlowBottom.isFinished()) { mEdgeGlowBottom.onRelease(); } } else { if (pulledToY > range) { mEdgeGlowBottom.onPull((float)deltaY / getHeight()); if (!mEdgeGlowTop.isFinished()) { mEdgeGlowTop.onRelease(); } } } if (mEdgeGlowTop != null && (!mEdgeGlowTop.isFinished() || !mEdgeGlowBottom.isFinished ())) { invalidate(); } } } break; } case android.view.MotionEvent.ACTION_UP: { if (mIsBeingDragged) { android.view.VelocityTracker velocityTracker = mVelocityTracker; velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); int initialVelocity = (int)velocityTracker.getYVelocity(mActivePointerId); if (getChildCount() > 0) { if ((System.Math.Abs(initialVelocity) > mMinimumVelocity)) { fling(-initialVelocity); } else { if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) { invalidate(); } } } mActivePointerId = INVALID_POINTER; endDrag(); } break; } case android.view.MotionEvent.ACTION_CANCEL: { if (mIsBeingDragged && getChildCount() > 0) { if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) { invalidate(); } mActivePointerId = INVALID_POINTER; endDrag(); } break; } case android.view.MotionEvent.ACTION_POINTER_DOWN: { int index = ev.getActionIndex(); float y = ev.getY(index); mLastMotionY = y; mActivePointerId = ev.getPointerId(index); break; } case android.view.MotionEvent.ACTION_POINTER_UP: { onSecondaryPointerUp(ev); mLastMotionY = ev.getY(ev.findPointerIndex(mActivePointerId)); break; } } return true; }
private void endDrag() { mIsBeingDragged = false; recycleVelocityTracker(); if (mEdgeGlowTop != null) { mEdgeGlowTop.onRelease(); mEdgeGlowBottom.onRelease(); } if (mScrollStrictSpan != null) { mScrollStrictSpan.finish(); mScrollStrictSpan = null; } }
/// <summary>Fling the scroll view</summary> /// <param name="velocityY"> /// The initial velocity in the Y direction. Positive /// numbers mean that the finger/cursor is moving down the screen, /// which means we want to scroll towards the top. /// </param> public virtual void fling(int velocityY) { if (getChildCount() > 0) { int height = getHeight() - mPaddingBottom - mPaddingTop; int bottom = getChildAt(0).getHeight(); mScroller.fling(mScrollX, mScrollY, 0, velocityY, 0, 0, 0, System.Math.Max(0, bottom - height), 0, height / 2); bool movingDown = velocityY > 0; if (mFlingStrictSpan == null) { mFlingStrictSpan = android.os.StrictMode.enterCriticalSpan("ScrollView-fling"); } invalidate(); } }
protected internal override void onDetachedFromWindow() { base.onDetachedFromWindow(); if (mScrollStrictSpan != null) { mScrollStrictSpan.finish(); mScrollStrictSpan = null; } if (mFlingStrictSpan != null) { mFlingStrictSpan.finish(); mFlingStrictSpan = null; } }
public override void computeScroll() { if (mScroller.computeScrollOffset()) { // This is called at drawing time by ViewGroup. We don't want to // re-show the scrollbars at this point, which scrollTo will do, // so we replicate most of scrollTo here. // // It's a little odd to call onScrollChanged from inside the drawing. // // It is, except when you remember that computeScroll() is used to // animate scrolling. So unless we want to defer the onScrollChanged() // until the end of the animated scrolling, we don't really have a // choice here. // // I agree. The alternative, which I think would be worse, is to post // something and tell the subclasses later. This is bad because there // will be a window where mScrollX/Y is different from what the app // thinks it is. // int oldX = mScrollX; int oldY = mScrollY; int x = mScroller.getCurrX(); int y = mScroller.getCurrY(); if (oldX != x || oldY != y) { int range = getScrollRange(); int overscrollMode = getOverScrollMode(); bool canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS || (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0); overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, range, 0, mOverflingDistance, false ); onScrollChanged(mScrollX, mScrollY, oldX, oldY); if (canOverscroll) { if (y < 0 && oldY >= 0) { mEdgeGlowTop.onAbsorb((int)mScroller.getCurrVelocity()); } else { if (y > range && oldY <= range) { mEdgeGlowBottom.onAbsorb((int)mScroller.getCurrVelocity()); } } } } awakenScrollBars(); // Keep on drawing until the animation has finished. postInvalidate(); } else { if (mFlingStrictSpan != null) { mFlingStrictSpan.finish(); mFlingStrictSpan = null; } } }
/// <summary> /// Like /// <see cref="android.view.View.scrollBy(int, int)">android.view.View.scrollBy(int, int) /// </see> /// , but scroll smoothly instead of immediately. /// </summary> /// <param name="dx">the number of pixels to scroll by on the X axis</param> /// <param name="dy">the number of pixels to scroll by on the Y axis</param> public void smoothScrollBy(int dx, int dy) { if (getChildCount() == 0) { // Nothing to do. return; } long duration = android.view.animation.AnimationUtils.currentAnimationTimeMillis( ) - mLastScroll; if (duration > ANIMATED_SCROLL_GAP) { int height = getHeight() - mPaddingBottom - mPaddingTop; int bottom = getChildAt(0).getHeight(); int maxY = System.Math.Max(0, bottom - height); int scrollY = mScrollY; dy = System.Math.Max(0, System.Math.Min(scrollY + dy, maxY)) - scrollY; mScroller.startScroll(mScrollX, scrollY, 0, dy); invalidate(); } else { if (!mScroller.isFinished()) { mScroller.abortAnimation(); if (mFlingStrictSpan != null) { mFlingStrictSpan.finish(); mFlingStrictSpan = null; } } scrollBy(dx, dy); } mLastScroll = android.view.animation.AnimationUtils.currentAnimationTimeMillis(); }