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

PS:之前也寫過關於圖片輪播這一塊的博客.不過寫的很爛,而且不少狀況沒有考慮到(沒有支持無線輪播,和手勢點擊事件).所以這裏寫一篇補上.也是當時太年輕了.服務器

注:圖片請放大後再看.不然看不清楚.ide

 

學習內容:oop

1.自定義ViewPager佈局

2.圖片無限輪播因爲ViewPager的預加載機制所致使的問題.學習

 

  之前也寫過關於圖片輪播的相關博客,不過整體寫的很是的爛,而且不可以無線輪播,並且也沒法對手勢事件進行相關的處理,所以在這裏補上,也是屬於自定義View的一篇內容吧.而且經過這個過程發現了無限輪播因爲ViewPager的預加載機制所致使的問題.也正遇上項目要上一個新的版本,發現了這個bug.個人同事想到了一個比較不錯的方案解決了這個問題.整體仍是很不錯的.所以在這裏記錄一下.this

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

i.初始化輪播.線程

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

/**
   * 初始化輪播的線程
   */
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.圖片輪播時小圓點隨之變更orm

  有了一個子線程和一個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

相關文章
相關標籤/搜索