Android之自定義ViewPager實現圖片的無線輪播


PS:以前也寫過關於圖片輪播這一塊的博客.不過寫的很爛,並且很多情況沒有考慮到(沒有支持無線輪播,和手勢點擊事件).因此這里寫一篇補上.也是當時太年輕了.

注:圖片請放大后再看.否則看不清楚.

 

學習內容:

1.自定義ViewPager

2.圖片無限輪播由於ViewPager的預加載機制所導致的問題.

 

  以前也寫過關於圖片輪播的相關博客,不過總體寫的非常的爛,並且不能夠無線輪播,而且也無法對手勢事件進行相關的處理,因此在這里補上,也是屬於自定義View的一篇內容吧.並且通過這個過程發現了無限輪播由於ViewPager的預加載機制所導致的問題.也正趕上項目要上一個新的版本,發現了這個bug.我的同事想到了一個比較不錯的方案解決了這個問題.總體還是很不錯的.因此在這里記錄一下.

  總體實現無限輪播的思想,其實和網上大部分的思路都是相同的,設置一個Integer的最大值的一半,然后根據position和圖片的數量去計算,來實現向左向右無限滑動這個功能.總體不是特別的難.自定義ViewPager之后,把相關的圖片和跟隨圖片滑動時的小圓點傳遞到ViewPager當中,然后設置相關的滑動監聽,Adapter就可以完美的實現圖片的無限輪播了.

i.初始化輪播.

  初始化輪播需要線程和Handler配合來完成,設置一個無線循環的線程,讓這個線程按照一定的周期一直向Activity發送Message,Handler在接收到消息后會不斷的對消息進行處理.改變ViewPager當前顯示的CurrentItem就可以實現無限輪播了.

/**
   * 初始化輪播的線程
   */
