【问题标题】:Android: velocity-based ViewPager scrollingAndroid:基于速度的 ViewPager 滚动
【发布时间】:2012-09-29 00:46:12
【问题描述】:

ViewPager 现在滚动的方式是每个手势一个项目。无论是全屏快闪还是慢拖,对挥动手势的处理方式都是一样的;在最后一页只前进了一步。

是否有任何项目或示例可能会添加基于速度的投掷,根据现有投掷的速度滚动多个项目(如果仍在进行中)并在投掷手势又宽又快时进一步滚动?

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

附:提供赏金。请不要参考 Gallery 或 Horizo​​ntalScrollView 的答案

【问题讨论】:

  • 我认为画廊的行为是这样的,您是否尝试过它是否适合您的需求? developer.android.com/reference/android/widget/Gallery.html
  • 可能是,我需要检查一下
  • 而且没有 +Ian Warwick - 使用画廊是不可能的
  • 您需要使用 ViewPager 还是可以考虑使用 ViewFlipper 等其他类?
  • 它已经是一个 ViewPager 并且完全符合我的需求。我所需要的只是使其按描述滚动的增强功能,所以没有 ViewFlipper

标签: android android-viewpager gestures onfling


【解决方案1】:

您可以覆盖 ScrollView 或 Horizo​​ntalScrollView 类,并添加该行为。 Gallery 中有很多错误,据我所知,自 api 级别 14 起它已被弃用。

【讨论】:

  • 我阅读了这个问题,并且有一条关于 Gallery 的评论。你写的“可能是,我需要检查一下”关于画廊,我无法添加评论,这就是为什么添加为答案。
  • 我提到了画廊代码作为如何增强 ViewPager 滚动的可能示例。这个问题完全是关于 ViewPager,我正在寻找替代方案
  • 我写了类似的扩展Horizo​​ntalScrollView。我相信 Horizo​​ntalScrollView 是你应该走的路。
【解决方案2】:

这里的技术是扩展ViewPager 并模仿寻呼机将在内部执行的大部分操作,以及来自Gallery 小部件的滚动逻辑。一般的想法是监控投掷(以及速度和伴随的滚动),然后将它们作为假拖动事件提供给底层ViewPager。如果您单独执行此操作,则将无法正常工作(您仍然只能获得一页滚动)。发生这种情况是因为假拖动在滚动有效的边界上实现了上限。您可以模拟扩展 ViewPager 中的计算并检测何时会发生这种情况,然后只需翻页并照常继续。使用假拖动的好处意味着您不必处理对齐页面或处理ViewPager 的边缘。

我在动画演示示例中测试了以下代码,可从 http://developer.android.com/training/animation/screen-slide.html 下载,方法是将 ScreenSlideActivity 中的 ViewPager 替换为此 VelocityViewPager(在布局 activity_screen_slide 和 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;
}

}

这有一些小问题,可以很容易地解决,但我会留给你,即如果你滚动(拖动,而不是扔)你可能会在页面之间结束(你会想要捕捉 ACTION_UP 事件)。此外,为了做到这一点,触摸事件被完全覆盖,因此您需要在适当的情况下将相关事件提供给底层ViewPager

【讨论】:

  • 谢谢@Dororo - 我会试试看我在这里缺少什么
  • 谢谢@Dororo。太棒了。
  • 我试过这个,它工作正常,但如果我从代码中执行 pager.setCurrentItem(m_CurrPage, false) 以在启动时将其初始化为不同于 0 的页面,则无法正常工作,每次返回第一页时,如何初始化起始页以使其正常工作?
  • 这个解决方案不适合我。触摸事件似乎不一致,并且有很多跳跃。其他人有问题吗?
  • 为了使这段代码与 setCurrentItem 一起工作,我们需要维护偏移量。在上面的 trackMotion 方法中添加/修改以下行-------> float scrollOffset = getScrollX() - (this.getCurrentItem( ) * widthWithMargin); float scrollX = getScrollX() - distX - scrollOffset;
【解决方案3】:

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

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

但是您会遇到一个问题:大约每 6 个月一次,如果您需要新功能,您必须将自定义支持库与新的官方版本合并。并且要小心更改,您不想破坏支持库类的兼容性。

【讨论】:

  • 这真的没用
【解决方案4】:

另一种选择是从支持库中复制整个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...一样的东西!
  • 我尝试了你所做的,但现在我可以解决 mAdapter 的问题。它用于处理 android.support.v4.view.ViewPager 实例,而不是 com.mypackage.ViewPager。您是如何解决这个问题的?
【解决方案5】:

我找到了比检查答案更好的实现方式,当我想停止滚动时,这个 ViewPager 的触摸表现更好 https://github.com/Benjamin-Dobell/VelocityViewPager

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-01
    • 1970-01-01
    相关资源
    最近更新 更多