開源界有一句很有名的話叫“不要重復(fù)發(fā)明輪子”,當(dāng)然,我今天的觀點(diǎn)不是要反駁這句話,輪子理論給我們的開發(fā)帶來了極大的便利,項(xiàng)目中要實(shí)現(xiàn)一些功能,便去網(wǎng)上找找,一般推薦使用一些有名的庫(kù),我本身也是這么做的,但我想說的是,既要會(huì)用輪子,也要知道輪子怎么造,必要的時(shí)候,自己也要造輪子(想要找到一個(gè)完全滿意的輪子還是不大容易的)。
由來
之前項(xiàng)目里面都是用的daimajia的AndroidImageSlider,一開始被驚艷的動(dòng)畫切換效果吸引了,還有各種自定義屬性動(dòng)畫啥的,感覺很棒,但隨著項(xiàng)目的進(jìn)展和時(shí)間的推移,我慢慢發(fā)現(xiàn)它也不是無所不能的,甚至我發(fā)現(xiàn)它只是好看,卻不怎么實(shí)用,我列舉一些我發(fā)現(xiàn)的問題:
- 庫(kù)中集成了Picasso,Picasso是極其耗內(nèi)存的,這點(diǎn)我在之前的一篇為什么圖片加載我首先Glide 提到過,當(dāng)一個(gè)頁(yè)面既有廣告又有列表(列表中也一般有圖片)會(huì)造成頁(yè)面嚴(yán)重卡頓。
- 動(dòng)畫效果太炫,實(shí)際項(xiàng)目中一個(gè)沒用到,一般用標(biāo)準(zhǔn)模式。
- 太多的依賴包,現(xiàn)在的許多項(xiàng)目都是基于android4.0以上版本開發(fā)的,nineoldandroid已經(jīng)不需要了
- 頁(yè)面點(diǎn)擊事件不直觀,如果有那種像ListView的OnItemClickListener就好了
總而言之,我感覺它現(xiàn)在已經(jīng)有些臃腫。當(dāng)然,我也在網(wǎng)上尋找更簡(jiǎn)潔實(shí)用的替代庫(kù),比如BGABanner-Android,但事實(shí)也是殘酷的,不支持網(wǎng)絡(luò)圖片,還有一個(gè)坑等著我跳,看代碼片段
if (mAutoPlayAble && views.size() < 3) {
throw new IllegalArgumentException("開啟指定輪播時(shí)至少有三個(gè)頁(yè)面");
}
低于3張圖片會(huì)直接拋異常,這叫我怎么用,需求也不是我能控制的。
當(dāng)然以上兩個(gè)庫(kù)的作者我都很喜歡,這兩個(gè)庫(kù)也非常不錯(cuò),不然也不值得我花時(shí)間研究。
我對(duì)輪子的要求
結(jié)合自己的理解,我認(rèn)為兩個(gè)庫(kù)中都有可取之處,也有不足之處,我就取長(zhǎng)補(bǔ)短,造自己的輪子,我對(duì)這個(gè)輪子的要求:
- 無限輪播
- 自動(dòng)加手動(dòng)滑動(dòng)
- 簡(jiǎn)單的自定義指示器樣式及位置
- 支持本地圖片及網(wǎng)絡(luò)圖片
- 滑動(dòng)流暢,無卡頓,無閃爍
- 廣告頁(yè)面不限制個(gè)數(shù)
- 頁(yè)面點(diǎn)擊監(jiān)聽事件
- 簡(jiǎn)單易用,高配置,無明顯bug
動(dòng)畫效果我先打算拋棄了,默認(rèn)的就好,以實(shí)用為主。
開始動(dòng)手,step by step
系統(tǒng)可以滑動(dòng)翻頁(yè)的控件就只有ViewPager和ViewFliper網(wǎng)上還有大神實(shí)現(xiàn)用RecyclerView實(shí)現(xiàn)了類似ViewPager的效果,這里暫不做過多研究,這里就選擇使用最多ViewPager作為滑動(dòng)翻頁(yè)控件,使用ViewPager+PagerAdapter可以很容易的實(shí)現(xiàn)翻頁(yè)切換效果,但存在幾個(gè)弊端:
- 不能無限輪回的翻頁(yè)(滑到第一個(gè)或者最后一個(gè)就不能繼續(xù)滑)
- 切換速度太快,系統(tǒng)默認(rèn)250毫秒,用做廣告欄切換會(huì)存在明顯閃爍
- 僅支持手動(dòng)滑動(dòng),不支持自動(dòng)切換
- 沒有提供直接的類似ListView的OnItemClickListener監(jiān)聽,使用起來很不方便
當(dāng)然還會(huì)有其他的坑,遇到了再解決,先解決上面的問題
無限輪播效果
這里采用網(wǎng)上通用的解決辦法,偽輪播(讓用戶看到輪播的假象,實(shí)際上用了很多頁(yè)面在不斷重復(fù)出現(xiàn),如果用戶滑動(dòng)幾十億下是可以滑到頭,實(shí)際幾乎不可能有人這么做)看具體實(shí)現(xiàn)代碼
public class LoopPagerAdapter extends PagerAdapter {
private List<View> views;
public LoopPagerAdapter(List<View> views) {
this.views = views;
}
@Override
public int getCount() {
//Integer.MAX_VALUE = 2147483647
return Integer.MAX_VALUE;
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
if (views.size() > 0) {
//position % view.size()是指虛擬的position會(huì)在[0,view.size())之間循環(huán)
View view = views.get(position % views.size());
if (container.equals(view.getParent())) {
container.removeView(view);
}
container.addView(view);
return view;
}
return null;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
}
}~~~
還需要做一件事情,就是設(shè)置當(dāng)前position為中間的一個(gè)較大的值,如果不設(shè)置或者設(shè)置的比較小,往左滑動(dòng)容易滑倒頭
pager.setCurrentItem(Integer.MAX_VALUE / 2 - Integer.MAX_VALUE / 2 % views.size());
####改變?cè)鶹iewPager切換速度
通過反射拿到ViewPager的滑動(dòng)器mScroller,改變duration參數(shù),看代碼:
public void setSliderTransformDuration(int duration) {
try {
Field mScroller = ViewPager.class.getDeclaredField("mScroller");
mScroller.setAccessible(true);
FixedSpeedScroller scroller = new FixedSpeedScroller(pager.getContext(), null, duration);
mScroller.set(pager, scroller);
} catch (Exception e) {
}
}
//FixedSpeedScroller.java
public class FixedSpeedScroller extends Scroller {
//默認(rèn)1秒,可以通過上面的方法控制
private int mDuration = 1000;
public FixedSpeedScroller(Context context) {
super(context);
}
public FixedSpeedScroller(Context context, Interpolator interpolator) {
super(context, interpolator);
}
public FixedSpeedScroller(Context context, Interpolator interpolator, int duration){
this(context,interpolator);
mDuration = duration;
}
@Override
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
// Ignore received duration, use fixed one instead
super.startScroll(startX, startY, dx, dy, mDuration);
}
@Override
public void startScroll(int startX, int startY, int dx, int dy) {
// Ignore received duration, use fixed one instead
super.startScroll(startX, startY, dx, dy, mDuration);
}
}
####自動(dòng)切換實(shí)現(xiàn)
這里可以有多種方式,使用Handler或者Timer都可以的,這里采用handler實(shí)現(xiàn),isAutoPlay可以控制是否禁止控件自動(dòng)輪播,autoPlayDuration是輪播間隔時(shí)間,還需注意觸摸時(shí)應(yīng)當(dāng)停止輪播,放開恢復(fù)正常
/**
* 開始自動(dòng)輪播
*/
public void startAutoPlay() {
if (isAutoPlay) {
handler.sendEmptyMessageDelayed(WHAT_AUTO_PLAY, autoPlayDuration);
}
}
/**
* 停止自動(dòng)輪播
*/
public void stopAutoPlay() {
if (isAutoPlay) {
handler.removeMessages(WHAT_AUTO_PLAY);
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
stopAutoPlay();
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
startAutoPlay();
break;
}
return super.dispatchTouchEvent(ev);
}
先附上一張預(yù)覽圖:

先寫這么多吧,后續(xù)完整代碼我會(huì)上傳到github,如果大家有興趣,我會(huì)抽時(shí)間寫剩下的內(nèi)容,這個(gè)控件的代碼也是借鑒了很多優(yōu)秀的開源庫(kù),并結(jié)合自己的理解寫的。
> 炫麗的效果固然吸引人眼球,平凡實(shí)用的東西才愈久彌香
完整代碼已上傳到github:[BannerLayoutDemo](https://github.com/dongjunkun/BannerLayoutDemo)
下一篇[自己造輪子--一款實(shí)用的Android廣告欄實(shí)現(xiàn)過程(二)](http://m.itdecent.cn/p/12d45f01e99e)