以编程方式滑动时更改 ViewPager 动画持续时间

我用以下代码更改幻灯片:

viewPager.setCurrentItem(index++, true);

但是变化太快了。有没有办法手动设置动画速度?

69327 次浏览

我一直想做我自己,并且已经达成了一个解决方案(然而,使用反射)。在 Galaxy Nexus JB 4.2.1上测试 我还没有对它进行测试,但是它应该可以工作,或者需要最小限度的修改。。您需要在 XML 中使用 ViewPagerCustomDuration而不是 ViewPager,然后您可以这样做:

ViewPagerCustomDuration vp = (ViewPagerCustomDuration) findViewById(R.id.myPager);
vp.setScrollDurationFactor(2); // make the animation twice as slow

ViewPagerCustomDuration.java :

import android.content.Context;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.animation.Interpolator;


import java.lang.reflect.Field;


public class ViewPagerCustomDuration extends ViewPager {


public ViewPagerCustomDuration(Context context) {
super(context);
postInitViewPager();
}


public ViewPagerCustomDuration(Context context, AttributeSet attrs) {
super(context, attrs);
postInitViewPager();
}


private ScrollerCustomDuration mScroller = null;


/**
* Override the Scroller instance with our own class so we can change the
* duration
*/
private void postInitViewPager() {
try {
Field scroller = ViewPager.class.getDeclaredField("mScroller");
scroller.setAccessible(true);
Field interpolator = ViewPager.class.getDeclaredField("sInterpolator");
interpolator.setAccessible(true);


mScroller = new ScrollerCustomDuration(getContext(),
(Interpolator) interpolator.get(null));
scroller.set(this, mScroller);
} catch (Exception e) {
}
}


/**
* Set the factor by which the duration will change
*/
public void setScrollDurationFactor(double scrollFactor) {
mScroller.setScrollDurationFactor(scrollFactor);
}


}

ScrollerCustomDuration.java :

import android.annotation.SuppressLint;
import android.content.Context;
import android.view.animation.Interpolator;
import android.widget.Scroller;


public class ScrollerCustomDuration extends Scroller {


private double mScrollFactor = 1;


public ScrollerCustomDuration(Context context) {
super(context);
}


public ScrollerCustomDuration(Context context, Interpolator interpolator) {
super(context, interpolator);
}


@SuppressLint("NewApi")
public ScrollerCustomDuration(Context context, Interpolator interpolator, boolean flywheel) {
super(context, interpolator, flywheel);
}


/**
* Set the factor by which the duration will change
*/
public void setScrollDurationFactor(double scrollFactor) {
mScrollFactor = scrollFactor;
}


@Override
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
super.startScroll(startX, startY, dx, dy, (int) (duration * mScrollFactor));
}


}

希望这对谁有帮助!

更好的解决方案是通过在支持包中创建类来访问私有字段。剪辑这是绑定到 MAX_SETTLE_DURATION的600ms,由 ViewPager类设置。

package android.support.v4.view;


import android.content.Context;
import android.util.AttributeSet;


public class SlowViewPager extends ViewPager {


// The speed of the scroll used by setCurrentItem()
private static final int VELOCITY = 200;


public SlowViewPager(Context context) {
super(context);
}


public SlowViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}


@Override
void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) {
setCurrentItemInternal(item, smoothScroll, always, VELOCITY);
}


}

当然,您可以添加一个自定义属性,这样就可以通过 XML 设置它。

我已经找到了更好的解决方案,基于 @ df778899的答案Android ValueAnimator API ,它不需要反射就可以很好地工作,而且非常灵活。 此外,也不需要制作自定义 ViewPager 并将其放入 android.support. v4.view 包中。 这里有一个例子:

private void animatePagerTransition(final boolean forward) {


ValueAnimator animator = ValueAnimator.ofInt(0, viewPager.getWidth());
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}


@Override
public void onAnimationEnd(Animator animation) {
viewPager.endFakeDrag();
}


@Override
public void onAnimationCancel(Animator animation) {
viewPager.endFakeDrag();
}


@Override
public void onAnimationRepeat(Animator animation) {
}
});


animator.setInterpolator(new AccelerateInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {


private int oldDragPosition = 0;


@Override
public void onAnimationUpdate(ValueAnimator animation) {
int dragPosition = (Integer) animation.getAnimatedValue();
int dragOffset = dragPosition - oldDragPosition;
oldDragPosition = dragPosition;
viewPager.fakeDragBy(dragOffset * (forward ? -1 : 1));
}
});


animator.setDuration(AppConstants.PAGER_TRANSITION_DURATION_MS);
if (viewPager.beginFakeDrag()) {
animator.start();
}
}

更新:

只是检查这个解决方案是否可以用来一次刷几个页面(例如,如果第一个页面应该显示在最后一个页面之后)。这是经过轻微修改的代码,用于处理指定的页面计数:

private int oldDragPosition = 0;


private void animatePagerTransition(final boolean forward, int pageCount) {
// if previous animation have not finished we can get exception
if (pagerAnimation != null) {
pagerAnimation.cancel();
}
pagerAnimation = getPagerTransitionAnimation(forward, pageCount);
if (viewPager.beginFakeDrag()) {    // checking that started drag correctly
pagerAnimation.start();
}
}


private Animator getPagerTransitionAnimation(final boolean forward, int pageCount) {
ValueAnimator animator = ValueAnimator.ofInt(0, viewPager.getWidth() - 1);
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}


