一種統計ListView滾動距離的方法

注:本文同步發佈於微信公衆號:stringwu的互聯網雜談 一種統計ListView滾動距離的方法java

ListView作爲Android中最常使用的列表控件,主要用來顯示同一類的數據,如應用列表,商品列表等。ListView的詳細使用與介紹可查閱官方文檔ListView。這裏再也不展現敘述。android

1 背景

ListView在屏幕上會固定必定長度,若是內容超過這個長度,通常是經過滑動來向下瀏覽更多的內容。此時有產品就想統計出用戶在某一次瀏覽中是否有滑動,而且想實際量化該滑動距離。雖然以爲這個需求很扯淡,但作爲開發的我仍是老老實實去尋找實際的統計解決方案。但搜索了一圈並無找到一個知足需求的解決方案。因而就有了此文。微信

2 方案

2.1 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來處理,在接收到滾動回調時進行自行處理。對象

2.2 統計方案

核心方案:經過第一個可見item的變化來統計判斷實際滑動的距離,離開時經過累加初始時可見item到離開時可見item的高度來統計實現blog

  • 第一次進來時(收到滾動回調)時,記錄下此時第一個可見item的index 爲 mInitPosition;
  • 每次收到滾動回調時,更新已滾動的第一個可見item的 index,並記錄下第一個item的最大的index 爲:mMaxPosition;
  • 每次收到滾動回調時,根據第一個item的變化,記錄下當前已滾動的最大距離;
  • 每次回調時,若是第一個item的最大index發生變化,則會累加上一個item的距離;
  • 離開時,經過 mMaxPosition 和 mInitPosition計算出當次滾動的最大距離;
//初次回調時
mInitPosition = getFirstItemPosition();
.....
//其餘回調時
mCurPosition = getFirstItemPosition();
mMaxPosition = Max(mCurPosition,mMaxPosition);
.....

整個統計方案須要解決如下幾個關鍵問題:接口

  • 滾動不超過一個item時的距離統計;
  • 進來時停留在某一個item時的滾動距離統計;
  • 快速滑動時的距離的統計;

2.2.1 滾動不超過一個item時的統計

由於咱們總體的方案是經過累加item的高度來判斷當前滾動了多少距離,大方案只能統計滾動恰好超過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;

2.2.2 進來時停留在某一個item時的滾動距離統計;

若是是從當前頁面A跳到其餘頁面B後,再跳轉回來,此時當前頁面A正常是停留在上一次瀏覽的位置(前提是頁面A未被回收掉),此時有多是停留在某個位置上的,如圖:
進來時滾動位置
此時向下滾動時,item1的滾動距離爲紅色部分,這部分的距離能夠怎樣計算獲得呢?在進入該頁面時,咱們經過該itemView的getTop方法拿到的初始值:mInitTop,該值的絕對值就爲橙色部分的高度。而 橙色部分高度 + 紅色部分高度 = 該item的實際高度,進而咱們能夠經過item的高度 - 橙色部分高度來獲得紅色部分的高度:

//進來時,記錄下該item的初始top
mInitTop = item1View.getTop();
.......
//item1的實際滾動距離scrollDistance
int scrollDistance = item1View.getHeight() + mInitTop;

2.2.3 快速滑動時的距離的統計

ListView在快速滑動時的滾動回調並不會每次都回調給註冊了滾動監聽的對象,有多是隔幾回纔會回調一次,這樣會致使咱們在收到滾動回調時時記錄的當前最大滾動距離不許?這裏有沒有辦法兼容快速滑動這種場景下的統計?筆者在實踐中採用了一種補償機制的方案:

  • 記錄下當前可見頁面的全部item的高度;
  • 每次更新最大滾動距離時,同步記錄下已更新到最大滾動距離的itemIndex;
  • 最終獲取最大滾動距離時,會判斷是否有漏掉item的高度,若是有漏掉item,則會記錄的全部item的高度進行一次補償;
//記錄下最大滾動距離裏記錄的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;
}

2.3 使用

實際使用時,咱們須要把自定義的ScrollListener設置給對應的ListView就能統計到具體的滾動距離:

ListView mList = findViewById(R.id.list_view);
mList.setOnScrollListener(new ScrollListener());

3 總結

本文從實際使用的場景出發,提出了一個可記錄ListView滾動距離的實際方案,該方案可精確統計各類場景下ListView的實際滾動距離,併兼容了常見的邊界統計的問題。是目前可直接運用於實際的生產環境的最優方案,沒有之一,就是這麼自信的。

相關文章
相關標籤/搜索