Android: speed-based sliding ViewPager scrolling

The way to scroll the ViewPager right now is one item per gesture. It handles the throwing gesture in the same way, whether it is a full-screen quick throw or slow drag; only one step is advanced on the final page.

Are there any projects, perhaps, or examples that would add speed-based acceleration that scrolls several elements based on the speed of an existing throw (if it is still running) and scrolls further if the waving gesture is wide and fast?

And if not, where to start with something like this?

PS Generosity is offered. Please do not respond to links to Gallery or HorizontalScrollView

+42
android android-viewpager gestures onfling
Oct 08 '12 at 18:20
source share
5 answers

Here we look at the ViewPager extension and ViewPager most of what the pager will do internally, combined with the scrolling logic from the Gallery widget. The general idea is to control the outlier (both speed and accompanying scrolls) and then feed them as fake shuffle events to the base ViewPager . If you do this alone, it won’t work (you still get only one page scroll). This is because fake drag and drop implements caps at borders that scrolls will be effective. You can simulate the calculations in the advanced ViewPager and detect when this happens, and then just turn the page and continue as usual. The advantage of using fake drag and drop means that you don’t have to deal with page snapping or edge processing of ViewPager .

I tested the following animation demo example code, downloaded from http://developer.android.com/training/animation/screen-slide.html , replacing ViewPager with ScreenSlideActivity with this VelocityViewPager (both in layout activity_screen_slide and field inside Activity).

 /* * Copyright 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Author: Dororo @ StackOverflow * An extended ViewPager which implements multiple page flinging. * */ package com.example.android.animationsdemo; import android.content.Context; import android.support.v4.view.ViewPager; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.GestureDetector; import android.widget.Scroller; public class VelocityViewPager extends ViewPager implements GestureDetector.OnGestureListener { private GestureDetector mGestureDetector; private FlingRunnable mFlingRunnable = new FlingRunnable(); private boolean mScrolling = false; public VelocityViewPager(Context context) { super(context); } public VelocityViewPager(Context context, AttributeSet attrs) { super(context, attrs); mGestureDetector = new GestureDetector(context, this); } // We have to intercept this touch event else fakeDrag functions won't work as it will // be in a real drag when we want to initialise the fake drag. @Override public boolean onInterceptTouchEvent(MotionEvent event) { return true; } @Override public boolean onTouchEvent(MotionEvent event) { // give all the events to the gesture detector. I'm returning true here so the viewpager doesn't // get any events at all, I'm sure you could adjust this to make that not true. mGestureDetector.onTouchEvent(event); return true; } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velX, float velY) { mFlingRunnable.startUsingVelocity((int)velX); return false; } private void trackMotion(float distX) { // The following mimics the underlying calculations in ViewPager float scrollX = getScrollX() - distX; final int width = getWidth(); final int widthWithMargin = width + this.getPageMargin(); final float leftBound = Math.max(0, (this.getCurrentItem() - 1) * widthWithMargin); final float rightBound = Math.min(this.getCurrentItem() + 1, this.getAdapter().getCount() - 1) * widthWithMargin; if (scrollX < leftBound) { scrollX = leftBound; // Now we know that we've hit the bound, flip the page if (this.getCurrentItem() > 0) { this.setCurrentItem(this.getCurrentItem() - 1, false); } } else if (scrollX > rightBound) { scrollX = rightBound; // Now we know that we've hit the bound, flip the page if (this.getCurrentItem() < (this.getAdapter().getCount() - 1) ) { this.setCurrentItem(this.getCurrentItem() + 1, false); } } // Do the fake dragging if (mScrolling) { this.fakeDragBy(distX); } else { this.beginFakeDrag(); this.fakeDragBy(distX); mScrolling = true; } } private void endFlingMotion() { mScrolling = false; this.endFakeDrag(); } // The fling runnable which moves the view pager and tracks decay private class FlingRunnable implements Runnable { private Scroller mScroller; // use this to store the points which will be used to create the scroll private int mLastFlingX; private FlingRunnable() { mScroller = new Scroller(getContext()); } public void startUsingVelocity(int initialVel) { if (initialVel == 0) { // there is no velocity to fling! return; } removeCallbacks(this); // stop pending flings int initialX = initialVel < 0 ? Integer.MAX_VALUE : 0; mLastFlingX = initialX; // setup the scroller to calulate the new x positions based on the initial velocity. Impose no cap on the min/max x values. mScroller.fling(initialX, 0, initialVel, 0, 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE); post(this); } private void endFling() { mScroller.forceFinished(true); endFlingMotion(); } @Override public void run() { final Scroller scroller = mScroller; boolean animationNotFinished = scroller.computeScrollOffset(); final int x = scroller.getCurrX(); int delta = x - mLastFlingX; trackMotion(delta); if (animationNotFinished) { mLastFlingX = x; post(this); } else { endFling(); } } } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distX, float distY) { trackMotion(-distX); return false; } // Unused Gesture Detector functions below @Override public boolean onDown(MotionEvent event) { return false; } @Override public void onLongPress(MotionEvent event) { // we don't want to do anything on a long press, though you should probably feed this to the page being long-pressed. } @Override public void onShowPress(MotionEvent event) { // we don't want to show any visual feedback } @Override public boolean onSingleTapUp(MotionEvent event) { // we don't want to snap to the next page on a tap so ignore this return false; } } 

There are a few minor problems with this that can be easily solved, but I will leave it to you, namely, things like if you scroll (drag and drop, and not drop), you may be halfway between the pages (you will want to bind to the ACTION_UP event) . In addition, touch events are completely redefined for this, so you will need to associate the corresponding events with the underlying ViewPager , if necessary.

+39
Feb 19 '13 at 23:54
source share

Another option is to copy the entire source code of the ViewPager implementation from the support library and configure the determineTargetPage(...) method. He is responsible for determining which page will scroll to fling gestures. This approach is not very convenient, but it works very well. See implementation code below:

 private int determineTargetPage(int curPage, float pageOffset, int velocity, int dx) { int target; if (Math.abs(dx) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) { target = calculateFinalPage(curPage, velocity); } else { final float truncator = curPage >= mCurItem ? 0.4f : 0.6f; target = (int) (curPage + pageOffset + truncator); } if (mItems.size() > 0) { final ItemInfo first = mItems.get(0); final ItemInfo last = mItems.get(mItems.size() - 1); // Only let the user target pages we have items for target = Math.max(first.position, Math.min(target, last.position)); } return target; } private int calculateFinalPage(int curPage, int velocity) { float distance = Math.abs(velocity) * MAX_SETTLE_DURATION / 1000f; float normalDistance = (float) Math.sqrt(distance / 2) * 25; int step = (int) - Math.signum(velocity); int width = getClientWidth(); int page = curPage; for (int i = curPage; i >= 0 && i < mAdapter.getCount(); i += step) { float pageWidth = mAdapter.getPageWidth(i); float remainingDistance = normalDistance - pageWidth * width; if (remainingDistance >= 0) { normalDistance = remainingDistance; } else { page = i; break; } } return page; } 
+4
Mar 12 '14 at 15:10
source share

I found a better implementation for myself than in the verified answer, this ViewPager behaves better with touches when I want to stop scrolling https://github.com/Benjamin-Dobell/VelocityViewPager

+3
Aug 19 '16 at 10:02
source share

ViewPager is a class from the support library. Download the support library source code and change about 10 lines of code in the onTouchEvent method to add the function you want.

I have been using a modified support library in my projects for about a year, because sometimes I need to change a few lines of code to slightly change or add a new method, and I do not want to copy the source code of the components. I use a modified version of the fragments and viewpager.

But there is one problem that you will get: once in 6 months you need to combine the user support library with the new official version if you need new features. And be careful with the changes, you do not want to break the compatibility of the support library classes.

+1
Feb 26 '13 at 10:30
source share

You can override the ScrollView or HorizontalScrollView class and add this behavior. There are a lot of mistakes in the “Gallery”, and, as I recall, it is deprecated from the 14th level api.

-one
Oct. 16
source share



All Articles