public void initLoop() {

    new Thread(new Runnable() {
        @Override
        public void run() {
            while (true) {
                try {
                    Thread.sleep(3000);
                    if (!stopLoopTag) {
                        Message message = Message.obtain();
                        message.what = 10;
                        message.arg1 = getCurrentItem() + 1;
                        mHandler.sendMessage(message);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }).start();
}

/**
   * 處理輪播的Handler
   */
private Handler mHandler = new Handler(new Handler.Callback() {
    @Override
    public boolean handleMessage(Message msg) {
        if (msg.what == 10 && !stopLoopTag) {
            setCurrentItem(msg.arg1);
        } else {
            mHandler.removeMessages(10);
        }
        return true;
    }
});

ii.圖片輪播時小圓點隨之變動

  有了一個子線程和一個Handler就可以實現無線循環的一個過程,我們知道一般圖片輪播都需要伴隨小圓點的移動,小圓點一般是直接布局到ViewPager當中的,因為需要給用戶一種更好的體驗性,因此在圖片輪播的同時伴隨着小圓點也隨之變動.那么小圓點如何在ViewPager的Item改變的時候也隨之變動呢?這就需要addOnPageChangeListener()來實現了.

private OnPageChangeListener onPageChangeListener = new OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } @Override public void onPageSelected(int position) { /** * 改變小圓點的狀態 * 三種情況: * 1.在初始位置 * 2.向左滑動 * 3.向右滑動 * 不要放在上一個方法中,會有明顯的延遲現象出現 * lastPointPosition 上一個點的位置,初始化為0 * lastPointIndex 上一個點在集合中的位置 * currentPointIndex 當前的點在集合中的位置 * */ if (lastPointPosition == 0) { currentPointIndex = 0; lastPointIndex = 0; } else if (lastPointPosition < position) { if (currentPointIndex == getImages().size() - 1) { currentPointIndex = 0; } else { currentPointIndex += 1; } } else if (lastPointPosition > position) { if (currentPointIndex == 0) { currentPointIndex = getImages().size() - 1; } else { currentPointIndex -= 1; } } dots.get(lastPointIndex).setImageResource(R.drawable.dot_normal); dots.get(currentPointIndex).setImageResource(R.drawable.dot_focus); lastPointPosition = position; lastPointIndex = currentPointIndex; } @Override public void onPageScrollStateChanged(int state) { } };

  這里我們通過addOnPageChangeListener()綁定Page的改變監聽來改變小圓點隨着Page改變的同時隨之改變.圖片的改變需要在適配器里去設置,適配器我留到最后說,因為其中有很多的細節.這樣有了無限循環,原點移動,那么就需要說一下當我們手指停留在ViewPager的時候,如何使ViewPager停止播放.因為涉及到了手勢事件,因此就要重寫相關的方法.

iii.重寫手勢事件

/**
    * 手勢事件的重寫
    */
@Override
public boolean onTouchEvent(MotionEvent event) {

    switch (event.getAction()) {

        case MotionEvent.ACTION_DOWN:
            /**
              * 按下的時候停止輪播
              * */
            stopLoop();
            xDown = (int) event.getX();
            yDown = (int) event.getY();
            break;

        case MotionEvent.ACTION_MOVE:
            /**
              * 這里不用做任何處理,移動的時候ViewPager內的圖片會自動滑動
              * */
            break;

        case MotionEvent.ACTION_UP:

            /**
              * 記錄按下時間
              * */
            if (timeThread == null) {
                touchTime = new Date().getTime();
                timeTag = true;
                timeThread = new TimeThread();
                timeThread.start();
            } else {
                touchTime = new Date().getTime();
            }

            /**
              * 判斷是否是點擊事件
              * */
            int xUp = (int) event.getX();
            int yUp = (int) event.getY();
            if (Math.abs(xDown - xUp) < 20 && Math.abs(yDown - yUp) < 20) {

                if (onImageItemClickListener != null) {
                        onImageItemClickListener.onItemClick(currentPointIndex);
                }
            }
            break;
    }
    return super.onTouchEvent(event);
}

  手勢事件的重寫就非常的簡單了,當點擊的時候停止輪播,移動的時候我們不需要做任何的處理,在手指離開之后,我們需要計算離開的時間,離開的時間超過兩秒,就再次開啟輪播.關閉輪播只需要停止最開始的那個子線程,讓標記位StopLoopTag為true,再將消息隊列中的消息移除隊列.這樣Handler也接收不到消息了.這樣就停止了輪播的效果.計算手指按下和離開的時間也需要一個子線程去處理.

/**
   * 時間線程,用於記錄手指離開點擊ViewPager的時間
   * 如果離開的時間 >= 2000毫秒,那么ViewPager繼續輪播
   */
class TimeThread extends Thread {

    @Override
    public void run() {
        while (timeTag) {
            long currentTime = new Date().getTime();
            if (currentTime - touchTime >= 2000) {
                openLoop();
                timeTag = false;
                timeThread = null;
            }
        }
    }
}

iv.PagerAdapter.

  PagerAdapter也是最蛋疼的一塊.instantiateItem()方法里的那一大堆代碼是關鍵.很多人都用這樣的代碼來實現圖片的加載過程.

@Override
public Object instantiateItem(ViewGroup container, int position) {

        position %= images.size();
        if (position < 0) {
            position = position + images.size();
        }
        ImageView imageView = images.get(initPosition);
        ViewParent parent = imageView.getParent();
        if (parent != null) {
            ViewGroup viewGroup = (ViewGroup) parent;
            viewGroup.removeView(imageView);
        }
        container.addView(imageView);
        return imageView;
}
     

 其實這樣寫是有很大的問題的,因為這里沒有考慮到ViewPager的預加載機制的問題,這也是我們項目出現的一個bug,我們是從服務器上獲取相關的圖片數據,然后保存在集合當中,如果我們在將圖片加載到ViewPager中的時候,僅調用image.get(position),position為上面代碼計算出的position,這樣的話實際上會導致圖片錯位.雖然顯示的是5張圖片,但是實際上他們在ViewPager中顯示的順序是不對的.比如說我們在后台定義了這樣的順序, 1,2,3,4,5的順序發送給我們,但是如果我們按照上面的方法從集合中拿數據的時候,ViewPager顯示的不是1,2,3,4,5這樣的順序,這也是我們在項目中發現的問題.因為在點擊圖片的時候,是需要走不同的鏈接的,也正是這個原因我們發現了這個bug.因此這里做了很多的處理.

public class PictureAdapter extends PagerAdapter {


    private List<ImageView> images;
    
    /**
     * initPosition -1為初始化的位置,后面是當前的圖片索引位置
     * topPosition 記錄上一次初始化的索引位,用於計算上次的position和本次position的偏移量
     * 
     * */
    private int initPosition = -1;
    private int topPosition = -1;

    public PictureAdapter(List<ImageView> images) {
        this.images = images;
    }


    @Override
    public int getCount() {
        return Integer.MAX_VALUE;
    }

    @Override
    public boolean isViewFromObject(View view, Object object) {
        return view == object;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {

    }

    /**
     * 實例化Item
     */
    @Override
    public Object instantiateItem(ViewGroup container, int position) {

        if(images.size() <=1){
            ImageView imageView = images.get(topPosition);
            container.addView(imageView);
            return imageView;
        }else{
            /**
             * 初始化狀態
             * 向左滑動
             * 向右滑動
             * 由於ViewPager有預加載機制,默認加載一頁,因此在第一次初始化的時候,會調用三次這個方法.
             * (第一次: position=1073741823 第二次: position=1073741822 第三次: position=1073741824)
             *
             * 而后續,這個方法僅被執行一次,並且執行的是預加載下一頁的請求.
             * */
            Log.e("TAG","position="+position);
            if (initPosition == -1) {
                /**
                 * 初始化狀態
                 * topPosition 記錄第一次初始化的索引位.用於后續作比較判斷下次是向右滑動還是向左滑動
                 * initPosition 初始化圖片集合的索引值
                 * */
                topPosition = position;
                initPosition = 0;
            } else if (topPosition < position) {
                /**
                 * 向左滑動
                 * 得出偏移量后比較是否超過圖片集合的大小
                 * */
                int value = position - topPosition;
                initPosition += value;
                if (initPosition == images.size()) {
                    /**
                     * 滑動到了最后一頁
                     * */
                    initPosition = 0;
                } else if (initPosition > images.size()) {
                    /**
                     * 如果超出了圖片集合的大小,則 initPosition = 超過的數值
                     * */
                    initPosition = (initPosition - images.size());
                }
                topPosition = position;
            } else if (topPosition > position) {
                int value = topPosition - position;
                initPosition -= value;
                if (initPosition == -1) {
                    /**
                     * 滑動到了第一頁
                     * */
                    initPosition = images.size() - 1;
                } else if (initPosition < -1) {
                    /**
                     * 當計算后的值小於了集合大小,則用集合大小減去小於的這部分
                     * */
                    initPosition = (images.size() - (Math.abs(initPosition)));
                }
                topPosition = position;
            }
            Log.e("TAG","topPosition="+topPosition);
            Log.e("TAG","initPosition="+initPosition);
            /**
             * 只用這句話應該會出現問題
             * */
//        position %= images.size();
//        if (position < 0) {
//            position = position + images.size();
//        }
            ImageView imageView = images.get(initPosition);
            ViewParent parent = imageView.getParent();
            if (parent != null) {
                ViewGroup viewGroup = (ViewGroup) parent;
                viewGroup.removeView(imageView);
            }
            container.addView(imageView);
            return imageView;
        }
    }
} 

  這就是我們定義的PagerAdapter.里面做了很多的邏輯處理.一張圖解釋一下其中的原理.

  這張圖說明了一種的道理,上面代碼不難發現.會出現 (initPosition > images.size())和initPosition < -1這兩種情況.這種原因的導致就是因為position會由於這種預加載機制出現數值跳躍問題.這里大家可以去根據Log信息結合我說的原理,好好的研究一下.相信大家會研究明白的.

  最后就是點擊事件了.對Activity暴露接口,讓Activity去實現就可以了.

/**
     * 對Activity暴露接口
     */
    private OnImageItemClickListener onImageItemClickListener;

    public interface OnImageItemClickListener {
        void onItemClick(int itemPosition);
    }

    public void setOnImageItemClickListener(OnImageItemClickListener onImageItemClickListener) {
        this.onImageItemClickListener = onImageItemClickListener;
    }

  剩下的就是MainActivity了.這里就不粘貼代碼了.里面的內容比較的簡單.這樣就實現了圖片的無線輪播,並且支持點擊停止,松開繼續播放的功能,還有的圖片點擊事件.最后放一個源代碼:

  源代碼地址:http://pan.baidu.com/s/1dFqig17

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM