利用RecyclerView實現無限輪播廣告條

wizardev發表於2019-03-03

前言: 公司產品需要新增懸浮廣告條的功能,要求是可以迴圈滾動,並且點選相應的浮條會跳轉到相應的介面,在實現這個功能的時候遇到一些坑,幸運的是最後從這些坑中爬了出來。這篇文章的主要內容就是介紹功能的實現以及爬坑的經驗。

效果展示

  在文章開始前,先看下最後實現的效果,最終的效果如下圖

利用RecyclerView實現無限輪播廣告條

需求分析

  我們已經知道了產品的需求,下面要做的就是分析這個需求應該怎樣實現,首先我們要實現的功能就是讓廣告條迴圈滾動,看最終的效果圖可以發現,滾動的方向是由下往上滾動,平時我們見的banner圖都是左右滾動的,如果是左右滾動的就好辦了,可以通過ViewPager來實現。但是這個上下滾動的應該怎麼實現呢?首先,想到的是利用ViewFlipper這個系統控制元件,但是這個控制元件只能滿足迴圈滾動這個功能,我們還有一個需求是點選不同的浮條跳轉不同的內容呢!這個功能ViewFlipper就無法滿足了。既要迴圈滾動又要每個浮條有相應的點選事件,自然的就想到了RecyclerView,下面就利用RecyclerView來實現這些需求。

功能實現

  RecyclerView的使用相信大家都會的,但是這裡有個問題是怎樣讓RecyclerView迴圈滾動?再把問題細分一下,首先就是怎樣讓RecyclerView自己滾動,然後是怎樣實現裡面的內容迴圈。

動起來吧!RecyclerView

  怎樣讓RecyclerView自己滾動呢?通過查官方的Api,發現RecyclerView的LayoutManager中有這樣一個方法

利用RecyclerView實現無限輪播廣告條

這個方法的說明是

使用提供的SmoothScroller開始平滑滾動。

好了,現在我們知道了這個方法的作用是讓RecyclerView平滑滾動的,既然是讓RecyclerView平滑滾動,那麼我們肯定要告訴startSmoothScroll方法,RecyclerView怎樣滾動,如,滾動的方向、距離、速度等。上面的方法說明也說了根據提供的SmoothScroller滾動,因此這裡我們要實現SmoothScroller類來制定一些滾動的規則,檢視原始碼可以發現SmoothScroller是抽象類,而官方文件中說它的已知的直接實現類是LinearSmoothScroller,所以這裡直接例項化LinearSmoothScroller,重寫相應的方法即可。具體程式碼如下

 mSmoothScroller = new LinearSmoothScroller(this) {
            @Override
            protected int getVerticalSnapPreference() {
                return LinearSmoothScroller.SNAP_TO_START;
            }

            @Override
            protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
                return 3f / (displayMetrics.density);
            }
        };
複製程式碼

可以看到這裡重寫了兩個方法。getVerticalSnapPreference這個方法是制定對齊的規則,就是RecyclerView裡面的item頂部或底部與RecyclerView的對齊方式,這裡有三種對齊方式,以下是官方文件中對這三種對齊方式的具體說明

利用RecyclerView實現無限輪播廣告條

再看下calculateSpeedPerPixel這個方法,這個方法是用來計算滾動的速度的,返回值是滾動一個畫素花費的毫秒數。displayMetrics.density這個是1dp對應的畫素密度,就是1dp等於多少畫素。

注:SmoothScroller是將目標item滾動到RecyclerView中,即讓目標item在RecyclerView中可見。

  已經設定好滾動的規則了,下面要做的就是讓RecyclerView中的item滾動,並且迴圈滾動。

實現RecyclerView的迴圈滾動

  在實現迴圈滾動之前,看下實現滾動的程式碼,如下

 private void startAuto() {

        if (mAutoTask != null && !mAutoTask.isDisposed()) {
            mAutoTask.dispose();
        }
        mAutoTask = Observable.interval(1, 2, TimeUnit.SECONDS).observeOn(AndroidSchedulers.mainThread()).subscribe(new Consumer<Long>() {

            @Override
            public void accept(Long aLong) {
                mSmoothScroller.setTargetPosition(aLong.intValue());
                RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();
                if (layoutManager!=null)
                layoutManager.startSmoothScroll(mSmoothScroller);
            }
        });
    }
複製程式碼

從這段程式碼中可以看到,用RxJava中的interval方法實現了一個迴圈數字遞增定時器,時間間隔是2s。mSmoothScroller.setTargetPosition(aLong.intValue());這句程式碼就是設定哪個item出現在RecyclerView中。layoutManager.startSmoothScroll(mSmoothScroller);這句程式碼實際上呼叫的就是LinearSmoothScroller類中的start方法,這段程式碼實現的功能就是每隔兩秒,就讓設定的目標item平滑滾動到RecyclerView中。

  現在已經開始滾動了,那麼怎麼讓目標item重複出現呢?其實這很簡單,就是將itemCount設定成無限大,具體程式碼如下

@Override
    public int getItemCount() {
        return Integer.MAX_VALUE;
    }

@Override
    public void onBindViewHolder(@NonNull final AdViewHolder holder,  int position) {

        if (mDynamicAdsDetails.size() != 0) {
            String media1 = mDynamicAdsDetails.get(position % mDynamicAdsDetails.size());
            Picasso.get()
                    .load(media1)
                    .error(R.mipmap.ic_launcher)
                    .placeholder(R.mipmap.ic_launcher)
                    .into( holder.ivFlipperItem);

        }
        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //控制點選頻率
                if ((System.currentTimeMillis() - between) / 1000 < 1) {
                    return;
                }
                between = System.currentTimeMillis();
                Toast.makeText(mContext,"點選了第"+holder.getAdapterPosition() % mDynamicAdsDetails.size()+"個",Toast.LENGTH_SHORT).show();

            }
        });
    }
