注:本文同步发布于微信公众号:stringwu的互联网杂谈 一种统计ListView滚动距离的方法

ListView做为Android中最常使用的列表控件,主要用来显示同一类的数据,如应用列表,商品列表等。ListView的详细使用与介绍可查阅官方文档ListView。这里不再展示叙述。

1 背景

ListView在屏幕上会固定一定长度,如果内容超过这个长度,一般是通过滑动来向下浏览更多的内容。此时有产品就想统计出用户在某一次浏览中是否有滑动,并且想实际量化该滑动距离。虽然觉得这个需求很扯淡,但做为开发的我还是老老实实去寻找实际的统计解决方案。但搜索了一圈并没有找到一个满足需求的解决方案。于是就有了此文。

2 方案

2.1 ListView滚动监听

ListView提供了一个setOnScrollListener的接口来接收List的滚动事件:

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的接口为:

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的高度来统计实现

  • 第一次进来时(收到滚动回调)时,记录下此时第一个可见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的高度来计算得到。这里核心是通过系统提供的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的实际滚动距离,并兼容了常见的边界统计的问题。是目前可直接运用于实际的生产环境的最优方案,没有之一,就是这么自信的。

一种统计ListView滚动距离的方法的更多相关文章

  1. Android 记录和恢复ListView滚动的位置的三种方法

    本文主要介绍记录和恢复listView滚动位置的3种方法(1)记录listView滚动到的位置的坐标(推荐)(2)记录listView显示在屏幕上的第一个item的位置(3)通知适配器数据改变. 有时 ...

  2. Android ListView 滚动的N种方法

    Android 里面让ListView滚动有N种方法,这儿列举三种: 我的需求是通过按键让Listview滚动起来,当然这些按键不是通过Android标识接口传输过来的,所以不能通过监听按键事件来实现 ...

  3. ListView组件中 onEndReached 方法在滚动到距离列表最底部一半时执行

    初次使用ListView,在写列表滚动到最底部自动加载使用到方法onEndReached, 发现: ListView组件中 onEndReached 方法在滚动到距离列表最底部一半时执行, 于是翻看文 ...

  4. Android项目:使用pulltorefresh开源项目扩展为下拉刷新上拉加载更多的处理方法,监听listview滚动方向

    很多android应用的下拉刷新都是使用的pulltorefresh这个开源项目,但是它的扩展性在下拉刷新同时又上拉加载更多时会有一定的局限性.查了很多地方,发现这个开源项目并不能很好的同时支持下拉刷 ...

  5. Android开发日常-listview滚动方法梳理

    listview滚动方法梳理 1.setSelection(position); 滚动到指定条目 2.setSelectionFromTop(position,y): 距离指定条目向下偏移y(像素) ...

  6. [ 原创 ]学习笔记-三种向ListView中填充简单文本的方法

    Android 中ListView是很重要的一块内容 掌握ListView的基本用法 对学习安卓起着举足轻重的作用 今天就介绍一下三种向ListView 填充简单文本的方法 填充其他数据类型的用法之后 ...

  7. android listview滚动到顶部

    1.为了实现类似IOS点击状态栏,列表回滚到顶部的功能(要平滑滚动效果),android上点击一个按钮或是图片什么的也可以让listview一次性滚动到顶部(滑动太多页时,一次可能滚不到顶部,使用ha ...

  8. “Win10 UAP 开发系列”之 在MVVM模式中控制ListView滚动位置

    这个扩展属性从WP8.1就开始用了,主要是为了解决MVVM模式中无法直接控制ListView滚动位置的问题.比如在VM中刷新了数据,需要将View中的ListView滚动到顶部,ListView只有一 ...

  9. [Android]ListView的Adapter.getView()方法中延迟加载图片的优化

    以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/4139998.html 举个例子吧,以好友列表为例 ListVi ...

随机推荐

  1. 用aop去解决事物问题(tx)记录学习之aop1.2

    上一个文章我们了解了什么事aop,以及aop的使用方法,主要是把自己想要加入的通知(advice)加入到我们的方法里, 比如上一章我们说的事把myadvice类中的before方法织入到userser ...

  2. Win10 第三方浏览器无法上网或者上网速度慢 的终极解决方案

    请参考: https://blog.csdn.net/shouzei123/article/details/82862478 近期,工程师收到大量反馈360浏览器,极速模式无法打开网页,兼容模式无此问 ...

  3. 17_Python的常用模块

    1.随机数模块 random 1.随机小数 import random # (0,1)随机取浮点数 random.random() # 0.17988578778011 # (1, 3)取指定范围的浮 ...

  4. JVM学习第三天(JVM的执行子系统)之字节码指令

    早上看了Class类文件结构,晚上继续来看字节码指令,毕竟谁也不是一步登天的(说白了还是穷); 字节码指令 Java虚拟机的指令由一个字节长度的.代表着某种特定操作含义的数字(称为操作码,Opcode ...

  5. 用安卓 WebView 做一个“套壳”应用

    前言 目前手机应用市场上的 APP 类型主要为以下两种: Native App(原生应用):直接针对平台(Android.iOS 等手机系统)进行开发,属于性能最优的方案,也是开发成本最大的方案. H ...

  6. ZT:如果有来生,要做一棵树

    出处:https://zhidao.baidu.com/question/393644025.html 原以为是三毛所作,想不到还有争议. 如果有来生,要做一棵树, 站成永恒.没有悲欢的姿势, 一半在 ...

  7. leetcode刷题-62不同路径2

    题目 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” ). 机器人每次只能向下或者向右移动一步.机器人试图达到网格的右下角(在下图中标记为“Finish”). 现在 ...

  8. Python中的type(),isinstance,()dir(),的区别

    1.type() type(),获取一个变量的类型,返回值为:<class '类名'>,属于class类型2.isinstance() isinstance(),判断一个对象是否属于某种数 ...

  9. java输出1-100之间的数并求和for+while+do while实现

    public static void main(String args[]) {//do while int sum = 0; //当前之和 int i = 1; //加数 do { if (i%2= ...

  10. 你准备好开始DevOps了吗?

    前面一章节我们已经了解了Agile,CI/CD,DevOps,作为DevOps的起点,对于一个团队,如何开始自己的持续集成?根据我的经验,列出了一下需要考虑的点 1. 代码管理/分支策略 代码托管在哪 ...