学习的最终目标就是要学以致用,本文所分享的案例都是自己在公司实战开发过程中的真实案例,现在把它分享出来,希望对初学者有所帮助
好久没有写博客了,今天是周末,所以有时间来写一篇,前些天在工作中出现了一个关于滑动冲突的问题,我把解决它的过程记录下来,现在分享出来,以便给大家遇到了类似的问题提供参考。
关于事件分发在三年前曾经写过一个专栏,共有六篇文章,三篇理论,一篇总结,两篇实战,今天再来写一篇关于实战的文章,如果对事件分发流程不熟悉,请先阅读之前我写过的专栏《View事件分发》系列文章,然后再来看这篇文章你会轻松很多。
整个APP首页的布局架构为: BottomNavigationView +TabLayout+ViewPager+SwipeRefreshLayout+NestedScrollView+RecyclerView,
这种结构在现在的app中也是最通用的布局架构方式,打开手机随便点开一个APP几乎都是这种布局方式,不管是无人不用的微信,还是现在火爆的抖音,头条....等无一例外都是这样的布局方式,侧边栏菜单的模式好像对于国人的使用习惯并不合适,滴滴打车APP在前几年也用过侧边栏的方式,不过在后来的版本升级中也改成了大众所熟悉的底边栏菜单。
在APP首页中ViewPager有左右滑动的动作来翻页,在第一页中有个教材学习的横向RecycleView,它也有左右滑动的动作,当在RecycleView中左右滑动的时候,在它在向右滑到头的时候,此时继续向右滑动ViewPager就开始翻页了,ViewPager中已经给我们解决了滑动冲突,看起来貌似没有问题,但是细心的同学可能发现RecycleView在左右滑动的过程中有明显的卡顿,同样的功能和IOS相比较很明显没有IOS那样的丝滑流畅。
具体的问题效果如下:

教材学习的RecycleView左右滑动的过程中会明显的感觉到不流畅,有卡顿,这是因为RecycleView在左右滑动的过程中,一部分滑动事件被ViewPager消费了,所以就出现的卡顿,那么只要事件落在教材学习的RecycleView之上就把所有的事件都交给RecycleView来处理即可解决问题,在最顶级的ViewPager可以做如下处理:
首先创建一个方法isBookTouch来判断触摸事件是否在RecycleView中
private fun isBookTouch(event: MotionEvent): Boolean {
slideViewPagerListener?.getHomeBookRecView()?.let {
val rect = Rect()
it.getHitRect(rect)
if (rect.contains(event.x.toInt(), event.y.toInt())) {
KLog.v("MYTAG", "isBookTouch true")
return true
}
}
return false
}
在isBookTouch方法中我们用到了一个getHitRect方法来判断当前触摸点是否在指定的View上,相关联的有四个方法,分别介绍如下:
1.getHitRect: 获取View可点击矩形左、上、右、下边界相对于父View的左顶点的距离(偏移量)
2.getDrawingRect: 获取View的绘制范围,即左、上、右、下边界相对于此View的左顶点的距离(偏移量),即0、0、View的宽、View的高
3.getLocalVisibleRect: 获取View在第一个可滚动的上级View(父View或祖父View或...)中的可见区域相对于此View的左顶点的距离(偏移量)
4.getGlobalVisibleRect: 获取View在第一个可滚动的上级View(父View或祖父View或...)中的可见区域相对于屏幕左顶点的距离(偏移量)
在顶级的ViewPager中做如下处理:
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
if (isBookTouch(event)) {
slideViewPagerListener?.getHomeBookRecView()?.let {
return it.dispatchTouchEvent(event)
}
}
return super.dispatchTouchEvent(event)
}
只要判断该滑动事件在在RecycleView中就全部交给RecycleView来处理

