summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrian Attwell <brianattwell@google.com>2014-09-23 20:59:44 +0000
committerAndroid Git Automerger <android-git-automerger@android.com>2014-09-23 20:59:44 +0000
commit6137dcf741d074dbd565ce6500700d2f64b82e03 (patch)
tree61bc1dd7bfab096ba3af55084fa6c4193e99ec48
parent01409f7ae47bda458144a5805bfd2c22202d7b72 (diff)
parent532e95628b24f9590d3cc3442d09efb9bff2067f (diff)
am 532e9562: Merge "Improve scrolling, handle onNestedPreFling" into lmp-dev
* commit '532e95628b24f9590d3cc3442d09efb9bff2067f': Improve scrolling, handle onNestedPreFling
-rw-r--r--src/com/android/dialer/list/ListsFragment.java59
-rw-r--r--src/com/android/dialer/list/SpeedDialFragment.java4
-rw-r--r--src/com/android/dialer/widget/OverlappingPaneLayout.java226
-rw-r--r--src/com/android/dialer/widget/ViewDragHelper.java87
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)