diff options
author | Brian Attwell <brianattwell@google.com> | 2014-09-23 20:49:39 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2014-09-23 20:49:40 +0000 |
commit | 532e95628b24f9590d3cc3442d09efb9bff2067f (patch) | |
tree | 7a9edfeed074507797828a416efd4d00db1c6988 | |
parent | e8dc29f5e298505943901e92dd8186c427c57596 (diff) | |
parent | 764f652b451d27282cfaf73407d31c9522e6cb0e (diff) |
Merge "Improve scrolling, handle onNestedPreFling" into lmp-dev
-rw-r--r-- | src/com/android/dialer/list/ListsFragment.java | 59 | ||||
-rw-r--r-- | src/com/android/dialer/list/SpeedDialFragment.java | 4 | ||||
-rw-r--r-- | src/com/android/dialer/widget/OverlappingPaneLayout.java | 226 | ||||
-rw-r--r-- | src/com/android/dialer/widget/ViewDragHelper.java | 87 |
4 files changed, 317 insertions, 59 deletions
diff --git a/src/com/android/dialer/list/ListsFragment.java b/src/com/android/dialer/list/ListsFragment.java index 7cc519fe1..2aa78a2d2 100644 --- a/src/com/android/dialer/list/ListsFragment.java +++ b/src/com/android/dialer/list/ListsFragment.java @@ -4,15 +4,10 @@ import android.animation.LayoutTransition; import android.app.ActionBar; import android.app.Fragment; import android.app.FragmentManager; -import android.app.LoaderManager; import android.content.Context; -import android.content.CursorLoader; -import android.content.Loader; import android.content.SharedPreferences; import android.database.Cursor; -import android.net.Uri; import android.os.Bundle; -import android.provider.CallLog; import android.support.v13.app.FragmentPagerAdapter; import android.support.v4.view.ViewPager; import android.support.v4.view.ViewPager.OnPageChangeListener; @@ -20,6 +15,7 @@ import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.AbsListView; import android.widget.ListView; import com.android.contacts.common.GeoUtil; @@ -34,7 +30,7 @@ import com.android.dialer.calllog.ContactInfoHelper; import com.android.dialer.list.ShortcutCardsAdapter.SwipeableShortcutCard; import com.android.dialer.util.DialerUtils; import com.android.dialer.widget.OverlappingPaneLayout; -import com.android.dialer.widget.OverlappingPaneLayout.PanelSlideListener; +import com.android.dialer.widget.OverlappingPaneLayout.PanelSlideCallbacks; import com.android.dialerbind.analytics.AnalyticsFragment; import com.android.dialerbind.ObjectFactory; @@ -108,7 +104,7 @@ public class ListsFragment extends AnalyticsFragment implements CallLogQueryHand */ private long mCurrentCallShortcutDate = 0; - private PanelSlideListener mPanelSlideListener = new PanelSlideListener() { + private PanelSlideCallbacks mPanelSlideCallbacks = new PanelSlideCallbacks() { @Override public void onPanelSlide(View panel, float slideOffset) { // For every 1 percent that the panel is slid upwards, clip 1 percent off the top @@ -152,8 +148,35 @@ public class ListsFragment extends AnalyticsFragment implements CallLogQueryHand } mIsPanelOpen = false; } + + @Override + public void onPanelFlingReachesEdge(int velocityY) { + if (getCurrentListView() != null) { + getCurrentListView().fling(velocityY); + } + } + + @Override + public boolean isScrollableChildUnscrolled() { + final AbsListView listView = getCurrentListView(); + return listView != null && (listView.getChildCount() == 0 + || listView.getChildAt(0).getTop() == listView.getPaddingTop()); + } }; + private AbsListView getCurrentListView() { + final int position = mViewPager.getCurrentItem(); + switch (getRtlPosition(position)) { + case TAB_INDEX_SPEED_DIAL: + return mSpeedDialFragment == null ? null : mSpeedDialFragment.getListView(); + case TAB_INDEX_RECENTS: + return mRecentsFragment == null ? null : mRecentsFragment.getListView(); + case TAB_INDEX_ALL_CONTACTS: + return mAllContactsFragment == null ? null : mAllContactsFragment.getListView(); + } + throw new IllegalStateException("No fragment at position " + position); + } + public class ViewPagerAdapter extends FragmentPagerAdapter { public ViewPagerAdapter(FragmentManager fm) { super(fm); @@ -178,6 +201,26 @@ public class ListsFragment extends AnalyticsFragment implements CallLogQueryHand } @Override + public Object instantiateItem(ViewGroup container, int position) { + // On rotation the FragmentManager handles rotation. Therefore getItem() isn't called. + // Copy the fragments that the FragmentManager finds so that we can store them in + // instance variables for later. + final Fragment fragment = (Fragment) super.instantiateItem(container, position); + switch (getRtlPosition(position)) { + case TAB_INDEX_SPEED_DIAL: + mSpeedDialFragment = (SpeedDialFragment) fragment; + return mSpeedDialFragment; + case TAB_INDEX_RECENTS: + mRecentsFragment = (CallLogFragment) fragment; + return mRecentsFragment; + case TAB_INDEX_ALL_CONTACTS: + mAllContactsFragment = (AllContactsFragment) fragment; + return mAllContactsFragment; + } + return super.instantiateItem(container, position); + } + + @Override public int getCount() { return TAB_INDEX_COUNT; } @@ -360,7 +403,7 @@ public class ListsFragment extends AnalyticsFragment implements CallLogQueryHand // the framework better supports nested scrolling. paneLayout.setCapturableView(mViewPagerTabs); paneLayout.openPane(); - paneLayout.setPanelSlideListener(mPanelSlideListener); + paneLayout.setPanelSlideCallbacks(mPanelSlideCallbacks); paneLayout.setIntermediatePinnedOffset( ((HostInterface) getActivity()).getActionBarHeight()); diff --git a/src/com/android/dialer/list/SpeedDialFragment.java b/src/com/android/dialer/list/SpeedDialFragment.java index 9732e19b3..c02c3d7f9 100644 --- a/src/com/android/dialer/list/SpeedDialFragment.java +++ b/src/com/android/dialer/list/SpeedDialFragment.java @@ -420,4 +420,8 @@ public class SpeedDialFragment extends AnalyticsFragment implements OnItemClickL public void cacheOffsetsForDatasetChange() { saveOffsets(0); } + + public AbsListView getListView() { + return mListView; + } } diff --git a/src/com/android/dialer/widget/OverlappingPaneLayout.java b/src/com/android/dialer/widget/OverlappingPaneLayout.java index b6b9ec777..b81722942 100644 --- a/src/com/android/dialer/widget/OverlappingPaneLayout.java +++ b/src/com/android/dialer/widget/OverlappingPaneLayout.java @@ -33,6 +33,7 @@ import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; +import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.ViewParent; import android.view.accessibility.AccessibilityEvent; @@ -116,18 +117,27 @@ public class OverlappingPaneLayout extends ViewGroup { /** * Indicates that the layout is currently in the process of a nested pre-scroll operation where - * the child scrolling view is being dragged downwards, and still has the ability to consume - * scroll events itself. If so, we should open the pane up to the maximum offset defined in - * {@link #mIntermediateOffset}, and no further, so that the child view can continue performing - * its own scroll. + * the child scrolling view is being dragged downwards. */ - private boolean mInNestedPreScrollDownwards = false; + private boolean mInNestedPreScrollDownwards; /** - * Indicates whether or not a nested scrolling child is able to scroll internally at this point - * in time. + * Indicates that the layout is currently in the process of a nested pre-scroll operation where + * the child scrolling view is being dragged upwards. */ - private boolean mChildCannotConsumeScroll; + private boolean mInNestedPreScrollUpwards; + + /** + * Indicates that the layout is currently in the process of a fling initiated by a pre-fling + * from the child scrolling view. + */ + private boolean mIsInNestedFling; + + /** + * Indicates the direction of the pre fling. We need to store this information since + * OverScoller doesn't expose the direction of its velocity. + */ + private boolean mInUpwardsPreFling; /** * Stores an offset used to represent a point somewhere in between the panel's fully closed @@ -139,7 +149,7 @@ public class OverlappingPaneLayout extends ViewGroup { private float mInitialMotionX; private float mInitialMotionY; - private PanelSlideListener mPanelSlideListener; + private PanelSlideCallbacks mPanelSlideCallbacks; private final ViewDragHelper mDragHelper; @@ -154,9 +164,18 @@ public class OverlappingPaneLayout extends ViewGroup { private final Rect mTmpRect = new Rect(); /** - * Listener for monitoring events about sliding panes. + * How many dips we need to scroll past a position before we can snap to the next position + * on release. Using this prevents accidentally snapping to positions. + * + * This is needed since vertical nested scrolling can be passed to this class even if the + * vertical scroll is less than the the nested list's touch slop. */ - public interface PanelSlideListener { + private final int mReleaseScrollSlop; + + /** + * Callbacks for interacting with sliding panes. + */ + public interface PanelSlideCallbacks { /** * Called when a sliding pane's position changes. * @param panel The child view that was moved @@ -176,6 +195,22 @@ public class OverlappingPaneLayout extends ViewGroup { * @param panel The child view that was slid to a closed position */ public void onPanelClosed(View panel); + + /** + * Called when a sliding pane is flung as far open/closed as it can be. + * @param velocityY Velocity of the panel once its fling goes as far as it can. + */ + public void onPanelFlingReachesEdge(int velocityY); + + /** + * Returns true if the second panel's contents haven't been scrolled at all. This value is + * used to determine whether or not we can fully expand the header on downwards scrolls. + * + * Instead of using this callback, it would be preferable to instead fully expand the header + * on a View#onNestedFlingOver() callback. The behavior would be nicer. Unfortunately, + * no such callback exists yet (b/17547693). + */ + public boolean isScrollableChildUnscrolled(); } public OverlappingPaneLayout(Context context) { @@ -199,6 +234,8 @@ public class OverlappingPaneLayout extends ViewGroup { mDragHelper = ViewDragHelper.create(this, 0.5f, new DragHelperCallback()); mDragHelper.setMinVelocity(MIN_FLING_VELOCITY * density); + + mReleaseScrollSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); } /** @@ -218,27 +255,21 @@ public class OverlappingPaneLayout extends ViewGroup { mCapturableView = capturableView; } - public void setPanelSlideListener(PanelSlideListener listener) { - mPanelSlideListener = listener; + public void setPanelSlideCallbacks(PanelSlideCallbacks listener) { + mPanelSlideCallbacks = listener; } void dispatchOnPanelSlide(View panel) { - if (mPanelSlideListener != null) { - mPanelSlideListener.onPanelSlide(panel, mSlideOffset); - } + mPanelSlideCallbacks.onPanelSlide(panel, mSlideOffset); } void dispatchOnPanelOpened(View panel) { - if (mPanelSlideListener != null) { - mPanelSlideListener.onPanelOpened(panel); - } + mPanelSlideCallbacks.onPanelOpened(panel); sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); } void dispatchOnPanelClosed(View panel) { - if (mPanelSlideListener != null) { - mPanelSlideListener.onPanelClosed(panel); - } + mPanelSlideCallbacks.onPanelClosed(panel); sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); } @@ -820,7 +851,7 @@ public class OverlappingPaneLayout extends ViewGroup { @Override public void computeScroll() { - if (mDragHelper.continueSettling(true)) { + if (mDragHelper.continueSettling(/* deferCallbacks = */ false)) { if (!mCanSlide) { mDragHelper.abort(); return; @@ -897,7 +928,6 @@ public class OverlappingPaneLayout extends ViewGroup { final boolean startNestedScroll = (nestedScrollAxes & SCROLL_AXIS_VERTICAL) != 0; if (startNestedScroll) { mIsInNestedScroll = true; - mChildCannotConsumeScroll = true; mDragHelper.startNestedScroll(mSlideableView); } if (DEBUG) { @@ -915,19 +945,41 @@ public class OverlappingPaneLayout extends ViewGroup { if (DEBUG) { Log.d(TAG, "onNestedPreScroll: " + dy); } - mInNestedPreScrollDownwards = - mChildCannotConsumeScroll && dy < 0 && mSlideOffsetPx <= mIntermediateOffset; + + mInNestedPreScrollDownwards = dy < 0; + mInNestedPreScrollUpwards = dy > 0; + mIsInNestedFling = false; mDragHelper.processNestedScroll(mSlideableView, 0, -dy, consumed); } @Override + public boolean onNestedPreFling(View target, float velocityX, float velocityY) { + if (!(velocityY > 0 && mSlideOffsetPx != 0 + || velocityY < 0 && mSlideOffsetPx < mIntermediateOffset + || velocityY < 0 && mSlideOffsetPx < mSlideRange + && mPanelSlideCallbacks.isScrollableChildUnscrolled())) { + // No need to consume the fling if the fling won't collapse or expand the header. + // How far we are willing to expand the header depends on isScrollableChildUnscrolled(). + return false; + } + + if (DEBUG) { + Log.d(TAG, "onNestedPreFling: " + velocityY); + } + mInUpwardsPreFling = velocityY > 0; + mIsInNestedFling = true; + mIsInNestedScroll = false; + mDragHelper.processNestedFling(mSlideableView, (int) -velocityY); + return true; + } + + @Override public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { if (DEBUG) { Log.d(TAG, "onNestedScroll: " + dyUnconsumed); } - mChildCannotConsumeScroll = false; - mInNestedPreScrollDownwards = false; + mIsInNestedFling = false; mDragHelper.processNestedScroll(mSlideableView, 0, -dyUnconsumed, null); } @@ -938,8 +990,10 @@ public class OverlappingPaneLayout extends ViewGroup { } if (mIsInNestedScroll) { mDragHelper.stopNestedScroll(mSlideableView); + mInNestedPreScrollDownwards = false; + mInNestedPreScrollUpwards = false; + mIsInNestedScroll = false; } - mIsInNestedScroll = false; } private class DragHelperCallback extends ViewDragHelper.Callback { @@ -955,6 +1009,10 @@ public class OverlappingPaneLayout extends ViewGroup { @Override public void onViewDragStateChanged(int state) { + if (DEBUG) { + Log.d(TAG, "onViewDragStateChanged: " + state); + } + if (mDragHelper.getViewDragState() == ViewDragHelper.STATE_IDLE) { if (mSlideOffset == 0) { updateObscuredViewsVisibility(mSlideableView); @@ -965,6 +1023,16 @@ public class OverlappingPaneLayout extends ViewGroup { mPreservedOpenState = true; } } + + if (mDragHelper.getVelocityMagnitude() > 0 + && (mDragHelper.getCurrentScrollY() == 0 + || mDragHelper.getCurrentScrollY() == mIntermediateOffset) + && mIsInNestedFling) { + mIsInNestedFling = false; + final int flingVelocity = !mInUpwardsPreFling ? + -mDragHelper.getVelocityMagnitude() : mDragHelper.getVelocityMagnitude(); + mPanelSlideCallbacks.onPanelFlingReachesEdge(flingVelocity); + } } @Override @@ -980,20 +1048,96 @@ public class OverlappingPaneLayout extends ViewGroup { } @Override + public void onViewFling(View releasedChild, float xVelocity, float yVelocity) { + if (releasedChild == null) { + return; + } + if (DEBUG) { + Log.d(TAG, "onViewFling: " + yVelocity); + } + + // Flings won't always fully expand or collapse the header. Instead of performing the + // fling and then waiting for the fling to end before snapping into place, we + // immediately snap into place if we predict the fling won't fully expand or collapse + // the header. + int yOffsetPx = mDragHelper.predictFlingYOffset((int) yVelocity); + if (yVelocity < 0) { + // Only perform a fling if we know the fling will fully compress the header. + if (-yOffsetPx > mSlideOffsetPx) { + mDragHelper.flingCapturedView(releasedChild.getLeft(), /* minTop = */ 0, + mSlideRange, Integer.MAX_VALUE, (int) yVelocity); + } else { + mIsInNestedFling = false; + onViewReleased(releasedChild, xVelocity, yVelocity); + } + } else { + // Only perform a fling if we know the fling will expand the header as far + // as it can possible be expanded, given the isScrollableChildUnscrolled() value. + if (yOffsetPx + mSlideOffsetPx >= mSlideRange + && mPanelSlideCallbacks.isScrollableChildUnscrolled()) { + mDragHelper.flingCapturedView(releasedChild.getLeft(), /* minTop = */ 0, + Integer.MAX_VALUE, mSlideRange, (int) yVelocity); + } else if (yOffsetPx + mSlideOffsetPx >= mIntermediateOffset + && mSlideOffsetPx <= mIntermediateOffset + && !mPanelSlideCallbacks.isScrollableChildUnscrolled()) { + mDragHelper.flingCapturedView(releasedChild.getLeft(), /* minTop = */ 0, + Integer.MAX_VALUE, mIntermediateOffset, (int) yVelocity); + } else { + mIsInNestedFling = false; + onViewReleased(releasedChild, xVelocity, yVelocity); + } + } + + mInNestedPreScrollDownwards = false; + mInNestedPreScrollUpwards = false; + + // Without this invalidate, some calls to flingCapturedView can have no affect. + invalidate(); + } + + @Override public void onViewReleased(View releasedChild, float xvel, float yvel) { + if (DEBUG) { + Log.d(TAG, "onViewReleased: " + + " unscrolled=" + mPanelSlideCallbacks.isScrollableChildUnscrolled() + + ", mInNestedPreScrollDownwards = " + mInNestedPreScrollDownwards + + ", mInNestedPreScrollUpwards = " + mInNestedPreScrollUpwards + + ", yvel=" + yvel); + } if (releasedChild == null) { return; } - final LayoutParams lp = (LayoutParams) releasedChild.getLayoutParams(); + final LayoutParams lp = (LayoutParams) releasedChild.getLayoutParams(); int top = getPaddingTop() + lp.topMargin; - if (mInNestedPreScrollDownwards) { - // Snap to the closest pinnable position based on the current slide offset - // (in pixels) [0 - mIntermediateoffset - mSlideRange] - if (yvel > 0) { + // Decide where to snap to according to the current direction of motion and the current + // position. The velocity's magnitude has no bearing on this. + if (mInNestedPreScrollDownwards || yvel > 0) { + // Scrolling downwards + if (mSlideOffsetPx > mIntermediateOffset + mReleaseScrollSlop) { + top += mSlideRange; + } else if (mSlideOffsetPx > mReleaseScrollSlop) { + top += mIntermediateOffset; + } else { + // Offset is very close to 0 + } + } else if (mInNestedPreScrollUpwards || yvel < 0) { + // Scrolling upwards + if (mSlideOffsetPx > mSlideRange - mReleaseScrollSlop) { + // Offset is very close to mSlideRange top += mSlideRange; - } else if (0 <= mSlideOffsetPx && mSlideOffsetPx <= mIntermediateOffset / 2) { + } else if (mSlideOffsetPx > mIntermediateOffset - mReleaseScrollSlop) { + // Offset is between mIntermediateOffset and mSlideRange. + top += mIntermediateOffset; + } else { + // Offset is between 0 and mIntermediateOffset. + } + } else { + // Not moving upwards or downwards. This case can only be triggered when directly + // dragging the tabs. We don't bother to remember previous scroll direction + // when directly dragging the tabs. + if (0 <= mSlideOffsetPx && mSlideOffsetPx <= mIntermediateOffset / 2) { // Offset is between 0 and mIntermediateOffset, but closer to 0 // Leave top unchanged } else if (mIntermediateOffset / 2 <= mSlideOffsetPx @@ -1005,8 +1149,6 @@ public class OverlappingPaneLayout extends ViewGroup { // mSlideRange top += mSlideRange; } - } else if (yvel > 0 || (yvel == 0 && mSlideOffset > 0.5f)) { - top += mSlideRange; } mDragHelper.settleCapturedViewAt(releasedChild.getLeft(), top); @@ -1029,9 +1171,15 @@ public class OverlappingPaneLayout extends ViewGroup { final LayoutParams lp = (LayoutParams) mSlideableView.getLayoutParams(); final int newTop; + int previousTop = top - dy; int topBound = getPaddingTop() + lp.topMargin; - int bottomBound = topBound - + (mInNestedPreScrollDownwards ? mIntermediateOffset : mSlideRange); + int bottomBound = topBound + (mPanelSlideCallbacks.isScrollableChildUnscrolled() + || !mIsInNestedScroll ? mSlideRange : mIntermediateOffset); + if (previousTop > bottomBound) { + // We were previously below the bottomBound, so loosen the bottomBound so that this + // makes sense. This can occur after the view was directly dragged by the tabs. + bottomBound = Math.max(bottomBound, mSlideRange); + } newTop = Math.min(Math.max(top, topBound), bottomBound); return newTop; diff --git a/src/com/android/dialer/widget/ViewDragHelper.java b/src/com/android/dialer/widget/ViewDragHelper.java index 91016d15b..e4fe12be2 100644 --- a/src/com/android/dialer/widget/ViewDragHelper.java +++ b/src/com/android/dialer/widget/ViewDragHelper.java @@ -27,7 +27,6 @@ import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; -import android.view.animation.Interpolator; import java.util.Arrays; @@ -202,6 +201,18 @@ public class ViewDragHelper { public void onViewReleased(View releasedChild, float xvel, float yvel) {} /** + * Called when the child view has been released with a fling. + * + * <p>Calling code may decide to fling or otherwise release the view to let it + * settle into place.</p> + * + * @param releasedChild The captured child view now being released + * @param xvel X velocity of the fling. + * @param yvel Y velocity of the fling. + */ + public void onViewFling(View releasedChild, float xvel, float yvel) {} + + /** * Called when one of the subscribed edges in the parent view has been touched * by the user while no child view is currently captured. * @@ -321,16 +332,6 @@ public class ViewDragHelper { } } - /** - * Interpolator defining the animation curve for mScroller - */ - private static final Interpolator sInterpolator = new Interpolator() { - public float getInterpolation(float t) { - t -= 1.0f; - return t * t * t * t * t + 1.0f; - } - }; - private final Runnable mSetIdleRunnable = new Runnable() { public void run() { setDragState(STATE_IDLE); @@ -389,7 +390,7 @@ public class ViewDragHelper { mTouchSlop = vc.getScaledTouchSlop(); mMaxVelocity = vc.getScaledMaximumFlingVelocity(); mMinVelocity = vc.getScaledMinimumFlingVelocity(); - mScroller = ScrollerCompat.create(context, sInterpolator); + mScroller = ScrollerCompat.create(context); } /** @@ -702,6 +703,46 @@ public class ViewDragHelper { } /** + * Settle the captured view based on standard free-moving fling behavior. + * The caller should invoke {@link #continueSettling(boolean)} on each subsequent frame + * to continue the motion until it returns false. + * + * @param minLeft Minimum X position for the view's left edge + * @param minTop Minimum Y position for the view's top edge + * @param maxLeft Maximum X position for the view's left edge + * @param maxTop Maximum Y position for the view's top edge + * @param yvel the Y velocity to fling with + */ + public void flingCapturedView(int minLeft, int minTop, int maxLeft, int maxTop, int yvel) { + if (!mReleaseInProgress) { + throw new IllegalStateException("Cannot flingCapturedView outside of a call to " + + "Callback#onViewReleased"); + } + mScroller.abortAnimation(); + mScroller.fling(mCapturedView.getLeft(), mCapturedView.getTop(), 0, yvel, minLeft, maxLeft, + minTop, maxTop); + + setDragState(STATE_SETTLING); + } + + /** + * Predict how far a fling with {@param yvel} will cause the view to travel from stand still. + * @return predicted y offset + */ + public int predictFlingYOffset(int yvel) { + mScroller.abortAnimation(); + mScroller.fling(0, 0, 0, yvel, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, + Integer.MAX_VALUE); + final int finalY = mScroller.getFinalY(); + mScroller.abortAnimation(); + return finalY; + } + + public int getCurrentScrollY() { + return mScroller.getCurrY(); + } + + /** * Move the captured settling view by the appropriate amount for the current time. * If <code>continueSettling</code> returns true, the caller should call it again * on the next frame to continue. @@ -750,6 +791,28 @@ public class ViewDragHelper { return mDragState == STATE_SETTLING; } + public void processNestedFling(View target, int yvel) { + mCapturedView = target; + dispatchViewFling(0, yvel); + } + + public int getVelocityMagnitude() { + // Use Math.abs() to ensure this always returns an absolute value, even if the + // ScrollerCompat implementation changes. + return (int) Math.abs(mScroller.getCurrVelocity()); + } + + private void dispatchViewFling(float xvel, float yvel) { + mReleaseInProgress = true; + mCallback.onViewFling(mCapturedView, xvel, yvel); + mReleaseInProgress = false; + + if (mDragState == STATE_DRAGGING) { + // onViewReleased didn't call a method that would have changed this. Go idle. + setDragState(STATE_IDLE); + } + } + /** * Like all callback events this must happen on the UI thread, but release * involves some extra semantics. During a release (mReleaseInProgress) |