改变ViewPager的滑动速度,自定义动态设置切换动画时间

KaelLi 2019年2月18日15:50:20
评论
3,236

ViewPager是一个非常常用的Android控件,除了列表页面并列的时候使用外,还有一个很常见的应用场景就是轮播图,也就是Carousel或者Banner,一般其展示内容都是图片或者广告之类的。往往在轮播图里,需要实现自动播放,即不需要用户用手指滑动ViewPager,也能自己不断的往下切换数据元素,当跳转到最后一条时,接下来又重新回到最开始的位置。

一般来说,实现自动播放与无限循环不难,前者使用RxJava或者Runnable+Handler实现定时器然后不断的给ViewPager调用setCurrentItem方法,后者则是在PagerAdapter的getCount方法设置一个极大的值(一般是整型的上限),并且利用好取余的方法来从实际的数据List里获取数据即可。

一切好像看起来好像都没啥问题,是吧?但作为一个要进阶的Android Developer,我们不能仅仅满足于功能的实现,而且还要考虑一下体验的问题。我们先用一个简单的Demo,实现一个非常原始的轮播图,多余的代码就不罗列了,主要给一下PagerAdapter的实现,以及用Runnable+Handler实现的定时轮播,其他代码可以参考ViewPager与PagerAdapter使用详解与源码解析一文。注意啦,内存泄漏、取消Runnable等操作请自行处理,我为了省事就不额外写代码了。

初始化了一些必要的数据:

// 这里初始化了一个包含4个图片url的List,就不列举图片地址了
private List<String> mImageList =
        Arrays.asList("", "", "", "");

// Glide需要的一个配置选项
private RequestOptions options = new RequestOptions()
            .priority(Priority.NORMAL)
            .placeholder(R.mipmap.ic_launcher)
            .error(R.mipmap.ic_launcher)
            .skipMemoryCache(false)
            .diskCacheStrategy(DiskCacheStrategy.ALL);
private Handler handler = new Handler();

然后就是实现一个PagerAdapter:

private class CarouselAdapter extends PagerAdapter {
    @Override
    public int getCount() {

// 为了无限轮播,所以取整型的极限值。
        return Integer.MAX_VALUE;
    }

    @Override
    public boolean isViewFromObject(@NonNull View view, @NonNull Object o) {
        return view == o;
    }

    @NonNull
    @Override
    public Object instantiateItem(@NonNull ViewGroup container, int position) {
        ImageView imageView = new ImageView(VPActivity.this);
        imageView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
        imageView.setScaleType(ImageView.ScaleType.FIT_XY);

        // mImageList是一个包含了几个图片url的List
        String url = mImageList.get(position % mImageList.size());

        // 使用Glide加载图片,options是提前定义好的一个RequestOptions对象(Glide4.0后新添加的东西)。
        Glide.with(VPActivity.this)
                .load(url)
                .apply(options)
                .into(imageView);
        container.addView(imageView);
        return imageView;
    }

    @Override
    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
        container.removeView((ImageView)object);
    }
}

然后我们在给ViewPager绑定了PagerAdapter后,使用Runnable+Handler来实现轮播:

Runnable runnable = new Runnable() {
    @Override
    public void run() {
        vpTest.setCurrentItem(vpTest.getCurrentItem() + 1, true);
        handler.postDelayed(this, 1000);
    }
};
runnable.run();

运行结果:

改变ViewPager的滑动速度,自定义动态设置切换动画时间

不过呢,很多人会注意到一个问题:切换的太快了!用户看到这么快的切换动画,体验不太好。如果这个切换过程能慢一点,就好多了。注意了,刚才我设置的postDelayed间隔是1秒钟,但切换间隔与此无关,即使你设置成10秒,在切换的时候依然速度快的飞起!想要改善用户体验,让切换动画变得慢一点是很有必要的。

然而,ViewPager并没有提供相应的接口,所以只能去看它的源码了。很幸运,我们可以在ViewPager的源码里,找到这样一个变量:

private Scroller mScroller; // 就不复制它的初始化代码了

而且可以在源码里很清楚的看出来,setCurrentItem方法最终会调用smoothScrollTo(int x, int y, int velocity)方法,而其中又是依靠

this.mScroller.startScroll(sx, sy, dx, dy, duration);

这行代码,来把滑动动画的各种参数进行了设置,当然包括动画的持续时间。然后,再通过

ViewCompat.postInvalidateOnAnimation(this);

来实现动画的执行。

显然,如果能把tartScroll(sx, sy, dx, dy, duration)中的duration值进行改动,就算完成了我们减慢ViewPager切换动画,动态设定动画时间的目标了。不过呢,这个duration只是smoothScrollTo方法内部的一个局部变量,我们是改动不了的,所以就需要另辟蹊径了。

思路并不麻烦,可以一步一步的推:我们需要修改mScroller.startScroll的duration参数,但是这个参数获取不到。那么如果我们自己去继承Scroller类,并且覆盖startScroll方法,调用super.startScroll(startX, startY, dx, dy, mDuration)的时候,直接把这个间隔时间的值改掉,不就可以了?不过,ViewPager源码中的mScroller是private私有的,显然,想要替换替换掉它,需要依赖反射了。下面给出实现的代码:

// 自定义的Scroller
public class ViewPagerScroller extends Scroller {
    private int mDuration = 1000;

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

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

    public ViewPagerScroller(Context context, Interpolator interpolator, boolean flywheel) {
        super(context, interpolator, flywheel);
    }


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

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

    // 方法支持代码动态设定动画时间,实现改变ViewPager切换位置的滑动速度
    public void setDuration(int time) {
        mDuration = time;
    }
}

然后是通过反射来改变ViewPager中的mScroller:

try {
    Field mScroller = ViewPager.class.getDeclaredField("mScroller");
    mScroller.setAccessible(true);
    ViewPagerScroller scroller = new ViewPagerScroller(this, new AccelerateInterpolator());
    scroller.setDuration(500);
    mScroller.set(vpTest, scroller);
} catch (Exception e) {
    e.printStackTrace();
}

这里我把动画执行时间设置为500毫秒,然后看一下运行结果:

改变ViewPager的滑动速度,自定义动态设置切换动画时间

恩,这样的滑动速度就舒服多了。

KaelLi
  • 本文由 发表于 2019年2月18日15:50:20
  • 转载请务必保留本文链接:https://www.kaelli.com/31.html
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: