用RecyclerView做轮播图,比想象中的简单

发布于 2020-06-19  94 次阅读


老早就听说RecyclerView也是能用来做轮播图的了,然后又有点厌烦了ViewPager的实现轮播图实现方式。

就网上找了些文章看看,实际做了一下,发觉用起来还挺简单的。

布局中添加控件

<!--轮播图-->
<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/banner_rv"
    android:layout_width="match_parent"
    android:layout_height="200dp"/>

布局上并没有什么不同,总之先放置一个RecyclerView吧。

设置横向的布局管理器

mBannerLM = new LinearLayoutManager(getActivity(), LinearLayoutManager.HORIZONTAL, false);
banner_rv.setLayoutManager(mBannerLM);

轮播图通常是横向的,所以这里也是设置的横向的布局管理器。

绑定 LinearSnapHelper

mLinearSnapHelper = new LinearSnapHelper();
mLinearSnapHelper.attachToRecyclerView(banner_rv);

LinearSnapHelper是SnapHelper的一个官方实现类,作用是让RecyclerView在滚动时可以保持有一个item在中间位置,类似于轮播图的效果。
创建好之后,通过attachToRecyclerView()方法绑定上RecyclerView即可。

设置适配器

mBannerAdapter = new HomeBannerAdapter();
banner_rv.setAdapter(mBannerAdapter);

老样子,需要设置个适配器。

对滑动事件进行过滤

banner_rv.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        LinearLayoutManager manager = (LinearLayoutManager) recyclerView.getLayoutManager();
        int firstVisibleItemPosition = manager.findFirstVisibleItemPosition();
        switch (newState) {
            case SCROLL_STATE_IDLE:  //(静止没有滚动)
                if(isSlidingByHand){
                    mCurrentPosition = firstVisibleItemPosition;
                }
                break;
            case SCROLL_STATE_DRAGGING:  //(正在被外部拖拽,一般为用户正在用手指滚动)
                isSlidingByHand = true;
                isSlidingAuto = false;
                break;
            case SCROLL_STATE_SETTLING:  //(自动滚动)
                isSlidingAuto = !isSlidingByHand;
                break;
        }
    }
});

为了更好的用户体验,对RecyclerView的滑动事件做一下处理。

适配器中修改item数为最大值

public int getInitPosition(){
    if(mList == null){
        return 0;
    }
    int position = Integer.MAX_VALUE / 2;
    int diff = position % mList.size();
    return position - diff;
}

类似于ViewPager做轮播图无限轮播时的手法,当然也一样是个假的无限。

给适配器设置数据

HttpHelper.getPlayTabBanner(new HttpResponseRunOnUI() {

    @Override
    public void onErrorRunOnUI(String msg) {
        showMoErrorToast("获取Banners时网络错误");
    }

    @Override
    public void onSuccessRunOnUI(String json) {
        Log.i("mo--", "json=" + json);
        List<PlayTabBannerBean> bannerList = JsonUtil.parseList(json, PlayTabBannerBean.class);
        if(bannerList.size() == 0){
            // 没有轮播图
            return;
        }
        // 设置轮播图
        mBannerAdapter.setData(bannerList);
        // 通知更新
        mBannerAdapter.notifyDataSetChanged();
        // 设置展示到中间
        AppUtils.MoveToPosition(mBannerLM, banner_rv, mBannerAdapter.getInitPosition());
        mCurrentPosition = mBannerAdapter.getInitPosition();
    }
});

给适配器设置数据后记得要notifyDataSetChanged();。

跳到中间位置

/**
 * RecyclerView 移动到当前位置,
 *
 * @param manager   设置RecyclerView对应的manager
 * @param mRecyclerView  当前的RecyclerView
 * @param n  要跳转的位置
 */
public static void MoveToPosition(LinearLayoutManager manager, RecyclerView mRecyclerView, int n) {
    int firstItem = manager.findFirstVisibleItemPosition();
    int lastItem = manager.findLastVisibleItemPosition();
    if (n <= firstItem) {
        mRecyclerView.scrollToPosition(n);
    } else if (n <= lastItem) {
        int top = mRecyclerView.getChildAt(n - firstItem).getTop();
        mRecyclerView.scrollBy(0, top);
    } else {
        mRecyclerView.scrollToPosition(n);
    }
}

由于item设置为了一个非常大的数值了,因此只需要将初始的坐标移动到总item数的中间就达到了无限轮播的假象了。

使用定时器开始轮播

mBannerInterval = Observable.interval(4, 4, TimeUnit.SECONDS, AndroidSchedulers.mainThread())
    .map(aLong -> isVisible())
    .subscribe(aLong -> {
        if (mBannerAdapter != null && mBannerAdapter.getItemCount() != 0) {
            LogUtil.i("Banner定时器进行中");
            mCurrentPosition++;
            banner_rv.smoothScrollToPosition(mCurrentPosition);
         }
    });

最近都喜欢用RxJava来弄定时器,RxJava2的写法与上面代码有差异了,使用java的定时器还是RxJava或者RxJava2来定时,就自行解决吧,能周期性得动起来就行。

处理轮播图点击

mBannerAdapter.setOnItemClickListener((parent, view, position, id) -> {
    PlayTabBannerBean picRes = mBannerAdapter.getItem(position);
    //showToast("点击了轮播图:" + position);
    String type = picRes.getType();
    if("web".equals(type)){
        // 网页打开
        if(getActivity() == null){
            return;
        }
        AppUtils.openWebUrl(getActivity(), picRes.getClick());
    }else if("key".equals(type)){
        // 搜索关键字
        intentPlaySearchResultActivity(picRes.getClick());
    }
});

点击事件的处理方法就是RecyclerView的处理方法,毕竟本质还是RecyclerView嘛。

完成

由此就实现了一个项目经常用到的轮播图的功能了。相对于传统的ViewPager的实现方式,用RecyclerView来做的话,在增加、删除、变更条目排序这些需要上,是要更加灵活易用。因此对于轮播图这种需求,我是建议采用RecyclerView来完成的。


做一只特立独行的猪