ListView优化(二) 禁用ListView的fling

2024-02-14 05:18
文章标签 优化 禁用 listview fling

本文主要是介绍ListView优化(二) 禁用ListView的fling,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

问题解决-优化listView卡顿和如何禁用ListView的fling

前戏很长,转载请保留出处:http://blog.csdn.net/u012123160/article/details/47720257

问题产生

这算是刚到实习公司接触到的第一个任务。公司某一产品中某个界面的listView快速滑动会有卡顿的现象发生,我的任务就是解决它。

产生原因分析

我一开始的想法比较简单,可能是listview的优化没有做到位,例如convertView的复用、viewHolder的使用等等基础的优化措施,然并卵。好长时间后终于找到了问题发生的相关代码...经过在可疑语句上(onTouchEvent方法中的几个case、onScrollStateChanged方法中的SCROLLSTATEXXX状态下)打印log,发现如下几个问题和卡顿现象有关系:

  1. 在每天固定的一个时间段内,listView展示的数据室实时刷新的,也就是服务器会定时向客户端发送数据刷新的信息,客户端再向服务器发出数据请求,客户端主要属于被动刷新;其他时间段只是单由客户端想服务区主动发数据请求。
  2. listView展示的数据是分页请求的,根据滚动位置来动态请求某一段数据。
  3. 根据打印的log,在执行到onScrollStateChanged方法中,也就是说当前listView的状态时滚动状态(包括fling)时,中间可能会插入数据请求的log。
  4. 经过请教导师,问题原因缩小到了数据请求、数据解析更新界面导致的卡顿。

于是再看代码,发现原因在于接受数据、解析数据、更新listView的Adapter内容的代码,没有和listView的滑动状态相关联,即滑动过程中可能就接收到了新数据并刷新了界面,造成卡顿。

问题初解思路

主要是滑动和数据接收更新界面之间的冲突,二者通过一个boolean变量关联一下即可。即只有在scrollState为IDLE时,才允许进行数据接收和界面刷新(调用notifyAdapterDataChanged方法),其他状态(FLING,TOUCH_SCROLL)都不允许。 而在服务器活跃的时间段之外,原本的代码中也是在滑动结束后才主动发起数据请求的,所以这方面不用考虑。

问题再发生——数据丢失

但是组长提出,可能会发生这样的问题,在滑动过程中数据不接收,会发生数据丢失的问题,这个应用本身对数据的实时性很高,所以这个也需要解决。

问题再解

测试了一下发现,卡顿的原因主要在于界面的刷新而不是数据接收,于是将控制范围缩小到了:只有在不滑动的状态下才允许更新界面,而数据接收在滑动过程中也可执行。保留最新接收到的数据即可。在滑动结束后及时判断有无最新的数据缓存。

问题再再提出——disable fling

组长的老板感觉还是卡顿...这次问清楚了,竟然觉得界面上滑块的移动有一顿一顿很难受...我和另一个前辈对此真是相当无语了。最后没办法了,组长说那就把这个fling的功能给禁止了吧...

fling问题研究

百度搜索关键词“android listView fling”,出现的第一条就是下面这个,【Andorid X 项目笔记】禁用ListView的Fling功能(1),可是也是然并卵...

/** 手势识别类 */
private class TouchGesture extends SimpleOnGestureListener {/** 快速滚动 */@Overridepublic boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {return true;}}private OnTouchListener mOnListViewTouchListener = new OnTouchListener() {@Overridepublic boolean onTouch(View v, MotionEvent event) {if (mTouchGesture.onTouchEvent(event))return true;return false;}
};

代码中mTouchGesture根本没有onTouchEvent(event)这个方法啊啊啊啊啊啊...一开始我是很开心的.... 然后就是各种百度,各种google,各种“静态流”(stackOverFlow的中文备案名称,我也是醉了),都没有个能用的解决方案。 其中可以使用但是不能达到要求的一段代码我觉得不错,留下链接-Android listview垂直滑动指定距离,利用反射的原理,从AbsListView中找到一个相对来说比较通用的方法boolean trackMotionScroll(int deltaY, int incrementalDeltaY)来达到控制滑动距离的目的。以后可能用得上。

禁止fling思路之一——切断事件链

