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了.這裏就不粘貼代碼了.裏面的內容比較的簡單.這樣就實現了圖片的無線輪播,而且支持點擊中止,鬆開繼續播放的功能,還有的圖片點擊事件.最後放一個源代碼: