viewpager是一個常用的android元件,不過通常我們使用viewpager的時候不能實作左右無限循環滑動,在滑到邊界的時候會看到一個不能翻頁的動畫,可能影響使用者體驗。此外,某些區域性的viewpager(例如展示廣告或者公告之類的viewpager),可能需要自動輪播的效果,即使用者在不用滑動的情況下就能夠看到其他頁面的資訊。
為此我查閱了網絡上現有的一些關于實作這樣效果的例子,但都不是很滿意,經過反複實驗,在這裡總結并分享給大家,希望能有所幫助。
循環滑動效果的實作:pageradapter
我們知道viewpager自帶的滑動效果非常出色,是以我們基本不需要處理這個滑動,隻處理内容的顯示。而内容的顯示是由adapter控制的,是以這裡重點就是這個adapter了。為簡單起見,本例的每個view直接是一張圖檔。下面是adapter的代碼:
private class imageadapter extends pageradapter{
private arraylist<imageview> viewlist;
public imageadapter(arraylist<imageview> viewlist) {
this.viewlist = viewlist;
}
@override
public int getcount() {
//設定成最大,使使用者看不到邊界
return integer.max_value;
public boolean isviewfromobject(view arg0, object arg1) {
return arg0==arg1;
@override
public void destroyitem(viewgroup container, int position,
object object) {
//warning:不要在這裡調用removeview
}
public object instantiateitem(viewgroup container, int position) {
//對viewpager頁号求模取出view清單中要顯示的項
position %= viewlist.size();
if (position<0){
position = viewlist.size()+position;
}
imageview view = viewlist.get(position);
//如果view已經在之前添加到了一個父元件,則必須先remove,否則會抛出illegalstateexception。
viewparent vp =view.getparent();
if (vp!=null){
viewgroup parent = (viewgroup)vp;
parent.removeview(view);
container.addview(view);
//add listeners here if necessary
return view;
}
這裡有幾個地方需要注意:
getcount() 方法的傳回值:這個值直接關系到viewpager的“邊界”,是以當我們把它設定為integer.max_value之後,使用者基本就看不到這個邊界了(估計滑到這裡的時候電池已經挂了吧o_o)。當然,通常情況下設定為100倍實際内容個數也是可以的,之前看的某個實作就是這麼幹的。
instantiateitem() 方法position的處理:由于我們設定了count為 integer.max_value,是以這個position的取值範圍很大很大,但我們實際要顯示的内容肯定沒這麼多(往往隻有幾項),是以這裡肯定會有求模操作。但是,簡單的求模會出現問題:考慮使用者向左滑的情形,則position可能會出現負值。是以我們需要對負值再處理一次,使其落在正确的區間内。
instantiateitem() 方法父元件的處理:通常我們會直接addview,但這裡如果直接這樣寫,則會抛出illegalstateexception。假設一共有三個view,則當使用者滑到第四個的時候就會觸發這個異常,原因是我們試圖把一個有父元件的view添加到另一個元件。但是,如果直接寫成下面這樣:
(viewgroup)view.getparent().removeview(view);
則又會因為一開始的時候元件并沒有父元件而抛出nullpointerexception。是以,需要進行一次判斷。也就是上面的代碼。
destroyitem() 方法:由于我們在instantiateitem()方法中已經處理了remove的邏輯,是以這裡并不需要處理。實際上,實驗表明這裡如果加上了remove的調用,則會出現viewpager的内容為空的情況。
輪播效果的實作:使用handler進行更新
這裡我定義了一個handler來處理viewpager的輪播。所謂的“輪播”效果實作起來是這樣的:每隔一定時間(這裡是3秒)切換一次顯示的頁面。通過控制各頁面以一定順序循環播放,就達到了輪播的效果。為此,我們可以使用handler的sendemptymessagedelayed()方法來實作定時更新,并
注意使用者也可能會對帶有輪播效果的viewpager手動進行滑動操作,是以我認為使用者這時候是希望檢視指定頁面的,這時候應該取消輪播。下面是這個handler的實作:
private static class imagehandler extends handler{
/**
* 請求更新顯示的view。
*/
protected static final int msg_update_image = 1;
* 請求暫停輪播。
protected static final int msg_keep_silent = 2;
* 請求恢複輪播。
protected static final int msg_break_silent = 3;
* 記錄最新的頁号,當使用者手動滑動時需要記錄新頁号,否則會使輪播的頁面出錯。
* 例如目前如果在第一頁,本來準備播放的是第二頁,而這時候使用者滑動到了末頁,
* 則應該播放的是第一頁,如果繼續按照原來的第二頁播放,則邏輯上有問題。
protected static final int msg_page_changed = 4;
//輪播間隔時間
protected static final long msg_delay = 3000;
//使用弱引用避免handler洩露.這裡的泛型參數可以不是activity,也可以是fragment等
private weakreference<mainactivity> weakreference;
private int currentitem = 0;
protected imagehandler(weakreference<mainactivity> wk){
weakreference = wk;
public void handlemessage(message msg) {
super.handlemessage(msg);
log.d(log_tag, "receive message " + msg.what);
mainactivity activity = weakreference.get();
if (activity==null){
//activity已經回收,無需再處理ui了
return ;
}
//檢查消息隊列并移除未發送的消息,這主要是避免在複雜環境下消息出現重複等問題。
if (activity.handler.hasmessages(msg_update_image)){
activity.handler.removemessages(msg_update_image);
switch (msg.what) {
case msg_update_image:
currentitem++;
activity.viewpager.setcurrentitem(currentitem);
//準備下次播放
activity.handler.sendemptymessagedelayed(msg_update_image, msg_delay);
break;
case msg_keep_silent:
//隻要不發送消息就暫停了
case msg_break_silent:
case msg_page_changed:
//記錄目前的頁号,避免播放的時候頁面顯示不正确。
currentitem = msg.arg1;
default:
}
內建代碼:mainactivity
下面是mainactivity的代碼,主要是加載view和對viewpager進行初始化設定。因為代碼量比較少,重要的部分已經加了注釋,就不贅述了
public class mainactivity extends activity {
private static final string log_tag = "mainactivity";
private imagehandler handler = new imagehandler(new weakreference<mainactivity>(this));
private viewpager viewpager;
@override
protected void oncreate(bundle savedinstancestate) {
super.oncreate(savedinstancestate);
setcontentview(r.layout.activity_main);
//初始化iewpager的内容
viewpager = (viewpager) findviewbyid(r.id.main_viewpager);
layoutinflater inflater = layoutinflater.from(this);
imageview view1 = (imageview) inflater.inflate(r.layout.item, null);
imageview view2 = (imageview) inflater.inflate(r.layout.item, null);
imageview view3 = (imageview) inflater.inflate(r.layout.item, null);
view1.setimageresource(r.drawable.ics);
view2.setimageresource(r.drawable.jellybean);
view3.setimageresource(r.drawable.kitkat);
arraylist<imageview> views = new arraylist<imageview>();
views.add(view1);
views.add(view2);
views.add(view3);
viewpager.setadapter(new imageadapter(views));
viewpager.setonpagechangelistener(new viewpager.onpagechangelistener() {
//配合adapter的currentitem字段進行設定。
@override
public void onpageselected(int arg0) {
handler.sendmessage(message.obtain(handler, imagehandler.msg_page_changed, arg0, 0));
public void onpagescrolled(int arg0, float arg1, int arg2) {
//覆寫該方法實作輪播效果的暫停和恢複
public void onpagescrollstatechanged(int arg0) {
switch (arg0) {
case viewpager.scroll_state_dragging:
handler.sendemptymessage(imagehandler.msg_keep_silent);
break;
case viewpager.scroll_state_idle:
handler.sendemptymessagedelayed(imagehandler.msg_update_image, imagehandler.msg_delay);
default:
}
});
viewpager.setcurrentitem(integer.max_value/2);//預設在中間,使使用者看不到邊界
//開始輪播效果
handler.sendemptymessagedelayed(imagehandler.msg_update_image, imagehandler.msg_delay);
}//end of oncreate
}//e