通常Android的輪播圖(俗名:Banner)都是用ViewPager實現的,但是我在實際專案運用中碰到了一些小問題,於是決定另尋思路,採用RecyclerView這個更優雅更強大的控制元件來實現輪播的功能,順便複習下RecyclerView的相關知識。
實現
一般輪播圖就兩個重要的部分:可以無限左右滑動的圖片流和圖片位置的標示點,可能更簡單的連指示點都省略了。主要的難點還是在前者,因為一個輪播圖要播放的圖片一般也就十來張,不做任何處理直接塞到RecyclerView裡面,不僅稍微滑一下就沒了而且開始還不能先往左邊滑,所以我們需要在設定Adapter的總數時設定成一個比較大的數(可以是Integer.MAX_VALUE
),然後在設定完圖片資料後把RecyclerView的當前位置轉到中間的一個數(為了保證從第一張開始播放,必須是圖片總數的倍數,比如10000*size),這樣item回收複用的時候,我們只要取當前位置和圖片數量的餘數,得到真正的圖片位置。聽起來有點複雜,還是直接看下程式碼吧:
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() {
//如果只有一張圖片就不滑動了
return bannerSize < 2 ? 1 : Integer.MAX_VALUE;
}
}複製程式碼
至於自動滑動圖片,就用Handler不斷延遲傳送訊息就好了:
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;
}
});複製程式碼
好了,無限輪播解決了,接下來就是標示點了,既然無限輪播圖都用RecyclerView解決了,那麼標示點也用它來解決吧:
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;
}
}複製程式碼
其實Adapter也很簡單,設定一個當前位置的標識點,然後在圖片改變的時候notifyDataSetChanged()
就行了。
好了最後就剩下怎麼監聽RecyclerView的位置改變了(可沒有像Viewpager的addOnPageChangeListener那麼直接的方法),沒辦法直接分析RecyclerView.OnScrollListener
中的回撥方法吧:
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
//解決連續滑動時指示器不更新的問題
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) {
//連續滑動時可能不會回撥
int first = mLinearLayoutManager.findFirstVisibleItemPosition();
int last = mLinearLayoutManager.findLastVisibleItemPosition();
if (currentIndex != first && first == last) {
currentIndex = first;
refreshIndicator();
}
}
});複製程式碼
看看成果
好了解決了這些東西,再加些自定義View常用的屬性,回撥方法,設定的介面,輪播圖就做好了,我們來看看效果:
嗯,看著還不錯,可是怎麼有點怪?唉,這圖片怎麼滑動的這麼快,而且還能停在中間,這個不是我們想要的`標準`輪播圖。要解決這個問題就要用到RecyclerView的另一個功能:SnapHelper。SnapHelper旨在支援RecyclerView的對齊方式,也就是通過計算對齊RecyclerView中TargetView 的指定點或者容器中的任何畫素點。自定義一個SnapHelper挺麻煩的,還好android已經為我們內建好了兩個實現: LinearSnapHelper & PagerSnapHelper。其中PagerSnapHelper真是我們需要的可以把RecyclerView改的像Viewpager的工具。new PagerSnapHelper().attachToRecyclerView(mRecyclerView);
現在看著順暢多了:一次只能滑動一張圖片,停止的時候圖片的位置也對了。這樣一個基礎版的輪播圖就做成了。因為本質是一個RecyclerView,我們可以RecyclerView.Itemanimator,來做出更多的動畫效果(這個我目前就不太會了(T_T))。最後奉上github地址,裡面有更完整程式碼,封裝了很多自定義屬性,歡迎star!