用什么來(lái)實(shí)現(xiàn)輪播:
在比較常見(jiàn)的主流控件里面煌寇,其實(shí) ViewPager 和 RecyclerView 已經(jīng)實(shí)現(xiàn)了類(lèi)似的功能淹朋,尤其是 ViewPager,可以說(shuō)是已經(jīng)實(shí)現(xiàn)了我們這個(gè)控件的大部分功能邻耕,所以如果我們基于 ViewPager 來(lái)進(jìn)行改造的話(huà)嗦哆,也能讓我們的輪播控件更加穩(wěn)定
那 ViewPager 跟我們需要的自動(dòng)輪播控件有多少差距呢,主要有兩個(gè):
1.不支持自動(dòng)播放
2.無(wú)法從最后一張滑動(dòng)到第一張
所以我們主要是針對(duì)這兩部分進(jìn)行相應(yīng)的改造闪彼,從而實(shí)現(xiàn)我們自己的自動(dòng)輪播控件甜孤。
1.1 實(shí)現(xiàn)自動(dòng)輪播功能
要想實(shí)現(xiàn)自動(dòng)輪播功能,我們最先想到的應(yīng)該是通過(guò) Timer 或者 ScheduledExecutorService 來(lái)實(shí)現(xiàn)計(jì)時(shí)器的功能畏腕,然后讓 ViewPager 通過(guò) serCurrentItem(int position) 方法缴川,將當(dāng)前的 Item 設(shè)置為下一個(gè) position 的數(shù)據(jù),但是如果通過(guò)定時(shí)器來(lái)實(shí)現(xiàn)的話(huà)描馅,會(huì)有一個(gè)問(wèn)題把夸,那就是我們?cè)谛枰?banner 進(jìn)行停止播放的時(shí)候就比較麻煩,所以通過(guò) Handler 用 sendMessage 的形式铭污,進(jìn)行事件的發(fā)送實(shí)現(xiàn) ViewPager 的自動(dòng)輪播恋日,以及某些場(chǎng)景的停止是比較合理的。
代碼實(shí)現(xiàn):
private static class BannerHander extends Handler {
private WeakReference<AutoScrollViewPager> 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岂膳,然后通過(guò)弱引用的形式防止內(nèi)存泄露,通過(guò)在 handlerMessage() 方法里面磅网,調(diào)用 setCurrentItem() 方法谈截,將當(dāng)前 ViewPager 的 Item 設(shè)置為對(duì)應(yīng)的 position + 1 的數(shù)據(jù),所以我們只要在外部調(diào)用 Handler 的 sendMessage() 方法涧偷,就能使 ViewPager 進(jìn)行自動(dòng)的無(wú)限輪播簸喂。
1.2 讓 ViewPager 從最后一張滑動(dòng)到第一張
我們知道,ViewPager 是無(wú)法從最后一頁(yè)滑動(dòng)到第一頁(yè)的燎潮,但我們可以換一個(gè)思路喻鳄,如果我們?cè)?ViewPager 的 Adapter 里面,通過(guò) getCount() 方法將 ViewPager 的大小設(shè)置為無(wú)限大跟啤,然后通過(guò)取余的方式來(lái)保證滑動(dòng)的頁(yè)面一直對(duì)應(yīng)數(shù)據(jù)源的那幾個(gè)數(shù)據(jù)诽表,這樣便能讓 ViewPager 實(shí)現(xiàn)從最后一張滑動(dòng)到第一張的效果。
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); // 通過(guò)取余的方式
imageView = new SimpleDraweeView(mContext);
((SimpleDraweeView) imageView).setImageURI(uri);
container.addView(imageView);
return imageView;
}
return null;
}
二隅肥、如何進(jìn)行優(yōu)化
在上面我們只是簡(jiǎn)單的實(shí)現(xiàn)了 ViewPager 的自動(dòng)輪播功能,但其實(shí)還有很多的細(xì)節(jié)需要我們進(jìn)行優(yōu)化袄简,例如:我們是通過(guò)將 ViewPager 的大小設(shè)置為無(wú)限大的方式腥放,來(lái)實(shí)現(xiàn)從最后一張滑動(dòng)到第一張的,但這時(shí)候如果不進(jìn)行緩存的話(huà)绿语,我們?cè)?Adapter 的 instantiateItem(ViewGroup container, final int position) 方法里面秃症,便需要返回很多新 new 出來(lái)的 View候址,這樣會(huì)造成不必要的內(nèi)存浪費(fèi),只有對(duì)這些細(xì)節(jié)進(jìn)行優(yōu)化种柑,才能讓我們的控件更加的好用岗仑,穩(wěn)定性和性能方面也會(huì)更加優(yōu)異。
private final ArrayList<View> 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 中取出已經(jīng)被緩存的 View荠雕,進(jìn)行重復(fù)利用
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 {
// 當(dāng)緩存集合有數(shù)據(jù)時(shí),進(jìn)行復(fù)用
imageView = (ImageView) mViewCaches.remove(0);
}
}
2.2 適當(dāng)?shù)耐V棺詣?dòng)輪播
當(dāng)我們觸摸 Banner 或者離開(kāi)當(dāng)前展示 Banner 的頁(yè)面時(shí)驶赏,如果 banner 還在不停的進(jìn)行無(wú)線輪播的話(huà)炸卑,會(huì)造成沒(méi)必要的性能損失,所以我們需要在觸摸 Banner 以及當(dāng)前的 Activity 為不可見(jiàn)狀態(tài)的時(shí)候煤傍,停止 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 在進(jìn)行自動(dòng)輪播的時(shí)候蚯姆,切換速度是特別快的五续,會(huì)給人一種很突兀的感覺(jué),而且 ViewPager 也沒(méi)有提供接口給我們對(duì) ViewPager 進(jìn)行切換速度的設(shè)置龄恋,所以我們需要通過(guò)反射的方式疙驾,使用 Scroller 來(lái)進(jìn)行切換速度的設(shè)置,從而讓我們的 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);
}
}
至此荆萤,我們的自動(dòng)輪播控件,無(wú)論是性能上還是穩(wěn)定性上都已經(jīng)很不錯(cuò)了铣卡。