天天看點

viewpager循環滾動和自動輪播的問題

viewpager是一個常用的android元件,不過通常我們使用viewpager的時候不能實作左右無限循環滑動,在滑到邊界的時候會看到一個不能翻頁的動畫,可能影響使用者體驗。此外,某些區域性的viewpager(例如展示廣告或者公告之類的viewpager),可能需要自動輪播的效果,即使用者在不用滑動的情況下就能夠看到其他頁面的資訊。

為此我查閱了網絡上現有的一些關于實作這樣效果的例子,但都不是很滿意,經過反複實驗,在這裡總結并分享給大家,希望能有所幫助。

循環滑動效果的實作:pageradapter

我們知道viewpager自帶的滑動效果非常出色,是以我們基本不需要處理這個滑動,隻處理内容的顯示。而内容的顯示是由adapter控制的,是以這裡重點就是這個adapter了。為簡單起見,本例的每個view直接是一張圖檔。下面是adapter的代碼:

viewpager循環滾動和自動輪播的問題

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添加到另一個元件。但是,如果直接寫成下面這樣:

viewpager循環滾動和自動輪播的問題

(viewgroup)view.getparent().removeview(view);  

則又會因為一開始的時候元件并沒有父元件而抛出nullpointerexception。是以,需要進行一次判斷。也就是上面的代碼。

destroyitem() 方法:由于我們在instantiateitem()方法中已經處理了remove的邏輯,是以這裡并不需要處理。實際上,實驗表明這裡如果加上了remove的調用,則會出現viewpager的内容為空的情況。

輪播效果的實作:使用handler進行更新

這裡我定義了一個handler來處理viewpager的輪播。所謂的“輪播”效果實作起來是這樣的:每隔一定時間(這裡是3秒)切換一次顯示的頁面。通過控制各頁面以一定順序循環播放,就達到了輪播的效果。為此,我們可以使用handler的sendemptymessagedelayed()方法來實作定時更新,并

注意使用者也可能會對帶有輪播效果的viewpager手動進行滑動操作,是以我認為使用者這時候是希望檢視指定頁面的,這時候應該取消輪播。下面是這個handler的實作:

viewpager循環滾動和自動輪播的問題

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進行初始化設定。因為代碼量比較少,重要的部分已經加了注釋,就不贅述了

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

繼續閱讀