Android:基于速度的ViewPager滚动

Bos*_*one 43 android gestures onfling android-viewpager

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

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

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

PS提供赏金.请不要回答对Gallery或Horizo​​ntalScrollView的引用

Dor*_*oro 40

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

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

}
Run Code Online (Sandbox Code Playgroud)

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

  • 这个解决方案不适合我.触摸事件似乎不一致,并且有很多跳跃.其他人有问题吗? (2认同)
  • 为了使这个代码与setCurrentItem一起工作,我们需要在trackMotion方法上面维护offset.Add/modify下面的行-------> float scrollOffset = getScrollX() - (this.getCurrentItem()*widthWithMargin); float scrollX = getScrollX() - distX - scrollOffset; (2认同)