@Override
public void onAnimationEnd(Animator animation) {
viewPager.endFakeDrag();
}


@Override
public void onAnimationCancel(Animator animation) {
viewPager.endFakeDrag();
}


@Override
public void onAnimationRepeat(Animator animation) {
viewPager.endFakeDrag();
oldDragPosition = 0;
viewPager.beginFakeDrag();
}
});


animator.setInterpolator(new AccelerateInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {


@Override
public void onAnimationUpdate(ValueAnimator animation) {
int dragPosition = (Integer) animation.getAnimatedValue();
int dragOffset = dragPosition - oldDragPosition;
oldDragPosition = dragPosition;
viewPager.fakeDragBy(dragOffset * (forward ? -1 : 1));
}
});


animator.setDuration(AppConstants.PAGER_TRANSITION_DURATION_MS / pageCount); // remove divider if you want to make each transition have the same speed as single page transition
animator.setRepeatCount(pageCount);


return animator;
}
 public class PresentationViewPager extends ViewPager {


public static final int DEFAULT_SCROLL_DURATION = 250;
public static final int PRESENTATION_MODE_SCROLL_DURATION = 1000;


public PresentationViewPager (Context context) {
super(context);
}


public PresentationViewPager (Context context, AttributeSet attrs) {
super(context, attrs);
}


public void setDurationScroll(int millis) {
try {
Class<?> viewpager = ViewPager.class;
Field scroller = viewpager.getDeclaredField("mScroller");
scroller.setAccessible(true);
scroller.set(this, new OwnScroller(getContext(), millis));
} catch (Exception e) {
e.printStackTrace();
}
}


public class OwnScroller extends Scroller {


private int durationScrollMillis = 1;


public OwnScroller(Context context, int durationScroll) {
super(context, new DecelerateInterpolator());
this.durationScrollMillis = durationScroll;
}


@Override
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
super.startScroll(startX, startY, dx, dy, durationScrollMillis);
}
}
}

这是我在 Librera Reader中使用的代码

public class MyViewPager extends ViewPager {


public MyViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
initMyScroller();
}


private void initMyScroller() {
try {
Class<?> viewpager = ViewPager.class;
Field scroller = viewpager.getDeclaredField("mScroller");
scroller.setAccessible(true);
scroller.set(this, new MyScroller(getContext())); // my liner scroller


Field mFlingDistance = viewpager.getDeclaredField("mFlingDistance");
mFlingDistance.setAccessible(true);
mFlingDistance.set(this, Dips.DP_10);//10 dip


Field mMinimumVelocity = viewpager.getDeclaredField("mMinimumVelocity");
mMinimumVelocity.setAccessible(true);
mMinimumVelocity.set(this, 0); //0 velocity


} catch (Exception e) {
LOG.e(e);
}


}


public class MyScroller extends Scroller {
public MyScroller(Context context) {
super(context, new LinearInterpolator()); // my LinearInterpolator
}


@Override
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
super.startScroll(startX, startY, dx, dy, 175);//175 duration
}
}


}

在浪费了我一整天的时间之后,我找到了一个解决方案,把截图页数限制设置为页面总数。

为了保持固定长度的 ViewPager 滚动平滑,setOffScreenlimit (page.length)将把所有视图保存在内存中。然而,对于任何涉及调用 View.requestLayout 函数的动画(例如,任何涉及更改边距或边界的动画)来说,这都是一个问题。这使得它们变得非常慢(就像 Romain Guy 所说的那样) ,因为内存中的所有视图都将失效。因此,我尝试了一些不同的方法来使事情变得顺利,但是覆盖 request Layout 和其他无效方法将导致许多其他问题。

一个很好的折衷方案是动态修改离屏限制,这样页面之间的大部分滚动将非常平滑,同时确保所有页面动画通过删除用户时的视图而平滑。当您只有1个或2个视图时,这种方法工作得非常好,因为这些视图必须使其他视图离开内存。

当没有任何解决方案时使用这个,因为通过设置偏移量限制,你将同时加载所有的片段

我使用 Cicero Moura 的版本创建了一个 Kotlin 类,它在 Android 10中仍然可以完美运行。

import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.animation.DecelerateInterpolator
import android.widget.Scroller
import androidx.viewpager.widget.ViewPager


class CustomViewPager(context: Context, attrs: AttributeSet) :
ViewPager(context, attrs) {




private companion object {
const val DEFAULT_SPEED = 1000
}


init {
setScrollerSpeed(DEFAULT_SPEED)
}


var scrollDuration = DEFAULT_SPEED
set(millis) {
setScrollerSpeed(millis)
}


private fun setScrollerSpeed(millis: Int) {
try {
ViewPager::class.java.getDeclaredField("mScroller")
.apply {
isAccessible = true
set(this@CustomViewPager, OwnScroller(millis))
}


} catch (e: Exception) {
e.printStackTrace()
}
}


inner class OwnScroller(private val durationScrollMillis: Int) : Scroller(context, AccelerateDecelerateInterpolator()) {
override fun startScroll(startX: Int, startY: Int, dx: Int, dy: Int, duration: Int) {
super.startScroll(startX, startY, dx, dy, durationScrollMillis)
}
}
}

从活动类初始化:

viewPager.apply {
scrollDuration = 2000
adapter = pagerAdapter
}