Android:基于速度的ViewPager滚动

ViewPager立即滚动的方式是每个手势一个项目。 无论是全屏快速还是慢速拖动,都会以同样的方式对待挥动的手势; 最后一页只提前一步。

有没有任何项目可能会增加基于速度的投掷,如果投掷的手势是宽而快的,则基于现有投掷的速度滚动多个项目(如果它仍在进行中)并且进一步滚动?

如果没有从这样的事情开始?

PS赏金提供。 请不要参考Gallery或Horizo​​ntalScrollView

这里的技术是扩展ViewPager并模仿大部分的内部ViewPager ,再加上来自Gallery小部件的滚动逻辑。 一般的想法是监视投掷(和速度和伴随的滚动),然后将它们作为假的拖动事件馈送到底层的ViewPager 。 如果你单独这样做,它不会工作,但(你仍然只会得到一个页面滚动)。 发生这种情况是因为假拖动在滚动将会生效的边界上实现上限。 您可以在扩展的ViewPager模拟计算,并检测何时会发生这种情况,然后只需翻转页面并照常继续。 使用假拖拽的好处意味着您不必处理捕捉页面或处理ViewPager的边缘。

我在animation演示示例中testing了以下代码,可以从http://developer.android.com/training/animation/screen-slide.html下载,通过用此;VelocityViewPager代替ScreenSlideActivity中的ScreenSlideActivity (两者都在布局activity_screen_slide和field在活动内)。

 /* * 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; } } 

这里有一些小问题,可以很容易解决,但我会留给你,就像你滚动(拖动,不扔),你可以在页面之间的一半(你会想要抓住ACTION_UP事件)。 此外,触摸事件正在被完全覆盖,为此,您需要在相应的位置将相关事件提供给底层的ViewPager

另一种select是复制支持库中的整个ViewPager实现源代码,并自定义一个determineTargetPage(...)方法。 它负责确定在哪个页面上滚动到滑动手势。 这种方法不是非常方便,但工作得很好。 请参阅以下实施代码:

 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; } 

我发现比在检查的答案更好的实现,这个ViewPager行为更好,当我想停止滚动触摸https://github.com/Benjamin-Dobell/VelocityViewPager

ViewPager是支持库的类。 下载支持库源代码,并在onTouchEvent方法中更改约10行代码以添加所需的function。

我在我的项目中使用修改后的支持库大约一年,因为有时候我需要修改几行代码来做一些改动或添加新的方法,而我不想复制组件源代码。 我使用片段和viewpager的修改版本。

但有一个问题你会得到:一旦在6个山左右,你必须合并自定义支持库与新的正式版本,如果你需要新的function。 并且要小心修改,你不想中断支持库类的兼容性。

您可以重写ScrollView或Horizo​​ntalScrollView类,并添加该行为。 Gallery中有很多bug,我记得自从14级以来已经弃用了。