整整困扰了我三天,既然不能从正面刚,那从侧面试试看吧。于是开始查询事件分发机制、fling的实现方式、fling的触发条件、scroll的相关内容...其中比较有用的是

  1. fling的触发是一系列的ACTIONXXX事件结合的结果,用户触摸屏幕后快速滑动,出现一个ACTIONDOWN,多个ACTIONMOVE和一个ACTIONUP相结合。链接-Android: 触屏fling/scroll/drag的区别及其详细过程
  2. 触摸事件分发就比较复杂了,主要涉及到三个重要的方法,分别是dispatchTouchEvent涉及事件分发,onInterceptTouchEvent涉及事件拦截和onTouchEvent涉及事件处理。遇到几次和这个相关的问题,每次都得查看相关的知识点...总是记不住。链接-Android 触摸事件分发ViewGroup&View多看几遍就看懂了。
  3. VelocityTracker这个类是用来测量滑动速度的。链接-手势事件:滑动动速度跟踪类VelocityTracker介绍
  4. AbsListView中,滑动达到了一定的速度,就会触发fling,而在AbsListView的源码中是由一个runnable的自定义类来完成的,太复杂所以没怎么看,但是有空了是一定要仔细研究的。

那么从上面这几点可以总结出一个结论,就是fling是由touch事件来控制的,而touch事件是由view&viewGroup来进行分发、拦截、相应的。今天早上起床的一个念头就是解决问题的方法:

拦截掉touch事件来从源头上打破触发fling的事件链,从而达到禁止fling的目的

说干就干,到了公司,找到问题发生的listView,找到容纳该listView的viewGroup(是一个LinearLayout的子类),将那三个touch函数写下了,打印log分析,从哪里拦截,拦截哪个事件比较好。最终决定在dispatchTouchEvent中拦截,在ACTION_UP中判断当前滑动在y(纵轴)方向上的速度,当速度达到某一个阀值后,return false,也就是将可以出发fling的事件链中最后一个事件给消灭掉,不传给子view。试了一下,果然好使!

总结

对公司项目代码还是不熟,大部分事件都花在定位问题代码上面了。再加上解决问题的积累还是不够,不能从根本上考虑,处处碰壁之后才想到从源头上来解决。其实我感觉解决问题的根本还是看一个人的知识积累和知识整合的能力,“熟读唐诗三百首”就是这个道理。

额外补充

  • 链接- Android View的onTouchEvent和OnTouch区别onTouch方法和onTouchEvent方法还是不同的,前者是OnTouchListener接口中的方法,而后者是view中的方法,而且前者比后者的优先级要高。

8月18日补充

提交代码后,发现效果还是不太理想。由于在事件分发的时候截断了事件链,导致在手指滑动屏幕过程中,速度一旦过快,屏幕就会停止响应,卡顿效果甚至比之前还要严重。而原本预期的效果应该是这样:

低速滑动可以触发fling。

高速滑动,可以滑动但是fling效果不明显,即有一种手指离开立即停止的效果。

无奈之下又返回头来看源码,有之前查找的资料做铺垫,在AbsListView这个类中寻找和fling相关的代码块,仔细搜索之下,有所发现。

改进过程——利用反射来从源码的角度解决fling

AbsListView类中有如下成员变量,和fling有关。

  • private FlingRunnable mFlingRunnable;这是一个Runnable类型的对象,其内是用于对ListView执行fling的运动,包括其开始、运动、遇到边界、结束等等操作。
  • private int mMinimumVelocity;触发fling的最低速度,其赋值语句为mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();,而在configuration这个类中,相关的值被定义为50,即每秒在屏幕上运动50dip单位。
  • private int mMaximumVelocity;和fling的最高速度、运动距离相关,赋值和上句类似,值为8000。velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity),前一个参数表示速率的基本时间单位,单位为毫秒,在此即为1秒,第二个参数表示速率超过mMaximumVelocity的都按照mMaximumVelocity的值来计算。
  • private float mVelocityScale = 1.0f;与fling的最远距离相关,最远距离的计算公式为velocityTracker.getYVelocity(mActivePointerId) * mVelocityScale,就是实际速度与mVelocityScale的乘积。

那么知道了上面的一些属性之后,就可以在代码中动态的修改这些值,来实际的控制fling的触发速度、fling的最远移动距离等效果了。但是注意一点,这些熟悉都是私有属性,所以还需要通过反射来获取AbsListView这个抽象父类进行修改。下面上相关代码。

