首页 文章

Android:基于速度的ViewPager滚动

提问于
浏览
42

ViewPager现在滚动的方式是每个手势一个项目 . 无论是全屏快速拖动还是慢速拖动,它都以同样的方式处理投掷手势;在结束页面只前进一步 .

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

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

附:提供赏金 . 请不要回答对Gallery或HorizontalScrollView的引用

5 回答

  • 39

    另一种选择是从支持库复制整个 ViewPager 实现源代码并自定义 determineTargetPage(...) 方法 . 它负责确定在fling手势上滚动到哪个页面 . 这种方法不是很方便,但效果很好 . 请参阅以下实施代码:

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

    这里的技术是扩展 ViewPager 并模仿寻呼机将在内部执行的大部分内容,以及来自 Gallery 小部件的滚动逻辑 . 一般的想法是监视fling(和速度和伴随的滚动),然后将它们作为假拖动事件提供给底层的 ViewPager . 如果你单独这样做,它赢得了't work though (you'仍然只有一页滚动) . 发生这种情况是因为假拖动在滚动有效的边界上实现了限制 . 您可以模拟扩展 ViewPager 中的计算并检测何时会发生这种情况,然后只需翻转页面并继续照常操作即可 . 使用假拖动的好处意味着您不必处理对页面的捕捉或处理 ViewPager 的边缘 .

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

    这有一些小问题,可以很容易地解决,但我会留给你,即如果你滚动(拖动,而不是投掷),你可以在页面之间的一半结束(你会想要 grab ACTION_UP事件) . 此外,触摸事件正在被完全覆盖以执行此操作,因此您需要在适当的情况下将相关事件提供给基础 ViewPager .

  • 1

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

  • -1

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

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

    但是你会遇到一个问题:如果你需要新的功能,一旦进入大约6 mounth,你必须将自定义支持库与新的官方版本合并 . 并且要小心更改,您不希望破坏支持库类的兼容性 .

  • 4

    您可以覆盖ScrollView或HorizontalScrollView类,并添加该行为 . Gallery中有很多bug,我记得自api 14级以来它已被弃用了 .

相关问题