複製程式碼

注:將itemCount設定成無限大後,取列表中的值時,不能直接根據相應的position來取值了,應該這樣取mDynamicAdsDetails.get(position % mDynamicAdsDetails.size())

這樣就能實現迴圈滾動了。

開始爬坑

圖片錯亂問題

  這樣實現看起來顯然沒問題,但是專案跑起來後,卻發現圖片竟然不是迴圈顯示的,而是偶爾會一張圖片出現多次,然後才是下一張圖片。分析了一下原因,認為是RecyclerView的複用問題,圖片非同步請求的結果還沒有返回回來,複用了上次的控制元件,所以就出現一個圖片顯示多次的問題了。解決方法就是給ImageView設定Tag,具體程式碼如下

 if (mDynamicAdsDetails.size() != 0) {
            String media1 = mDynamicAdsDetails.get(position % mDynamicAdsDetails.size());
            holder.ivFlipperItem.setTag(media1);

            Picasso.get()
                    .load(media1)
                    .error(R.mipmap.ic_launcher)
                    .placeholder(R.mipmap.ic_launcher)
                    .into(new com.squareup.picasso.Target() {
                        @Override
                        public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
                            if (mDynamicAdsDetails.get(holder.getAdapterPosition() % mDynamicAdsDetails.size()).equals(holder.ivFlipperItem.getTag())) {
                                holder.ivFlipperItem.setScaleType(ImageView.ScaleType.FIT_XY);
                                holder.ivFlipperItem.setImageBitmap(bitmap);
                            }
                        }

                        @Override
                        public void onBitmapFailed(Exception e, Drawable errorDrawable) {

                        }

                        @Override
                        public void onPrepareLoad(Drawable placeHolderDrawable) {

                        }
                    });

        }
複製程式碼

滾動問題

  先看下滑動RecyclerView時出現的問題,如圖

利用RecyclerView實現無限輪播廣告條

可以發現,雖然可以滑動RecyclerView,但是滑動過後,item會回退回來,然後繼續上次的位置開始滾動。這個問題解決方法有兩種

  1. 記住手動滑動到的item的未知,然後在interval方法中把滑動的位置設定為目標位置。
  2. 禁止RecyclerView的滑動。

因為需求沒有可以滑動的這個功能,所以這裡採用方法2,禁止RecyclerView的滑動,詳細程式碼如下

public class AutoScrollRecyclerView extends RecyclerView {
    private int mState;
    private OnScrollListener mScrollListener;

    public AutoScrollRecyclerView(Context context) {
        this(context,null);
    }

    public AutoScrollRecyclerView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public AutoScrollRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

        @Override
    public boolean onTouchEvent(MotionEvent e) {
        switch (e.getAction()) {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_UP:
                return true;
            case MotionEvent.ACTION_MOVE:
                return false;
            case MotionEvent.ACTION_POINTER_UP:
                return false;
        }
        return true;
    }
}
複製程式碼

這裡重寫了RecyclerVieiew的onTouchEvent方法,當滑動式返回false,不消費滑動的動作。

但是,這麼做之後會有新的問題,就是當圖片在滾動時,我們點選圖片,圖片會暫停住,這裡採用的解決方法是監聽RecyclerView 的滾動狀態,只有當RecyclerView滑動停止時,才不攔截事件,否則就攔截事件。具體程式碼如下

public class AutoScrollRecyclerView extends RecyclerView {
    private int mState;
    private OnScrollListener mScrollListener;

    public AutoScrollRecyclerView(Context context) {
        this(context,null);
    }

    public AutoScrollRecyclerView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public AutoScrollRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        mScrollListener = new OnScrollListener() {
            @Override
            public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                mState = newState;
            }
        };
        //新增RecyclerView的滑動監聽
        addOnScrollListener(mScrollListener);
    }
    //判斷是否攔截事件
    @Override
    public boolean onInterceptTouchEvent(MotionEvent e) {
        return mState != 0;
    }

    @Override
    public boolean onTouchEvent(MotionEvent e) {
        switch (e.getAction()) {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_UP:
                return mState == 0;
            case MotionEvent.ACTION_MOVE:
                return false;
            case MotionEvent.ACTION_POINTER_UP:
                return false;
        }
        return true;
    }

}
複製程式碼

好了,這樣就解決了滑動RecyclerView出現的問題。

自定義廣告樣式

  廣告的樣式是可以自己定義的,不僅僅是圖片,還可以實現圖文混排等,只需要修改layout檔案即可,這裡為了方便就直接在layout中放了一張圖片。

結束語

  實現的功能挺簡單的,但是如果對RecyclerView滑動的方法不熟悉的話,實現起來還是有點難度的,還有就是我們在編寫程式碼的時候不僅要實現功能,還有注意對一些細節的處理,如果細節處理的不好,是很影響使用者體驗的。一些細節方面的問題是很考驗技能的,當然對技能提升的幫助也是很大的。

  最後,當然是放出原始碼了,點選這裡獲取原始碼

  轉載請註明出處:www.wizardev.cn

歡迎關注我的公眾號
掃碼關注公眾號,回覆“獲取資料”有驚喜

相關文章