手把手教你用ViewPager自定義實(shí)現(xiàn)Banner輪播
歡迎大家關(guān)注Android開源網(wǎng)絡(luò)框架NoHttp:https://github.com/yanzhenjie/NoHttp
我們在實(shí)際開發(fā)中,很多App都會(huì)在做一個(gè)廣告輪播器(可能是圖片,可能是其他View),很多同學(xué)都是使用別人封裝好的或者直接使用ViewPager自己來改,但是有人可能并不理解里面的原理,或者有人遇到了手勢滑動(dòng)沖突。我們今天就用150行代碼實(shí)現(xiàn)一自定義的廣告輪播器并不干擾原來View滑動(dòng)事件。
本例代碼源碼及Demo傳送門
效果演示
需求分析、解決方案
輪播器最重要的幾個(gè)特點(diǎn)就是:自動(dòng)滾動(dòng)、手動(dòng)滑動(dòng)、滾動(dòng)方向、每個(gè)Item顯示時(shí)間。因此我們設(shè)計(jì)提供以下幾個(gè)方法供外部調(diào)用:
/** * 設(shè)置每個(gè)Item的播放時(shí)間,默認(rèn)3000毫秒 */ public void setShowTime(int showTimeMillis); /** * 設(shè)置滾動(dòng)方向,默認(rèn)向左滾動(dòng) */ public void setDirection(Direction direction); /** * 開始自動(dòng)滾動(dòng) */ public void start(); /** * 停止自動(dòng)滾動(dòng) */ public void stop(); /** * 播放上一個(gè) */ public void previous(); /** * 播放下一個(gè) */ public void next();
仔細(xì)看過上面方法的同學(xué)會(huì)發(fā)現(xiàn),和我們的分析想比,還缺少一個(gè)手動(dòng)滑動(dòng)的方法,我們知道手勢滑動(dòng)肯定要用到onTouch等方法,所以我們想到v4包下ViewPager自帶滑動(dòng)效果,而且可以代碼控制跳轉(zhuǎn)到某個(gè)Item。因此我們自定義View繼承ViewPager不就完美解決了。
我們看到上面的方法中有一個(gè)Direction類,這個(gè)類不是系統(tǒng)的,是達(dá)哥自定義的方向控制枚舉,目前最常見的輪播方向無非就是左右輪播(上下本次先不討論),代碼如下:
public enum Direction { /** * 向左行動(dòng),播放的下一張應(yīng)該是右邊的 */ LEFT, /** * 向右行動(dòng),播放的下一張應(yīng)該是左邊的 */ RIGHT }
自定義View如何實(shí)現(xiàn)以及原理
上面分析到需要繼承ViewPager,so我們先結(jié)合上面的幾個(gè)方法把完整的代碼擼起來:
public class AutoPlayViewPager extends ViewPager { public AutoPlayViewPager(Context context) { super(context); } public AutoPlayViewPager(Context context, AttributeSet attrs) { super(context, attrs); } /** * 播放時(shí)間 */ private int showTime = 3 * 1000; /** * 滾動(dòng)方向 */ private Direction direction = Direction.LEFT; /** * 設(shè)置播放時(shí)間,默認(rèn)3秒 * * @param showTimeMillis 毫秒 */ public void setShowTime(int showTimeMillis) { this.showTime = showTime; } /** * 設(shè)置滾動(dòng)方向,默認(rèn)向左滾動(dòng) * * @param direction 方向 */ public void setDirection(Direction direction) { this.direction = direction; } /** * 開始 */ public void start() { // TODO 待實(shí)現(xiàn)開始播放 } /** * 停止 */ public void stop() { // TODO 待實(shí)現(xiàn)停止播放 } /** * 播放上一個(gè) */ public void previous() { // TODO 待實(shí)現(xiàn)播放上一個(gè) } /** * 播放下一個(gè) */ public void next() { // TODO 待實(shí)現(xiàn)播放下一個(gè) } public enum Direction { /** * 向左行動(dòng),播放的應(yīng)該是右邊的 */ LEFT, /** * 向右行動(dòng),播放的應(yīng)該是左邊的 */ RIGHT } }
setShowTime(int showTimeMillis)方法和setDirection(Direction direction)就是記錄一下某一個(gè)狀態(tài)而已,現(xiàn)在已經(jīng)簡單的實(shí)現(xiàn)了,重點(diǎn)就是開始播放和停止播放。
定時(shí)輪播的原理分析和實(shí)現(xiàn)
前方高能,各位看官小心褲子別掉了?,F(xiàn)在我們使用ViewPager最主要的就是自動(dòng)播放了,所以我們需要一個(gè)定時(shí)器去執(zhí)行任務(wù),but這樣子就得用到Handler,就有點(diǎn)小題大做了。這里介紹View的兩個(gè)方法:
// 線程將被添加到消息隊(duì)列,這個(gè)線程將會(huì)在UI線程(主線程)運(yùn)行。 public boolean post(Runnable action); // 線程將被添加到消息隊(duì)列,在指定的時(shí)間之后運(yùn)行,這個(gè)線程將會(huì)在UI線程(主線程)運(yùn)行。 public boolean postDelayed(Runnable action, long delayMillis);
看到第二個(gè)方法猶如醍醐灌頂啊有木有,既然是所有View都有的方法,ViewPager必須有啊,這樣不就可以在主線程定時(shí)發(fā)布一個(gè)任務(wù)了嗎?所以我們這里寫一個(gè)Runnable來做這個(gè)定時(shí)任務(wù)。
/** * 播放器 */ private Runnable player = new Runnable() { @Override public void run() { play(direction); } };
這里看到有一個(gè)play(direction);的方法待我們?nèi)?shí)現(xiàn),我們暫且把這個(gè)方法先放一放,我們先來看怎么利用這個(gè)private Runnable player完成開始播放和停止播放。
實(shí)現(xiàn)開始播放和停止播放
開始播放時(shí)我們?yōu)榱俗尞?dāng)前顯示的圖片顯示showTime的時(shí)間,所以我們利用postDelayed(Runnable action, long delayMillis);方法在showTime時(shí)間之后觸發(fā)private Runnable player:
/** * 開始 */ public void start() { postDelayed(player, showTime); }
停止播放,我們把這個(gè)線程給取消了就行了,因此停止的方法的實(shí)現(xiàn)是:
/** * 停止 */ public void stop() { removeCallbacks(player); }
但是為了防止開發(fā)者不停的調(diào)用start方法造成卡死現(xiàn)象,我們在start中做個(gè)優(yōu)化,每次star前先把之前private Runable player移除了,因此start方法完整的代碼是:
/** * 開始 */ public void start() { stop(); postDelayed(player, showTime); }
核心重點(diǎn):如何根據(jù)方向播放上一張和下一張
本例的核心和重點(diǎn)來了,現(xiàn)在回過頭去實(shí)現(xiàn)剛才擱置的play(direction);方法,它要根據(jù)播放的方向來播放上一張和下一張,我們具體看代碼,尤其要細(xì)細(xì)品味注釋哦:
/** * 執(zhí)行播放 * * @param direction 播放方向 */ private synchronized void play(Direction direction) { // 拿到ViewPager的適配器 PagerAdapter pagerAdapter = getAdapter(); if (pagerAdapter != null) {// 判空 // Item數(shù)量 int count = pagerAdapter.getCount(); // ViewPager現(xiàn)在顯示的第幾個(gè)? int currentItem = getCurrentItem(); switch (direction) { case LEFT:// 如是向左滾動(dòng)的,currentItem+1播放下一個(gè) currentItem++; // 如果+到最后一個(gè)了,就到第一個(gè) if (currentItem >= count) currentItem = 0; break; case RIGHT:// 如是向右滾動(dòng)的,currentItem-1播放上一個(gè) currentItem--; // 如果-到低一個(gè)了,就到最后一個(gè) if (currentItem < 0) currentItem = count - 1; break; } setCurrentItem(currentItem);// 播放根據(jù)方向計(jì)算出來的position的item } // 這就是當(dāng)可以循環(huán)播放的重點(diǎn),每次播放完成后,再次開啟一個(gè)定時(shí)任務(wù) start(); }
到這里其實(shí)我們已經(jīng)實(shí)現(xiàn)輪播啦,但是我們在使用的時(shí)候發(fā)現(xiàn)存在一種情況,當(dāng)我們用手指剛滑完一張,緊接著第二張又出來了,不賣關(guān)子了,原因就是我們手指滑動(dòng)的時(shí)候private Runnable player這個(gè)任務(wù)沒有停止,所以我們在手指滑動(dòng)時(shí)停止player,手指松開的時(shí)候再次開啟player:
@Override protected void onFinishInflate() { addOnPageChangeListener(new OnPageChangeListener() { @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } @Override public void onPageSelected(int position) { } @Override public void onPageScrollStateChanged(int state) { if (state == SCROLL_STATE_IDLE) start(); else if (state == SCROLL_STATE_DRAGGING) stop(); } }); }
有些同學(xué)可能不知道為什么不在構(gòu)造方法中而要在onFinishInflate中addOnPageChangeListener,是因?yàn)檫@個(gè)方法會(huì)在view被加載完成后調(diào)用,所以我們在這里做一些初始化的工作比較合理。
自定義ViewPager的完整代碼如下:
package com.yolanda.autoviewpager; import android.content.Context; import android.support.v4.view.PagerAdapter; import android.support.v4.view.ViewPager; import android.util.AttributeSet; /** * <p>用ViewPager自定義的廣告輪播器。</p>. * * @author Yolanda; */ public class AutoPlayViewPager extends ViewPager { public AutoPlayViewPager(Context context) { super(context); } public AutoPlayViewPager(Context context, AttributeSet attrs) { super(context, attrs); } /** * 播放時(shí)間 */ private int showTime = 3 * 1000; /** * 滾動(dòng)方向 */ private Direction direction = Direction.LEFT; /** * 設(shè)置播放時(shí)間,默認(rèn)3秒 * * @param showTimeMillis 毫秒 */ public void setShowTime(int showTimeMillis) { this.showTime = showTime; } /** * 設(shè)置滾動(dòng)方向,默認(rèn)向左滾動(dòng) * * @param direction 方向 */ public void setDirection(Direction direction) { this.direction = direction; } /** * 開始 */ public void start() { stop(); postDelayed(player, showTime); } /** * 停止 */ public void stop() { removeCallbacks(player); } /** * 播放上一個(gè) */ public void previous() { if (direction == Direction.RIGHT) { play(Direction.LEFT); } else if (direction == Direction.LEFT) { play(Direction.RIGHT); } } /** * 播放下一個(gè) */ public void next() { play(direction); } /** * 執(zhí)行播放 * * @param direction 播放方向 */ private synchronized void play(Direction direction) { PagerAdapter pagerAdapter = getAdapter(); if (pagerAdapter != null) { int count = pagerAdapter.getCount(); int currentItem = getCurrentItem(); switch (direction) { case LEFT: currentItem++; if (currentItem > count) currentItem = 0; break; case RIGHT: currentItem--; if (currentItem < 0) currentItem = count; break; } setCurrentItem(currentItem); } start(); } /** * 播放器 */ private Runnable player = new Runnable() { @Override public void run() { play(direction); } }; public enum Direction { /** * 向左行動(dòng),播放的應(yīng)該是右邊的 */ LEFT, /** * 向右行動(dòng),播放的應(yīng)該是左邊的 */ RIGHT } @Override protected void onFinishInflate() { super.onFinishInflate(); addOnPageChangeListener(new OnPageChangeListener() { @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } @Override public void onPageSelected(int position) { } @Override public void onPageScrollStateChanged(int state) { if (state == SCROLL_STATE_IDLE) start(); else if (state == SCROLL_STATE_DRAGGING) stop(); } }); } }
如何使用
其實(shí)我們沒有對ViewPager做什么太大的干涉,所以我們使用和原生的沒有什么區(qū)別。這里有幾個(gè)注意的地方也寫出來,免得有人踩坑。
如果使用自定ViewPager
AutoPlayViewPager autoPlayViewPage = (AutoPlayViewPager) findViewById(R.id.view_pager); autoPlayViewPage.setAdapter(bannerAdapter); // 以下兩個(gè)方法不是必須的,因?yàn)橛心J(rèn)值 autoPlayViewPage.setDirection(AutoPlayViewPager.Direction.LEFT);// 設(shè)置播放方向 autoPlayViewPage.setCurrentItem(200); // 設(shè)置每個(gè)Item展示的時(shí)間 autoPlayViewPage.start(); // 開始輪播
如何優(yōu)化Adapter
其實(shí)我們看到就是多調(diào)用了一個(gè)star方法,但是還不夠,我們在適配器中還要做一點(diǎn)點(diǎn)小事情。為了能讓輪播無限循環(huán),所以我們在getCount中返回int的最大值:
@Override public int getCount() { return resIds == null ? 0 : Integer.MAX_VALUE; }
resIds是我們的數(shù)據(jù)源。該了這個(gè)Count后,我們的instantiateItem()方法也要修改,不然會(huì)發(fā)生下標(biāo)越界的異常,我們在拿到position后要做個(gè)處理:
@Override public Object instantiateItem(ViewGroup container, int position) { position = position % resIds.size(); Object xxoo = resIds.get(position); ... }
本例代碼源碼:http://xiazai.jb51.net/201609/yuanma/AutoViewPager(jb51.net).rar
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android編程學(xué)習(xí)之異步加載圖片的方法
這篇文章主要介紹了Android編程學(xué)習(xí)之異步加載圖片的方法,以實(shí)例形式較為詳細(xì)的分析了Android異步加載圖片所涉及的頁面布局及功能實(shí)現(xiàn)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10Ubuntu中為Android實(shí)現(xiàn)Application Frameworks層增加硬件訪問服務(wù)
本文主要介紹Android實(shí)現(xiàn) Application Frameworks層增加硬件訪問服務(wù),這里對實(shí)現(xiàn)增加硬件訪問服務(wù)的功能做出了詳細(xì)的工作流程,并提供示例代碼,有需要的小伙伴參考下2016-08-08Android使用getIdentifier()獲取資源Id的方法
這篇文章主要介紹了Android使用getIdentifier()獲取資源Id的方法,涉及Android針對控件資源的相關(guān)操作技巧,需要的朋友可以參考下2016-08-08Android studio實(shí)現(xiàn)PopupWindow彈出框效果
這篇文章主要為大家詳細(xì)介紹了Android studio實(shí)現(xiàn)PopupWindow彈出框效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-10-10詳解Android使用Html.fromHtml需要注意的地方
本篇文章主要介紹了詳解Android使用Html.fromHtml需要注意的地方,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07