public void initFlingArgument(ListView listView,int maxVelocity,int minVelocity,double scale){if(listView == null){return null;}/*获取父类,如果是所要修改的类是ListView的子类的话,再获取一次父类即可*/Class cls = listView.getSuperclass();if(cls == AbsListView.class){try{//fling的最小响应速度Field f_min = AbsListView.class.getDeclaredField("mMinimumVelocity");//fling最大响应速度Field f_max = AbsListView.class.getDeclaredField("mMaximumVelocity");//fling scaleField f_scale = AbsListView.class.getDeclaredField("mVelocityScale");//设置属性可达f_min.setAccessible(true);f_max.setAccessible(true);f_scale.setAccessible(true);//设置具体值f_max.set(listView,maxVelocity);f_min.set(listView,minVelocity);f_scale.set(listView,scale);}catch(NoSuchFieldException e){e.printStackTrace();}catch(IllegalArgumentException e){e.printStackTrace();}catch(IllegalAccessException e){e.printStackTrace();}}//end if
}//end method

总结

这下应该差不多了吧...

8月20日补充

仍然不符合要求,在真机上测试,流畅程度仍然不尽人意。昨天做完导师分配的另一个任务后,接着研究这个。

新的要求

有可能是前期没有把需求搞明白,之后提出的要求变成了这样:低速滑动,有fling的效果,高速滑动手指,listView仍然可以滚动一段距离,然后停止,注意是停止,不是逐渐停止(也就是说停止fling)。

我的思考

相比于之前的实现,新的要求在于滚动一段距离后立即停止,不允许有减速停止的效果。那么解决这个问题的关键还是关于fling的实现上,或者另一个办法就是直接不使用fling,在快速滑动,手指离开屏幕后(触发ACTION_UP),用代码操作ListView进行滚动。

  • 思路1:在源码中找找有没有控制fling时间的参数或者方法。
  • 思路2:在竖直速度超过预定的阀值后,断掉fling的事件链(为什么不用上一次的方法来让mVelocityScale这个属性值为0呢,我的考虑是滑动是一个不断触发的过程,被调用的频率非常高,而反射的效率很低,所以会影响界面的响应,或者其他的副作用,不如直接断开事件链效率高),然后调用ListView的smoothScrollXXXX()系列的方法来进行自主控制的滚动。
  • 思路3:之前在网上看过一篇关于Scroller的介绍和基本运用,就想着能不能用Scroller的fling或者startScroll方法,链接- android scroller overscroller用法。

解决途径

实际动手开始写代码,发现和想象的根本不是一回事。之前想的被一一排除。

  • 排除思路1:源码中控制fling的是一个Runnable类,并没有fling时间的相关设置或者参数,是由start(int)方法和endFling()方法来分别启动和结束fling的。
  • 排除思路2:并没有什么卵用,可能是用的方法不对,但是没有试出结果来...不过有个方法叫setSelection(int)可以直接把可视范围确定在给定序号的item上,但是这没有滚动效果...
  • 排除思路3:没有仔细看博客说明...scroller控制的是view的内容,并不是view本身,导致我参数设置,一调用方法,listView的内容就不见了...和滚动是两个概念。

最后解决:既然之前能用反射把fling的参数设置了,那么也可以调用AbsListView里面的FlingRunnable对象的endFling()方法,再延时个几百毫秒的调用一下,停止掉fling。相关代码如下:

private Field mFlingEndField = null;
private Method mFlingEndMethod = null;
private Handler mStopFlingHandler = new Handler();
//放于初始化方法中被调用
public void stopFlingInit(){try{mFlingEndField = AbsListView.class.getDeclaredField("mFlingRunnable");mFlingEndField.setAccessible(true);mFlingEndMethod = mFlingEndField.getType().getDeclaredMethod("endFling");mFlingEndMethod.setAccessible(true);}catch(NoSuchFieldException e){e.printStackTrace();}catch(NoSuchMethodException e){e.printStackTrace();}catch(IllegalArgumentException e){e.printStackTrace();}catch(IllegalAccessException e){e.printStackTrace();}
}
//在需要停止fling的地方调用
public void stopFling(ListView listView){if(mFlingEndMethod != null){try{mFlingEndMethod.invoke(mFlingEndField.get(listView));}cache(InvocationTargetException e){e..printStackTrace();}}
}
ListView listView = this;/*这些我都是写在自定义的ListView内部的*/Runnable mStopFilingRunnable = new Runnable(){public void run(){stopFling(listView);}
}//使用方法,300毫秒后停止fling
mStopFlingHandler.postDelayed(mStopFilingRunnable,300);

结束

虚拟机上达到要求,但是真机上是不是还是然并卵...这就不知道了。但我有种预感,这事没完。

这篇关于ListView优化(二) 禁用ListView的fling的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/707586

相关文章

正则表达式高级应用与性能优化记录

《正则表达式高级应用与性能优化记录》本文介绍了正则表达式的高级应用和性能优化技巧,包括文本拆分、合并、XML/HTML解析、数据分析、以及性能优化方法,通过这些技巧,可以更高效地利用正则表达式进行复杂... 目录第6章:正则表达式的高级应用6.1 模式匹配与文本处理6.1.1 文本拆分6.1.2 文本合并6

Vue3 的 shallowRef 和 shallowReactive:优化性能

大家对 Vue3 的 ref 和 reactive 都很熟悉,那么对 shallowRef 和 shallowReactive 是否了解呢? 在编程和数据结构中,“shallow”(浅层)通常指对数据结构的最外层进行操作,而不递归地处理其内部或嵌套的数据。这种处理方式关注的是数据结构的第一层属性或元素,而忽略更深层次的嵌套内容。 1. 浅层与深层的对比 1.1 浅层(Shallow) 定义

HDFS—存储优化(纠删码)

纠删码原理 HDFS 默认情况下,一个文件有3个副本,这样提高了数据的可靠性,但也带来了2倍的冗余开销。 Hadoop3.x 引入了纠删码,采用计算的方式,可以节省约50%左右的存储空间。 此种方式节约了空间,但是会增加 cpu 的计算。 纠删码策略是给具体一个路径设置。所有往此路径下存储的文件,都会执行此策略。 默认只开启对 RS-6-3-1024k

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

MySQL高性能优化规范

前言:      笔者最近上班途中突然想丰富下自己的数据库优化技能。于是在查阅了多篇文章后,总结出了这篇! 数据库命令规范 所有数据库对象名称必须使用小写字母并用下划线分割 所有数据库对象名称禁止使用mysql保留关键字(如果表名中包含关键字查询时,需要将其用单引号括起来) 数据库对象的命名要能做到见名识意,并且最后不要超过32个字符 临时库表必须以tmp_为前缀并以日期为后缀,备份

SWAP作物生长模型安装教程、数据制备、敏感性分析、气候变化影响、R模型敏感性分析与贝叶斯优化、Fortran源代码分析、气候数据降尺度与变化影响分析

查看原文>>>全流程SWAP农业模型数据制备、敏感性分析及气候变化影响实践技术应用 SWAP模型是由荷兰瓦赫宁根大学开发的先进农作物模型,它综合考虑了土壤-水分-大气以及植被间的相互作用;是一种描述作物生长过程的一种机理性作物生长模型。它不但运用Richard方程,使其能够精确的模拟土壤中水分的运动,而且耦合了WOFOST作物模型使作物的生长描述更为科学。 本文让更多的科研人员和农业工作者

从状态管理到性能优化:全面解析 Android Compose

文章目录 引言一、Android Compose基本概念1.1 什么是Android Compose?1.2 Compose的优势1.3 如何在项目中使用Compose 二、Compose中的状态管理2.1 状态管理的重要性2.2 Compose中的状态和数据流2.3 使用State和MutableState处理状态2.4 通过ViewModel进行状态管理 三、Compose中的列表和滚动

构建高性能WEB之HTTP首部优化

0x00 前言 在讨论浏览器优化之前,首先我们先分析下从客户端发起一个HTTP请求到用户接收到响应之间,都发生了什么?知己知彼,才能百战不殆。这也是作为一个WEB开发者,为什么一定要深入学习TCP/IP等网络知识。 0x01 到底发生什么了? 当用户发起一个HTTP请求时,首先客户端将与服务端之间建立TCP连接,成功建立连接后,服务端将对请求进行处理,并对客户端做出响应,响应内容一般包括响应

DAY16:什么是慢查询,导致的原因,优化方法 | undo log、redo log、binlog的用处 | MySQL有哪些锁

目录 什么是慢查询,导致的原因,优化方法 undo log、redo log、binlog的用处  MySQL有哪些锁   什么是慢查询,导致的原因,优化方法 数据库查询的执行时间超过指定的超时时间时,就被称为慢查询。 导致的原因: 查询语句比较复杂:查询涉及多个表,包含复杂的连接和子查询,可能导致执行时间较长。查询数据量大:当查询的数据量庞大时,即使查询本身并不复杂,也可能导致

MySQL 数据优化

MySQL 数据优化的指南 MySQL 数据库优化是一个复杂且重要的过程,它直接影响到系统的性能、可靠性和可扩展性。在处理大量数据或高并发请求时,数据库的优化尤为关键。通过合理的数据库设计、索引使用、查询优化和硬件调优,可以大幅提高 MySQL 的运行效率。本文将从几个主要方面详细介绍 MySQL 的优化技巧,帮助你在实际应用中提升数据库性能。 一、数据库设计优化 1. 数据库的规范化与反规