This is what I ended up doing for a flingable view. It is definitely more painful (it required a lot of trial and error on my part) than just extending from view pager, but it gave me what I wanted. You will have to figure out a way to use the View's scrollX in order to determine what item to snap to and what to do when you reach the end or start of the view.
public class FlingableScroller extends View implements GestureDetector.OnGestureListener {
public FlingableScroller(Context context, AttributeSet attrs) {
super(context, attrs);
mGestureDetector = new GestureDetector(context, this);
mScroller = new OverScroller(context);
//This is how you "set current item" You will have to calculate INITIAL_SCROLL_X yourself
setScrollX(INITIAL_SCROLL_X);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
animateFlingIfApplicable();
}
private void animateFlingIfApplicable() {
//We have a fling only if computeScrollOffset is true.
if (mScroller.computeScrollOffset()) {
//Flinging passed the start point so continue the fling at the end
if (mScroller.getFinalX() == 0) {
int velocity = getFlingVelocity();
mScroller.forceFinished(true);
mScroller.fling(MAXIMUM_SCROLL_X, 0, velocity / FRICTION_COEFFICIENT, 0, 0, MAXIMUM_SCROLL_X, 0, 0);
//Flinging passed the end point so continue the fling at the start
} else if (mScroller.getFinalX() == MAXIMUM_SCROLL_X) {
int velocity = getFlingVelocity();
mScroller.forceFinished(true);
mScroller.fling(0, 0, velocity / FRICTION_COEFFICIENT, 0, 0, MAXIMUM_SCROLL_X, 0, 0);
} else if (mScroller.getFinalX() == getScrollX() || mScroller.getCurrVelocity() == 0) {
snapToItem();
mScroller.forceFinished(true);
} else {
scrollTo(mScroller.getCurrX(), 0);
}
}
invalidate();
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
//This logic handles the case when you scroll pass the beginning or the end of the scroller
if (getScrollX() > MAXIMUM_SCROLL_X) {
scrollTo(0, 0);
} else if (getScrollX() >= 0) {
scrollBy(distance, 0);
} else {
scrollTo(MAXIMUM_SCROLL_X, 0);
}
invalidate();
return true;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
//This is to prevent low velocity "flings"
if (Math.abs(velocityX) < 400 * SCREEN_DENSITY) {
return false;
}
mScroller.forceFinished(true);
//Define friction_coefficient to a value that gives you desirable flinging.
mScroller.fling(getScrollX(), getScrollY(), (int) -velocityX / FRICTION_COEFFICIENT, 0, 0, MAXIMUM_SCROLL_X, 0, 0);
invalidate();
return true;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mScroller.forceFinished(true);
break;
case MotionEvent.ACTION_UP:
snapToItem();
invalidate();
break;
}
return mGestureDetector.onTouchEvent(event);
}
private void snapToItem() {
//The the user lifts up their finger from a scroll or when a fling finishes determine what item to snap to. See the ViewPager source code to emulate the "fake drag"
int scrollByValue = getScrollX() / SOME_VALUE
scrollTo(scrollByValue, 0);
}
}