用RecyclerView打造一個(gè)輪播圖

通常Android的輪播圖(俗名:Banner)都是用ViewPager實(shí)現(xiàn)的,但是我在實(shí)際項(xiàng)目運(yùn)用中碰到了一些小問題,于是決定另尋思路,采用RecyclerView這個(gè)更優(yōu)雅更強(qiáng)大的空間來實(shí)現(xiàn)輪播的功能,順便復(fù)習(xí)下RecyclerView的相關(guān)知識。

實(shí)現(xiàn)

一般輪播圖就兩個(gè)重要的部分:可以無限左右滑動(dòng)的圖片流和圖片位置的標(biāo)示點(diǎn),可能更簡單的連指示點(diǎn)都省略了。主要的難點(diǎn)還是在前者,因?yàn)橐粋€(gè)輪播圖要播放的圖片一般也就十來張,不做任何處理直接塞到RecyclerView里面,不僅稍微滑一下就沒了而且開始還不能先往左邊滑,所以我們需要在設(shè)置Adapter的總數(shù)時(shí)設(shè)置成一個(gè)比較大的數(shù)(可以是Integer.MAX_VALUE),然后在設(shè)置完圖片數(shù)據(jù)后把RecyclerView的當(dāng)前位置轉(zhuǎn)到中間的一個(gè)數(shù)(為了保證從第一張開始播放,必須是圖片總數(shù)的倍數(shù),比如10000*size),這樣item回收復(fù)用的時(shí)候,我們只要取當(dāng)前位置和圖片數(shù)量的余數(shù),得到真正的圖片位置。聽起來有點(diǎn)復(fù)雜,還是直接看下代碼吧:

private class RecyclerAdapter extends RecyclerView.Adapter {
        List<String> urlList;

        public void setData(List<String> urlList) {
            this.urlList = urlList;
        }

        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            return new RecyclerView.ViewHolder(new ImageView(getContext())) { };
        }

        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
            if (urlList == null || urlList.isEmpty())
                return;
            String url = urlList.get(position % bannerSize);
            Glide.with(getContext()).load(url).into((ImageView) holder.itemView);
        }
        @Override
        public int getItemCount() {
          //如果只有一張圖片就不滑動(dòng)了
            return bannerSize < 2 ? 1 : Integer.MAX_VALUE;
        }
    }

至于自動(dòng)滑動(dòng)圖片,就用Handler不斷延遲發(fā)送消息就好了:

private Handler mHandler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            if (msg.what == WHAT_AUTO_PLAY) {
                    mRecyclerView.smoothScrollToPosition(++currentIndex);
                 refreshIndicator();
                    mHandler.sendEmptyMessageDelayed(WHAT_AUTO_PLAY, autoPlayDuration);

            }
            return false;
        }
    });

好了,無限輪播解決了,接下來就是標(biāo)示點(diǎn)了,既然無限輪播圖都用RecyclerView解決了,那么標(biāo)示點(diǎn)也用它來解決吧:

  private class IndicatorAdapter extends RecyclerView.Adapter {

        int currentPosition = 0;

        public void setPosition(int currentPosition) {
            this.currentPosition = currentPosition;
        }

        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            return new RecyclerView.ViewHolder(new ImageView(getContext())) {
            };
        }

        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
            ImageView bannerPoint = (ImageView) holder.itemView;
            bannerPoint.setImageDrawable(currentPosition == position ? mSelectedDrawable : mUnselectedDrawable);

        }

        @Override
        public int getItemCount() {
            return bannerSize;
        }
    }

其實(shí)Adapter也很簡單,設(shè)置一個(gè)當(dāng)前位置的標(biāo)識點(diǎn),然后在圖片改變的時(shí)候notifyDataSetChanged()就行了。
好了最后就剩下怎么監(jiān)聽RecyclerView的位置改變了(可沒有像Viewpager的addOnPageChangeListener那么直接的方法),沒辦法直接分析RecyclerView.OnScrollListener中的回調(diào)方法吧:

 mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                //解決連續(xù)滑動(dòng)時(shí)指示器不更新的問題
                if (bannerSize < 2) return;
                int firstReal = mLinearLayoutManager.findFirstVisibleItemPosition();
                View viewFirst = mLinearLayoutManager.findViewByPosition(firstReal);
                float width = getWidth();
                if (width != 0 && viewFirst != null) {
                    float right = viewFirst.getRight();
                    float ratio = right / width;
                    if (ratio > 0.8) {
                        if (currentIndex != firstReal) {
                            currentIndex = firstReal;
                            refreshIndicator();
                        }
                    } else if (ratio < 0.2) {
                        if (currentIndex != firstReal + 1) {
                            currentIndex = firstReal + 1;
                            refreshIndicator();
                        }
                    }
                }
            }

            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            //連續(xù)滑動(dòng)時(shí)可能不會回調(diào)
                int first = mLinearLayoutManager.findFirstVisibleItemPosition();
                int last = mLinearLayoutManager.findLastVisibleItemPosition();
                if (currentIndex != first && first == last) {
                    currentIndex = first;
                    refreshIndicator();
                }
            }
        });

看看成果

好了解決了這些東西,再加些自定義View常用的屬性,回調(diào)方法,設(shè)置的接口,輪播圖就做好了,我們來看看效果:


gif.gif

嗯,看著還不錯(cuò),可是怎么有點(diǎn)怪?唉,這圖片怎么滑動(dòng)的這么快,而且還能停在中間,這個(gè)不是我們想要的`標(biāo)準(zhǔn)`輪播圖。要解決這個(gè)問題就要用到RecyclerView的另一個(gè)功能:SnapHelper。SnapHelper旨在支持RecyclerView的對齊方式,也就是通過計(jì)算對齊RecyclerView中TargetView 的指定點(diǎn)或者容器中的任何像素點(diǎn)。自定義一個(gè)SnapHelper挺麻煩的,還好android已經(jīng)為我們內(nèi)置好了兩個(gè)實(shí)現(xiàn): LinearSnapHelper & PagerSnapHelper。其中PagerSnapHelper真是我們需要的可以把RecyclerView改的像Viewpager的工具。
new PagerSnapHelper().attachToRecyclerView(mRecyclerView);

gif.gif

現(xiàn)在看著順暢多了:一次只能滑動(dòng)一張圖片,停止的時(shí)候圖片的位置也對了。這樣一個(gè)基礎(chǔ)版的輪播圖就做成了。因?yàn)楸举|(zhì)是一個(gè)RecyclerView,我們可以RecyclerView.Itemanimator,來做出更多的動(dòng)畫效果(這個(gè)我目前就不太會了(T_T))。最后奉上github地址,里面有更完整代碼,封裝了很多自定義屬性,歡迎star!

PS:進(jìn)階版請戳:

用RecyclerView打造一個(gè)輪播圖(進(jìn)階版)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容