这样左右滑动非常的流畅,但是出现了一个问题,当滑动起点在教材学习RecycleView上方,下拉出现了卡顿,起点在RecView上,终点在Recview之下,下拉无响应,此操作录屏的无法演示。
代码优化如下:
var isBookTouch = false
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
isBookTouch = isBookTouch(event)
}
else -> {
if (isBookTouch) {
slideViewPagerListener?.getHomeBookRecView()?.let {
return it.dispatchTouchEvent(event)
}
}
}
}
return super.dispatchTouchEvent(event)
}
在滑动的时候判断只要是第一个事件在教材学习的RecycleView上那么后序的事件都交给RecycleView来处理,左右滑动很流畅,但是下拉的问题依旧,起点在RecView上,终点在Recview之下,下拉无响应,此操作录屏的无法演示,这是因为事件的在RecycleView之外本应该交给SwipeRefreshView来处理的事件也交给RecycleView来处理了,显然是不合理的。

我们要对RecycleView处理的事件要做进一步的限制,上下滑动的时候事件交给SwipeRefreshLayout来处理,左右滑动的事件交给教材学习的RecycleView即可,代码如下:
var x1 = 0f
var isBookTouch = false
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
isBookTouch = isBookTouch(event)
x1 = event.x
}
MotionEvent.ACTION_MOVE -> {
var x2 = event.x
if (isBookTouch && Math.abs(x2 - x1) > 50) {
slideViewPagerListener?.getHomeBookRecView()?.let {
return it.dispatchTouchEvent(event)
}
}
x1 = x2
}
MotionEvent.ACTION_UP -> {
isBookTouch = false
x1 = 0f
}
}
return super.dispatchTouchEvent(event)
}
事件在RecycleView之上,且是左右滑动,通过两次滑动x轴的偏移量来判断,只要offset偏移大于50即可,左右滑动流畅,上下滑动下拉也很流畅。
具体效果如下:

下面在介绍另外一种方法也可以解决此问题:
通过x轴滑动距离和y轴滑动距离做比较,只要x轴的offset比y轴的offset大则认为是左右滑动,代码如下:
var x1 = 0f
var y1 = 0f
var isBookTouch = false
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
isBookTouch = isBookTouch(event)
x1 = event.x
y1 = event.y
}
MotionEvent.ACTION_MOVE -> {
var x2 = event.x
var y2 = event.y
val offsetX = Math.abs(x2 - x1)
val offsetY = Math.abs(y2 - y1)
if (isBookTouch && offsetX > offsetY) {
slideViewPagerListener?.getHomeBookRecView()?.let {
return it.dispatchTouchEvent(event)
}
}
x1 = x2
y1 = y2
}
MotionEvent.ACTION_UP -> {
isBookTouch = false
x1 = 0f
y1 = 0f
}
}
return super.dispatchTouchEvent(event)
}
滑动效果如下:

左右滑动很流畅,上下滑动下拉刷新也没有问题。
当然在解决这个问题的时候,也出现了一些不该出现的错误,起初用外部拦截法在滑动时间结束的ACTION_UP事件没有给mFirstMotionEvent 变量重新初始化而导致的下拉刷新有偶尔卡死的现象,具体问题代码如下:
var x1 = 0f
var x2 = 0f
val MIN_DISTANCE = 50
var isBookClick = false
var mFirstMotionEvent : MotionEvent? = null
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
//KLog.v("MYTAG", "MainActivity dispatchTouchEvent start")
if(mFirstMotionEvent == null){
KLog.v("MYTAG", "mFirstMotionEvent is Null, touch:"+event.action)
mFirstMotionEvent = event
isBookClick = isBookRect(event)
}
when (event!!.action) {
MotionEvent.ACTION_DOWN ->{
KLog.v("MYTAG", "ACTION_DOWN touch")
x1 = event!!.x
}
MotionEvent.ACTION_MOVE ->{
KLog.v("MYTAG", "ACTION_MOVE touch")
x2 = event!!.x
val deltaX: Float = x2 - x1
if (Math.abs(deltaX) > MIN_DISTANCE) {
if(isBookRect(event) && isBookClick){
mHomeFragment.getBookRecycleView()?.let {
return it.dispatchTouchEvent(event)
}
}
}
}
MotionEvent.ACTION_UP -> {
KLog.v("MYTAG", "ACTION_UP touch")
x2 = event!!.x
val deltaX: Float = x2 - x1
if (Math.abs(deltaX) > MIN_DISTANCE) {
if(isBookRect(event) && isBookClick){
mHomeFragment.getBookRecycleView()?.let {
return it.dispatchTouchEvent(event)
}
}
}
//初始化位置不对,导致下拉卡顿
mFirstMotionEvent = null
isBookClick = false
}
}
KLog.v("MYTAG", "MainActivity super.dispatchTouchEvent start")
return super.dispatchTouchEvent(event)
}
private fun isBookRect(event: MotionEvent?) : Boolean{
if (mCurrFragment is HomeFragment) {
val bookRecycleView = mHomeFragment.getBookRecycleView()
bookRecycleView?.let {
val rect = Rect()
it.getGlobalVisibleRect(rect)
if (rect.contains(event?.x!!.toInt(), event?.y!!.toInt())) {
KLog.v("MYTAG", "bookRecycleView touch")
return true
}
}
}
return false
}
具体卡顿效果如下:

