自定义View系列教程08--滑动冲突的产生及其处理
2016-06-27 15:58
573 查看
自定义View系列教程01--常用工具介绍
自定义View系列教程02--onMeasure源码详尽分析
自定义View系列教程03--onLayout源码详尽分析
自定义View系列教程04--Draw源码分析及其实践
自定义View系列教程05--示例分析
自定义View系列教程06--详解View的Touch事件处理
自定义View系列教程07--详解ViewGroup分发Touch事件
自定义View系列教程08--滑动冲突的产生及其处理
在之前的几篇文章中,我们已经分析了View对于Touch的处理以及ViewGroup对于Touch事件的分发。
但在开发中时常遇到一个棘手的问题:Touch事件的滑动冲突。比如ListView嵌套ScrollView,ViewPager嵌套ScrollView,ListView嵌套ScrollView时常常发生。
一.常见冲突场景:
场景1:外部滑动方向和内部滑动方向不一致。例如:ViewPaget+Fragment(ListView)或者HorizontalScrollView+listView
场景2:外部滑动方向和内部滑动方向一致。例如:HoriaontalScrollView+ViewPager
场景3:上面两种的嵌套。例如:SlideMenu+ViewPager+ListView
二.滑动冲突的处理规则:
如下图:根据滑动过程中两点之间的坐标,可以得出到底是水平滑动还是竖直滑动。
得到坐标之后可以通过:1.滑动角度 2.滑动的距离差 3.滑动的速度差 或者是从业务上找突破点。
三.滑动冲突的解决方法:
1.在父View中准确地进行事件分发和拦截【外部拦截法】
我们可以重写父View中与Touch事件分发相关的方法,比如onInterceptTouchEvent( )。这些方法中摒弃系统默认的流程,结合自身的业务逻辑重写该部分代码,从而使父View放行子View需要的Touch。
2.子View禁止父View拦截Touch事件 【内部拦截法】
在分析ViewGroup的dispatchTouchEvent()源码时,我们知道:Touch事件是由父View分发的。如果一个Touch事件是子View需要的,但是被其父View拦截了,子View就无法处理该Touch事件了。在此情形下,子View可以调用requestDisallowInterceptTouchEvent( )禁止父View对Touch的拦截
伪代码:
外部拦截法:
示例效果,请看下图:
它的布局文件如下:
嗯哼,滑动冲突的原因已经清楚了,我们现在来解决这个问题。
第一种解决方法
在ViewPager中禁止HorizontalScrollView拦截Touch事件
mViewPager.getParent().requestDisallowInterceptTouchEvent(true);
此处首先利用 mViewPager.getParent( )得到了ViewPager的父View即HorizontalScrollView;然后再执行requestDisallowInterceptTouchEvent( )方法从而禁止掉父View对于Touch事件的传递。
第二种解决方法
在自定义HorizontalScrollView中合理地处理Touch事件的拦截和分发
在该方法中如果判定Touch是水平的滑动:
if(distanceX > distanceY)
那么直接返回false不拦截该Touch。这样Touch事件就可以顺利传递到其子View即ViewPager中,从而正常地切换图片。
既然已经采用了自定义的HorizontalScrollView就不要忘记修改布局文件喔~
在实际的项目开发中所遇到的滑动冲突可能比这要复杂,但是处理冲突的基本原理和方式是相同的。
至此,自定义View系列教程就全部结束了
多谢大家的指正和鼓励
Thank you very much
原文链接:http://blog.csdn.net/lfdfhl/article/details/51656492
自定义View系列教程02--onMeasure源码详尽分析
自定义View系列教程03--onLayout源码详尽分析
自定义View系列教程04--Draw源码分析及其实践
自定义View系列教程05--示例分析
自定义View系列教程06--详解View的Touch事件处理
自定义View系列教程07--详解ViewGroup分发Touch事件
自定义View系列教程08--滑动冲突的产生及其处理
在之前的几篇文章中,我们已经分析了View对于Touch的处理以及ViewGroup对于Touch事件的分发。
但在开发中时常遇到一个棘手的问题:Touch事件的滑动冲突。比如ListView嵌套ScrollView,ViewPager嵌套ScrollView,ListView嵌套ScrollView时常常发生。
一.常见冲突场景:
场景1:外部滑动方向和内部滑动方向不一致。例如:ViewPaget+Fragment(ListView)或者HorizontalScrollView+listView
场景2:外部滑动方向和内部滑动方向一致。例如:HoriaontalScrollView+ViewPager
场景3:上面两种的嵌套。例如:SlideMenu+ViewPager+ListView
二.滑动冲突的处理规则:
如下图:根据滑动过程中两点之间的坐标,可以得出到底是水平滑动还是竖直滑动。
得到坐标之后可以通过:1.滑动角度 2.滑动的距离差 3.滑动的速度差 或者是从业务上找突破点。
三.滑动冲突的解决方法:
1.在父View中准确地进行事件分发和拦截【外部拦截法】
我们可以重写父View中与Touch事件分发相关的方法,比如onInterceptTouchEvent( )。这些方法中摒弃系统默认的流程,结合自身的业务逻辑重写该部分代码,从而使父View放行子View需要的Touch。
2.子View禁止父View拦截Touch事件 【内部拦截法】
在分析ViewGroup的dispatchTouchEvent()源码时,我们知道:Touch事件是由父View分发的。如果一个Touch事件是子View需要的,但是被其父View拦截了,子View就无法处理该Touch事件了。在此情形下,子View可以调用requestDisallowInterceptTouchEvent( )禁止父View对Touch的拦截
伪代码:
外部拦截法:
@Override//父类 public boolean onInterceptTouchEvent(MotionEvent event) { boolean intercepted = false; int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { intercepted = false; break; } case MotionEvent.ACTION_MOVE: if (父容器需要当前点击事件)) { intercepted = true; } else { intercepted = false; } break; } case MotionEvent.ACTION_UP: { intercepted = false; break; } default: break; } mLastXIntercept = x; mLastYIntercept = y; return intercepted; } //注释:ACTION_DOWN这个事件,父容器必须返回false,因为一旦父拦截了,后续ACTION_MOVE,ACTION_UP事件都会直接交给父处理,事件无>法传给子元素。 //ACTION_MOVE这个事件,根据需要来决定:父需要拦截返回true,否则返回false。 //ACTION_UP这个事件,必须返回false,因为ACTION_UP事件本身没有太多意义。 //说明:为什么说ACTION_UP必须返回false? //当返回true时,考虑一种情况,假设事件交给子处理,如果父在ACTION_UP时返回true,就会导致子无法收到ACTION_UP事件,子的Click事件就无法触发。(就出现了问题) //即使总返回false,也不会出错,因为父容器比较特殊,一旦开始拦截任何一个事件,那么后续事件都会交给它来处理,而ACTION_UP作为最后一个事件,也必定可以传给父容器,即是父容器的onInterceptTouchEvent方法在ACTION_UP返回了false。内部拦截法:
//内部拦截法 @Override//子类 public boolean dispatchTouchEvent(MotionEvent event) { int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { parent.requestDisallowInterceptTouchEvent(true); break; } case MotionEvent.ACTION_MOVE: { int deltaX = x - mLastX; int deltaY = y - mLastY; if (父容器需要此类点击事件) { parent.requestDisallowInterceptTouchEvent(false); } break; } case MotionEvent.ACTION_UP: { break; } default: break; } mLastX = x; mLastY = y; return super.dispatchTouchEvent(event); } } //注释:除了子元素需要做处理以外,父元素也要默认拦截ACTION_DOWN以外的其他事件,这样当子元素调用parent.requestDisallowInterceptTouchEvent(false)方法时,父元素才能继续拦截所需的事件。 @Override//父类 public boolean onInterceptTouchEvent(MotionEvent event) { int x = (int) event.getX(); int y = (int) event.getY(); int action = event.getAction(); if (action == MotionEvent.ACTION_DOWN) { mLastX = x; mLastY = y; return false; } else { return true; } }四,示例展示解决滑动冲突的常用方法。
示例效果,请看下图:
它的布局文件如下:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.stay4it.testtouch3.MainActivity"> <HorizontalScrollView android:id="@+id/horizontalScrollView" android:layout_height="450dp" android:layout_width="match_parent" android:scrollbars="horizontal" android:fillViewport="true"> <android.support.v4.view.ViewPager android:id="@+id/viewPager" android:layout_width="match_parent" android:layout_height="match_parent" /> </HorizontalScrollView> </RelativeLayout>在该示例中,我们在HorizontalScrollView中嵌入ViewPager浏览图片。由于HorizontalScrollView和ViewPager都可以响应水平滑动,当我们用手指切换图片时发现ViewPager无法正常的滚动。这是因为Touch事件被HorizontalScrollView处理,根本没有传递给ViewPager。
嗯哼,滑动冲突的原因已经清楚了,我们现在来解决这个问题。
第一种解决方法
在ViewPager中禁止HorizontalScrollView拦截Touch事件
package com.stay4it.testtouch3; import android.os.Bundle; import android.support.v4.view.ViewPager; import android.support.v7.app.AppCompatActivity; import android.view.MotionEvent; import android.view.View; /** * 原创作者 * 谷哥的小弟 * * 博客地址 * http://blog.csdn.net/lfdfhl */ public class MainActivity extends AppCompatActivity { private ViewPager mViewPager; private ViewPagerAdapter mViewPagerAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); init(); } private void init(){ mViewPager = (ViewPager) findViewById(R.id.viewPager); mViewPagerAdapter=new ViewPagerAdapter(this); mViewPager.setAdapter(mViewPagerAdapter); mViewPager.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { mViewPager.getParent().requestDisallowInterceptTouchEvent(true); return false; } }); } }在此,请关注第32行代码
mViewPager.getParent().requestDisallowInterceptTouchEvent(true);
此处首先利用 mViewPager.getParent( )得到了ViewPager的父View即HorizontalScrollView;然后再执行requestDisallowInterceptTouchEvent( )方法从而禁止掉父View对于Touch事件的传递。
第二种解决方法
在自定义HorizontalScrollView中合理地处理Touch事件的拦截和分发
package com.stay4it.testtouch4; import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; import android.widget.HorizontalScrollView; /** * 原创作者 * 谷哥的小弟 * * 博客地址 * http://blog.csdn.net/lfdfhl */ public class HorizontalScrollViewSubclass extends HorizontalScrollView{ private float LastX; private float LastY; private float distanceX; private float distanceY; public HorizontalScrollViewSubclass(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { return super.dispatchTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { switch (ev.getAction()){ case MotionEvent.ACTION_DOWN: distanceX = 0f; distanceY = 0f; LastX = ev.getX(); LastY = ev.getY(); break; case MotionEvent.ACTION_MOVE: final float currentX = ev.getX(); final float currentY = ev.getY(); distanceX += Math.abs(currentX - LastX); distanceY += Math.abs(currentY - LastY); LastX = currentX; LastY = currentY; if(distanceX > distanceY){ return false; } break; case MotionEvent.ACTION_UP: break; } return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent ev) { return super.onTouchEvent(ev); } }从这段代码中,我们的主要操作是在自定义HorizontalScrollView中重写了onInterceptTouchEvent( )。
在该方法中如果判定Touch是水平的滑动:
if(distanceX > distanceY)
那么直接返回false不拦截该Touch。这样Touch事件就可以顺利传递到其子View即ViewPager中,从而正常地切换图片。
既然已经采用了自定义的HorizontalScrollView就不要忘记修改布局文件喔~
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.stay4it.testtouch4.MainActivity"> <com.stay4it.testtouch4.HorizontalScrollViewSubclass android:id="@+id/horizontalScrollView" android:layout_height="450dp" android:layout_width="match_parent" android:scrollbars="horizontal" android:fillViewport="true" > <android.support.v4.view.ViewPager android:id="@+id/viewPager" android:layout_width="match_parent" android:layout_height="match_parent" /> </com.stay4it.testtouch4.HorizontalScrollViewSubclass> </RelativeLayout>嗯哼,在此通过一个完整的示例展示了事件滑动冲突的常用解决方式。
在实际的项目开发中所遇到的滑动冲突可能比这要复杂,但是处理冲突的基本原理和方式是相同的。
至此,自定义View系列教程就全部结束了
多谢大家的指正和鼓励
Thank you very much
原文链接:http://blog.csdn.net/lfdfhl/article/details/51656492
相关文章推荐
- 配置View桌面时找不到域的解决方法
- 完全克隆的虚拟桌面部署问题
- 实例讲解JavaScript的Backbone.js框架中的View视图
- Android中View自定义组合控件的基本编写方法
- Android 自定义View步骤
- Android自定义View仿QQ健康界面
- Android重写View实现全新的控件
- 解读ASP.NET 5 & MVC6系列教程(16):自定义View视图文件查找逻辑
- Android自定义View过程解析
- Android 自定义View 密码框实例代码
- Android自定义View软键盘实现搜索
- thinkphp3.x自定义Action、Model及View的简单实现方法
- codeigniter中view通过循环显示数组数据的方法
- MVVM模式中ViewModel和View、Model有什么区别?
- Android使用WindowManager构造悬浮view
- Android App开发中自定义View和ViewGroup的实例教程
- Android自定义View实现左右滑动选择出生年份
- Android应用开发中View绘制的一些优化点解析
- 自定义滑动按钮为例图文剖析Android自定义View绘制
- Android自定义View实现带数字的进度条实例代码