一种统计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的实际滚动距离,并兼容了常见的边界统计的问题。是目前可直接运用于实际的生产环境的最优方案,没有之一,就是这么自信的。

相关文章
相关标签/搜索