編輯:關於Android編程
歡迎大家關注Android開源網絡框架NoHttp:https://github.com/yanzhenjie/NoHttp
我們在實際開發中,很多App都會在做一個廣告輪播器(可能是圖片,可能是其他View),很多同學都是使用別人封裝好的或者直接使用ViewPager自己來改,但是有人可能並不理解裡面的原理,或者有人遇到了手勢滑動沖突。我們今天就用150行代碼實現一自定義的廣告輪播器並不干擾原來View滑動事件。
本例代碼源碼及Demo傳送門
效果演示

需求分析、解決方案
輪播器最重要的幾個特點就是:自動滾動、手動滑動、滾動方向、每個Item顯示時間。因此我們設計提供以下幾個方法供外部調用:
/** * 設置每個Item的播放時間,默認3000毫秒 */ public void setShowTime(int showTimeMillis); /** * 設置滾動方向,默認向左滾動 */ public void setDirection(Direction direction); /** * 開始自動滾動 */ public void start(); /** * 停止自動滾動 */ public void stop(); /** * 播放上一個 */ public void previous(); /** * 播放下一個 */ public void next();
仔細看過上面方法的同學會發現,和我們的分析想比,還缺少一個手動滑動的方法,我們知道手勢滑動肯定要用到onTouch等方法,所以我們想到v4包下ViewPager自帶滑動效果,而且可以代碼控制跳轉到某個Item。因此我們自定義View繼承ViewPager不就完美解決了。
我們看到上面的方法中有一個Direction類,這個類不是系統的,是達哥自定義的方向控制枚舉,目前最常見的輪播方向無非就是左右輪播(上下本次先不討論),代碼如下:
public enum Direction {
/**
* 向左行動,播放的下一張應該是右邊的
*/
LEFT,
/**
* 向右行動,播放的下一張應該是左邊的
*/
RIGHT
}
自定義View如何實現以及原理
上面分析到需要繼承ViewPager,so我們先結合上面的幾個方法把完整的代碼撸起來:
public class AutoPlayViewPager extends ViewPager {
public AutoPlayViewPager(Context context) {
super(context);
}
public AutoPlayViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* 播放時間
*/
private int showTime = 3 * 1000;
/**
* 滾動方向
*/
private Direction direction = Direction.LEFT;
/**
* 設置播放時間,默認3秒
*
* @param showTimeMillis 毫秒
*/
public void setShowTime(int showTimeMillis) {
this.showTime = showTime;
}
/**
* 設置滾動方向,默認向左滾動
*
* @param direction 方向
*/
public void setDirection(Direction direction) {
this.direction = direction;
}
/**
* 開始
*/
public void start() {
// TODO 待實現開始播放
}
/**
* 停止
*/
public void stop() {
// TODO 待實現停止播放
}
/**
* 播放上一個
*/
public void previous() {
// TODO 待實現播放上一個
}
/**
* 播放下一個
*/
public void next() {
// TODO 待實現播放下一個
}
public enum Direction {
/**
* 向左行動,播放的應該是右邊的
*/
LEFT,
/**
* 向右行動,播放的應該是左邊的
*/
RIGHT
}
}
setShowTime(int showTimeMillis)方法和setDirection(Direction direction)就是記錄一下某一個狀態而已,現在已經簡單的實現了,重點就是開始播放和停止播放。
定時輪播的原理分析和實現
前方高能,各位看官小心褲子別掉了。現在我們使用ViewPager最主要的就是自動播放了,所以我們需要一個定時器去執行任務,but這樣子就得用到Handler,就有點小題大做了。這裡介紹View的兩個方法:
// 線程將被添加到消息隊列,這個線程將會在UI線程(主線程)運行。 public boolean post(Runnable action); // 線程將被添加到消息隊列,在指定的時間之後運行,這個線程將會在UI線程(主線程)運行。 public boolean postDelayed(Runnable action, long delayMillis);
看到第二個方法猶如醍醐灌頂啊有木有,既然是所有View都有的方法,ViewPager必須有啊,這樣不就可以在主線程定時發布一個任務了嗎?所以我們這裡寫一個Runnable來做這個定時任務。
/**
* 播放器
*/
private Runnable player = new Runnable() {
@Override
public void run() {
play(direction);
}
};
這裡看到有一個play(direction);的方法待我們去實現,我們暫且把這個方法先放一放,我們先來看怎麼利用這個private Runnable player完成開始播放和停止播放。
實現開始播放和停止播放
開始播放時我們為了讓當前顯示的圖片顯示showTime的時間,所以我們利用postDelayed(Runnable action, long delayMillis);方法在showTime時間之後觸發private Runnable player:
/**
* 開始
*/
public void start() {
postDelayed(player, showTime);
}
停止播放,我們把這個線程給取消了就行了,因此停止的方法的實現是:
/**
* 停止
*/
public void stop() {
removeCallbacks(player);
}
但是為了防止開發者不停的調用start方法造成卡死現象,我們在start中做個優化,每次star前先把之前private Runable player移除了,因此start方法完整的代碼是:
/**
* 開始
*/
public void start() {
stop();
postDelayed(player, showTime);
}
核心重點:如何根據方向播放上一張和下一張
本例的核心和重點來了,現在回過頭去實現剛才擱置的play(direction);方法,它要根據播放的方向來播放上一張和下一張,我們具體看代碼,尤其要細細品味注釋哦:
/**
* 執行播放
*
* @param direction 播放方向
*/
private synchronized void play(Direction direction) {
// 拿到ViewPager的適配器
PagerAdapter pagerAdapter = getAdapter();
if (pagerAdapter != null) {// 判空
// Item數量
int count = pagerAdapter.getCount();
// ViewPager現在顯示的第幾個?
int currentItem = getCurrentItem();
switch (direction) {
case LEFT:// 如是向左滾動的,currentItem+1播放下一個
currentItem++;
// 如果+到最後一個了,就到第一個
if (currentItem >= count)
currentItem = 0;
break;
case RIGHT:// 如是向右滾動的,currentItem-1播放上一個
currentItem--;
// 如果-到低一個了,就到最後一個
if (currentItem < 0)
currentItem = count - 1;
break;
}
setCurrentItem(currentItem);// 播放根據方向計算出來的position的item
}
// 這就是當可以循環播放的重點,每次播放完成後,再次開啟一個定時任務
start();
}
到這裡其實我們已經實現輪播啦,但是我們在使用的時候發現存在一種情況,當我們用手指剛滑完一張,緊接著第二張又出來了,不賣關子了,原因就是我們手指滑動的時候private Runnable player這個任務沒有停止,所以我們在手指滑動時停止player,手指松開的時候再次開啟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();
}
});
}
有些同學可能不知道為什麼不在構造方法中而要在onFinishInflate中addOnPageChangeListener,是因為這個方法會在view被加載完成後調用,所以我們在這裡做一些初始化的工作比較合理。
自定義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);
}
/**
* 播放時間
*/
private int showTime = 3 * 1000;
/**
* 滾動方向
*/
private Direction direction = Direction.LEFT;
/**
* 設置播放時間,默認3秒
*
* @param showTimeMillis 毫秒
*/
public void setShowTime(int showTimeMillis) {
this.showTime = showTime;
}
/**
* 設置滾動方向,默認向左滾動
*
* @param direction 方向
*/
public void setDirection(Direction direction) {
this.direction = direction;
}
/**
* 開始
*/
public void start() {
stop();
postDelayed(player, showTime);
}
/**
* 停止
*/
public void stop() {
removeCallbacks(player);
}
/**
* 播放上一個
*/
public void previous() {
if (direction == Direction.RIGHT) {
play(Direction.LEFT);
} else if (direction == Direction.LEFT) {
play(Direction.RIGHT);
}
}
/**
* 播放下一個
*/
public void next() {
play(direction);
}
/**
* 執行播放
*
* @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 {
/**
* 向左行動,播放的應該是右邊的
*/
LEFT,
/**
* 向右行動,播放的應該是左邊的
*/
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();
}
});
}
}
如何使用
其實我們沒有對ViewPager做什麼太大的干涉,所以我們使用和原生的沒有什麼區別。這裡有幾個注意的地方也寫出來,免得有人踩坑。
如果使用自定ViewPager
AutoPlayViewPager autoPlayViewPage = (AutoPlayViewPager) findViewById(R.id.view_pager); autoPlayViewPage.setAdapter(bannerAdapter); // 以下兩個方法不是必須的,因為有默認值 autoPlayViewPage.setDirection(AutoPlayViewPager.Direction.LEFT);// 設置播放方向 autoPlayViewPage.setCurrentItem(200); // 設置每個Item展示的時間 autoPlayViewPage.start(); // 開始輪播
如何優化Adapter
其實我們看到就是多調用了一個star方法,但是還不夠,我們在適配器中還要做一點點小事情。為了能讓輪播無限循環,所以我們在getCount中返回int的最大值:
@Override
public int getCount() {
return resIds == null ? 0 : Integer.MAX_VALUE;
}
resIds是我們的數據源。該了這個Count後,我們的instantiateItem()方法也要修改,不然會發生下標越界的異常,我們在拿到position後要做個處理:
@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
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持本站。
Android 安卓 VPN設置圖解 PPTP教程
第一步: 打開手機主菜單,選擇“設置”,然後選擇“無線和網絡”第二步:選擇“虛擬專用網設置&rd
利用UltimateAndroid快速開發(一):配置篇
UltimateAndroid快速開發框架教程(一):部署框架 為了方便大家更好的使用UltimateAndroid進行Android快速開發,特撰寫此教程。不當之處,還
Android下屏幕適配
適配:即當前應用在相同的手機上面顯示相同的效果。適配前需要首先確定當前手機所屬像素密度類型(如:xhdpi、hdpi、mdpi等),然後計算其像素密度,按一定比例給出界面
Android 中的小細節
1.EditView的自定義樣式其實這部分大家一定不陌生,通常默認的樣式都與我們的設計樣式有出入,那麼就需要我們自定義,通常我們使用Android:background=