注:本文同步發佈於微信公衆號:stringwu的互聯網雜談 一種統計ListView滾動距離的方法java
ListView
作爲Android
中最常使用的列表控件,主要用來顯示同一類的數據,如應用列表,商品列表等。ListView
的詳細使用與介紹可查閱官方文檔ListView。這裏再也不展現敘述。android
ListView
在屏幕上會固定必定長度,若是內容超過這個長度,通常是經過滑動來向下瀏覽更多的內容。此時有產品就想統計出用戶在某一次瀏覽中是否有滑動,而且想實際量化該滑動距離。雖然以爲這個需求很扯淡,但作爲開發的我仍是老老實實去尋找實際的統計解決方案。但搜索了一圈並無找到一個知足需求的解決方案。因而就有了此文。微信
ListView
提供了一個setOnScrollListener
的接口來接收List的滾動事件:this
public class AbsListView{ ..... /** * Set the listener that will receive notifications every time the list scrolls. * * @param l the scroll listener */ public void setOnScrollListener(OnScrollListener l) { mOnScrollListener = l; invokeOnItemScrollListener(); } }
其中,OnScrollListener
的接口爲:code
public class AbsListView{ public interface OnScrollListener { .... /** * Callback method to be invoked while the list view or grid view is being scrolled. If the * view is being scrolled, this method will be called before the next frame of the scroll is * rendered. In particular, it will be called before any calls to * {@link Adapter#getView(int, View, ViewGroup)}. * * @param view The view whose scroll state is being reported * * @param scrollState The current scroll state. One of * {@link #SCROLL_STATE_TOUCH_SCROLL} or {@link #SCROLL_STATE_IDLE}. */ public void onScrollStateChanged(AbsListView view, int scrollState); /** * Callback method to be invoked when the list or grid has been scrolled. This will be * called after the scroll has completed * @param view The view whose scroll state is being reported * @param firstVisibleItem the index of the first visible cell (ignore if * visibleItemCount == 0) * @param visibleItemCount the number of visible cells * @param totalItemCount the number of items in the list adapter */ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount); } }
從OnScrollListener
的回調方法onScroll
的參數裏咱們能夠看到,這裏並無實際滾動了多少距離的參數變量,若是想統計實際滾動的距離,則須要自定義一個ScrollListener
來處理,在接收到滾動回調時進行自行處理。對象
核心方案:經過第一個可見item的變化來統計判斷實際滑動的距離,離開時經過累加初始時可見item到離開時可見item的高度來統計實現blog
//初次回調時 mInitPosition = getFirstItemPosition(); ..... //其餘回調時 mCurPosition = getFirstItemPosition(); mMaxPosition = Max(mCurPosition,mMaxPosition); .....
整個統計方案須要解決如下幾個關鍵問題:接口
由於咱們總體的方案是經過累加item的高度來判斷當前滾動了多少距離,大方案只能統計滾動恰好超過item時滾動距離,但若是滾動未超過一個item時,其滾動距離則不能累加item的高度來處理,好比:
事件
實際滾動距離爲紅色部分,並無超過一個item的高度,此時應該怎樣統計該部分的距離呢?這確定沒有辦法直接經過item的高度來計算獲得。這裏核心是經過系統提供的View
的方法getTop
來拿到該View
最頂部距離其Parent
的距離:開發
/** * Top position of this view relative to its parent. * * @return The top of this view, in pixels. */ @ViewDebug.CapturedViewProperty public final int getTop() { return mTop; }
在該item第一次變成第一個可見item時,記錄下此時經過getTop
拿到的初始值:mInitTop
,在離開時,獲取當前停留的top
值:mCurTop
。在拿到這兩個階段的top
值時,咱們就能夠經過p這兩個值來計算出紅色部分的實際滾動距離:
//這裏你們能夠思考下爲何能夠經過減掉當前的top值就能獲取到當前實際滾動的距離的; int itemHeight = mInitTop - mCurTop;
若是是從當前頁面A跳到其餘頁面B後,再跳轉回來,此時當前頁面A正常是停留在上一次瀏覽的位置(前提是頁面A未被回收掉),此時有多是停留在某個位置上的,如圖:
此時向下滾動時,item1
的滾動距離爲紅色部分,這部分的距離能夠怎樣計算獲得呢?在進入該頁面時,咱們經過該itemView的getTop
方法拿到的初始值:mInitTop
,該值的絕對值就爲橙色部分的高度。而 橙色部分高度 + 紅色部分高度 = 該item的實際高度,進而咱們能夠經過item的高度 - 橙色部分高度來獲得紅色部分的高度:
//進來時,記錄下該item的初始top mInitTop = item1View.getTop(); ....... //item1的實際滾動距離scrollDistance int scrollDistance = item1View.getHeight() + mInitTop;
ListView
在快速滑動時的滾動回調並不會每次都回調給註冊了滾動監聽的對象,有多是隔幾回纔會回調一次,這樣會致使咱們在收到滾動回調時時記錄的當前最大滾動距離不許?這裏有沒有辦法兼容快速滑動這種場景下的統計?筆者在實踐中採用了一種補償機制的方案:
//記錄下最大滾動距離裏記錄的itemIndex; private List<Integer> mFistVisibleItem = new ArrayList<>(); //記錄下當前全部item的高度狀況 private SparseIntArray mItemHeight = new SparseIntArray();
最終獲取時會根據是否有漏掉記錄,根據記錄的mItemHeight
的值進行一個補償:
boolean isMissing(){ int count = mMaxPosition -mInitPosition; if (mFistVisibleItem.size() < count) { return true; } return false; }
實際使用時,咱們須要把自定義的ScrollListener
設置給對應的ListView
就能統計到具體的滾動距離:
ListView mList = findViewById(R.id.list_view); mList.setOnScrollListener(new ScrollListener());
本文從實際使用的場景出發,提出了一個可記錄ListView
滾動距離的實際方案,該方案可精確統計各類場景下ListView
的實際滾動距離,併兼容了常見的邊界統計的問題。是目前可直接運用於實際的生產環境的最優方案,沒有之一,就是這麼自信的。