Android viewPager实现banner轮播

用什么来实现轮播:

在比较常见的主流控件里面,其实 ViewPager 和 RecyclerView 已经实现了类似的功能,尤其是 ViewPager,可以说是已经实现了我们这个控件的大部分功能,所以如果我们基于 ViewPager 来进行改造的话,也能让我们的轮播控件更加稳定
那 ViewPager 跟我们需要的自动轮播控件有多少差距呢,主要有两个:

1.不支持自动播放
2.无法从最后一张滑动到第一张
所以我们主要是针对这两部分进行相应的改造,从而实现我们自己的自动轮播控件。

1.1 实现自动轮播功能

要想实现自动轮播功能,我们最先想到的应该是通过 Timer 或者 ScheduledExecutorService 来实现计时器的功能,然后让 ViewPager 通过 serCurrentItem(int position) 方法,将当前的 Item 设置为下一个 position 的数据,但是如果通过定时器来实现的话,会有一个问题,那就是我们在需要让 banner 进行停止播放的时候就比较麻烦,所以通过 Handler 用 sendMessage 的形式,进行事件的发送实现 ViewPager 的自动轮播,以及某些场景的停止是比较合理的。

代码实现:

private static class BannerHander extends Handler {

    private WeakReference mBannerRef;

    private static final int MSG_CHANGE_SELECTION = 1;

    BannerHander(AutoScrollViewPager autoScrollViewPager) {
        mBannerRef = new WeakReference<>(autoScrollViewPager);
    }

    private void start() {
        removeMessages(MSG_CHANGE_SELECTION);
        sendEmptyMessageDelayed(MSG_CHANGE_SELECTION, AUTO_SCROLL_TIME);
    }

    private void stop() {
        removeMessages(MSG_CHANGE_SELECTION);
    }

    @Override
    public void handleMessage(Message msg) {
        if (msg.what == MSG_CHANGE_SELECTION) {
            if (mBannerRef == null || mBannerRef.get() == null) {
                return;
            }
            AutoScrollViewPager banner = mBannerRef.get();

            if (banner.mSelectedIndex == Integer.MAX_VALUE) {
                int rightPos = banner.mSelectedIndex % banner.mBannerList.size();
                banner.setCurrentItem(banner.getInitPosition() + rightPos + 1, true);
            } else {
                if (!hasMessages(MSG_CHANGE_SELECTION)) {
                    banner.setCurrentItem(banner.mSelectedIndex + 1, true);
                    sendEmptyMessageDelayed(MSG_CHANGE_SELECTION, AUTO_SCROLL_TIME);
                }
            }

        }
    }
}

可以看到,我们先传入外部的 ViewPager,然后通过弱引用的形式防止内存泄露,通过在 handlerMessage() 方法里面,调用 setCurrentItem() 方法,将当前 ViewPager 的 Item 设置为对应的 position + 1 的数据,所以我们只要在外部调用 Handler 的 sendMessage() 方法,就能使 ViewPager 进行自动的无限轮播。

1.2 让 ViewPager 从最后一张滑动到第一张

我们知道,ViewPager 是无法从最后一页滑动到第一页的,但我们可以换一个思路,如果我们在 ViewPager 的 Adapter 里面,通过 getCount() 方法将 ViewPager 的大小设置为无限大,然后通过取余的方式来保证滑动的页面一直对应数据源的那几个数据,这样便能让 ViewPager 实现从最后一张滑动到第一张的效果。

public int getCount() {
if (mBannerList == null) {
return 0;
}
if (mBannerList.size() == 1) {
return 1;
} else {
return Integer.MAX_VALUE;
}
}

public Object instantiateItem(ViewGroup container, final int position) {
if (mBannerList != null && mBannerList.size() > 0) {
View imageView = null;
Uri uri = Uri.parse(mBannerList.get(position % mBannerList.size()).cover_url); // 通过取余的方式
imageView = new SimpleDraweeView(mContext);
((SimpleDraweeView) imageView).setImageURI(uri);
container.addView(imageView);
return imageView;
}
return null;
}

二、如何进行优化

在上面我们只是简单的实现了 ViewPager 的自动轮播功能,但其实还有很多的细节需要我们进行优化,例如:我们是通过将 ViewPager 的大小设置为无限大的方式,来实现从最后一张滑动到第一张的,但这时候如果不进行缓存的话,我们在 Adapter 的 instantiateItem(ViewGroup container, final int position) 方法里面,便需要返回很多新 new 出来的 View,这样会造成不必要的内存浪费,只有对这些细节进行优化,才能让我们的控件更加的好用,稳定性和性能方面也会更加优异。

private final ArrayList mViewCaches = new ArrayList<>();

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

然后在 Adapter 的 instantiateItem() 方法中,从 List 中取出已经被缓存的 View,进行重复利用

public Object instantiateItem(ViewGroup container, final int position) {
if (mBannerList != null && mBannerList.size() > 0) {
View imageView = null;
Uri uri = Uri.parse(mBannerList.get(position % mBannerList.size()).cover_url);
if (mViewCaches.isEmpty()) {
imageView = new SimpleDraweeView(GlobalContext.getContext());
} else {
// 当缓存集合有数据时,进行复用
imageView = (ImageView) mViewCaches.remove(0);
}
}

2.2 适当的停止自动轮播

当我们触摸 Banner 或者离开当前展示 Banner 的页面时,如果 banner 还在不停的进行无线轮播的话,会造成没必要的性能损失,所以我们需要在触摸 Banner 以及当前的 Activity 为不可见状态的时候,停止 Banner 的轮播,从而提升性能。

public boolean dispatchTouchEvent(MotionEvent ev) {
int action = ev.getAction();
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL
|| action == MotionEvent.ACTION_OUTSIDE) {
startAutoPlay();
} else if (action == MotionEvent.ACTION_DOWN) {
stopAutoPlay();
}
return super.dispatchTouchEvent(ev);
}

2.3 改变 ViewPager 切换速度

原生的 ViewPager 在进行自动轮播的时候,切换速度是特别快的,会给人一种很突兀的感觉,而且 ViewPager 也没有提供接口给我们对 ViewPager 进行切换速度的设置,所以我们需要通过反射的方式,使用 Scroller 来进行切换速度的设置,从而让我们的 Banner 更加的丝滑。

public AutoScrollViewPager(Context context) {
this(context, null);
initViewPagerScroll();
}

private void initViewPagerScroll() {
    try {
        Field mField = ViewPager.class.getDeclaredField("mScroller");
        mField.setAccessible(true);
        BannerScroller scroller = new BannerScroller(getContext());
        mField.set(this, scroller);
    } catch (Exception e) {
        Log.d(TAG, e.getMessage());
    }
}

public class BannerScroller extends Scroller {

private static final int BANNER_DURATION = 1000;
private int mDuration = BANNER_DURATION;

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

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

}

至此,我们的自动轮播控件,无论是性能上还是稳定性上都已经很不错了。

你可能感兴趣的:(Android viewPager实现banner轮播)