下拉的时候偶尔会出现这种卡死的现象,应该在ACTION_UP时候稍加修改即可解决问题:
MotionEvent.ACTION_UP -> {
KLog.v("MYTAG", "ACTION_UP touch")
x2 = event!!.x
val deltaX: Float = x2 - x1
//修改了初始化mFirstMotionEvent的位置
mFirstMotionEvent = null
if (Math.abs(deltaX) > MIN_DISTANCE) {
if(isBookRect(event) && isBookClick){
mHomeFragment.getBookRecycleView()?.let {
return it.dispatchTouchEvent(event)
}
}
}
}
好了,关于RecycleView+ViewPager+SwipeRefreshLayout滑动冲突我们今天就分析到这里,具体的核心算法就是:滑动事件在RecycleView上且是左右滑动,则该系列事件都交给RecycleView来处理,否则则交给系统来自行处理,左右滑动用阈值法和斜率法都可以解决问题,能用外部拦截法就尽量使用外部拦截发来解决问题,这样不但处理方便,而且执行的效率也会很高,希望这篇文章对你有所帮助。
是否有简单的方法来更改默认ISO格式(yyyy-mm-dd)的ActiveAdmin日期过滤器显示格式? 最佳答案 您可以像这样为日期选择器提供额外的选项,而不是覆盖js:=f.input:my_date,as::datepicker,datepicker_options:{dateFormat:"mm/dd/yy"} 关于ruby-on-rails-事件管理员日期过滤器日期格式自定义,我们在StackOverflow上找到一个类似的问题: https://s
文章目录git常用命令(简介,详细参数往下看)Git提交代码步骤gitpullgitstatusgitaddgitcommitgitpushgit代码冲突合并问题方法一:放弃本地代码方法二:合并代码常用命令以及详细参数gitadd将文件添加到仓库:gitdiff比较文件异同gitlog查看历史记录gitreset代码回滚版本库相关操作远程仓库相关操作分支相关操作创建分支查看分支:gitbranch合并分支:gitmerge删除分支:gitbranch-ddev查看分支合并图:gitlog–graph–pretty=oneline–abbrev-commit撤消某次提交git用户名密码相关配置g
最近因为项目需要,需要将Android手机系统自带的某个系统软件反编译并更改里面某个资源,并重新打包,签名生成新的自定义的apk,下面我来介绍一下我的实现过程。APK修改,分为以下几步:反编译解包,修改,重打包,修改签名等步骤。安卓apk修改准备工作1.系统配置好JavaJDK环境变量2.需要root权限的手机(针对系统自带apk,其他软件免root)3.Auto-Sign签名工具4.apktool工具安卓apk修改开始反编译本文拿Android系统里面的Settings.apk做demo,具体如何将apk获取出来在此就不过多介绍了,直接进入主题:按键win+R输入cmd,打开命令窗口,并将路
我正在尝试将以下SQL查询转换为ActiveRecord,它正在融化我的大脑。deletefromtablewhereid有什么想法吗?我想做的是限制表中的行数。所以,我想删除少于最近10个条目的所有内容。编辑:通过结合以下几个答案找到了解决方案。Temperature.where('id这给我留下了最新的10个条目。 最佳答案 从您的SQL来看,您似乎想要从表中删除前10条记录。我相信到目前为止的大多数答案都会如此。这里有两个额外的选择:基于MurifoX的版本:Table.where(:id=>Table.order(:id).
这是我在ActiveAdmin中的自定义页面ActiveAdmin.register_page"Settings"doaction_itemdolink_to('Importprojects','settings/importprojects')endcontentdopara"Text"endcontrollerdodefimportprojectssystem"rakedataspider:import_projects_ninja"para"OK"endendend我想做的是,当我单击“导入项目”按钮时,我想在Controller中执行rake任务。但是我无法访问该方法。可能是什
例如,假设我有一个名为Products的模型,并且在ProductsController中,我有以下代码用于product_listView以显示已排序的产品。@products=Product.order(params[:order_by])让我们想象一下,在product_listView中,用户可以使用下拉菜单按价格、评级、重量等进行排序。数据库中的产品不会经常更改。我很难理解的是,每次用户选择新的order_by过滤器时,rails是否必须查询,或者rails是否能够以某种方式缓存事件记录以在服务器端重新排序?有没有一种方法可以编写它,以便在用户排序时rails不会重新查询结果
我有一个将某些事件写入队列的Rails3应用。现在我想在服务器上创建一个服务,每x秒轮询一次队列,并按计划执行其他任务。除了创建ruby脚本并通过cron作业运行它之外,还有其他稳定的替代方案吗? 最佳答案 尽管启动基于Rails的持久任务是一种选择,但您可能希望查看更有序的系统,例如delayed_job或Starling管理您的工作量。我建议不要在cron中运行某些东西,因为启动整个Rails堆栈的开销可能很大。每隔几秒运行一次它是不切实际的,因为Rails上的启动时间通常为5-15秒,具体取决于您的硬件。不过,每天这样做几
我有一个帖子属于城市的关系,城市又属于一个州,例如:classPost现在我想找到所有帖子及其所属的城市和州。我编写了以下查询来获取带有城市的帖子,但不知道如何在同一查找器中获取带有城市的相应州:@post=Post.find:all,:include=>[:city]感谢任何帮助。谢谢。 最佳答案 Post.all(:include=>{:city=>:state}) 关于ruby-on-rails-使用Rails事件记录获取二级模型,我们在StackOverflow上找到一个类似的问
我觉得我错过了什么。我正在编写一个rubygem,它允许与事件记录进行交互,作为其主要功能的附加功能。在为其编写测试用例时,我需要能够指定虚拟事件记录模型来测试此功能。如果我可以获得一个事件记录模型的实例,它不需要与数据库的任何连接,可以有关系,所有这些东西,但不需要我在数据库中设置表,那就太棒了。我对测试还很陌生,在Rails测试之外我也很陌生,但似乎我应该能够相当轻松地完成类似的事情,但我什么也没找到。谁能告诉我我错过了什么?我看过工厂、制造商、固定装置,所有这些似乎都想达到目标。人们如何在您只需要AR对象进行测试的地方测试gem? 最佳答案
当你在类中包含方法名冲突的模块时,它会使用类定义的方法。有没有办法选择我想运行的?moduleBdefself.hello"helloB"endendclassAincludeBdefself.hello"helloA"endendA.hello#=>thisprints"helloA",whatifIwant"helloB"? 最佳答案 Ben,当你在Ruby中调用一个方法(比如hello)时,会发生以下情况:如果接收者的特征类有一个名为hello的方法,它将被调用。如果不是:如果接收者的类有一个名为hello的实例方法,它将被调