一、Android滑动冲突的完美解决方案
在Android开发中滑动冲突可以说是比较常见的一类问题,也是比较让人头疼的一类问题,两个原本完美的控件,组合在一起之后,忽然发现整个世界都不好了。滑动冲突主要分为同方向滑动冲突和不同方向滑动冲突,下面本文将详细说明两种滑动冲突如何解决。

关于滑动冲突

在Android开发中,如果是一些简单的布局,都很容易搞定,但是一旦涉及到复杂的页面,特别是为了兼容小屏手机而使用了ScrollView以后,就会出现很多点击事件的冲突,最经典的就是ScrollView中嵌套了ListView。我想大部分刚开始接触Android的同学们都踩到过这个坑,下面跟着小编一起来看看解决方案吧。。

同方向滑动冲突

比如ScrollView嵌套ListView,或者是ScrollView嵌套自己

这里先看一张效果图

上图是在购物软件上常见的上拉查看图文详情,关于这中动画效果的实现,其实实现整体的效果,办法是有很多的,网上有很多相关的例子,但是对某些细节的处理不是很清晰,比如,下拉之后显示的部分(例如底部的图文详情)又是一个类ScrollView的控件(比如WebView)的话,又会产生新的问题。这里就以下拉查看图文详情为背景做一下同方向滑动冲突的分析。

整体思路
这里看下图

多个ScrollView嵌套示意图

首先,关于这张图做一些设定:

     1.黑色的框代表手机屏幕

     2.绿色的框代表一个外层的ScrollView

     3.两个红色的框代表嵌套在里面的两个类ScrollView控件,这里我们就暂时简称为 SUp,SDown

好了,接下来就分析一下实现整个流程的过程。

这里必须明确的一点,无论何时,SUp和SDown可见的部分始终是手机屏幕的高度。知道了这一点,我们就可以按以下步骤展开

首先,我们确保外部的ScrollView不拦截滑动事件,这样SUp必然获得此次事件,然后根据其Action_Move事件,当其为向下滑动且自身滑动距离+屏幕高度=其自身高度 时,即可认为SUp滑动到了底部,此时外部ScrollView可拦截滑动事件,从而保证整个视图能够继续向下滑动,这个时候底部SDown就显示出来了。

同理,这时候不允许外部ScrollView拦截滑动事件,由SDown处理,根据其Action_move事件,当其为向上滑动,且自身可滑动距离为0时,就说明SDown已经滑动到了顶部,这时外部ScrollView又可以获得拦截滑动事件的权利,从而保证整个视图能够向上继续滑动,此时SUp再次显示,又开始新一轮循环拦截。

这样整体的一个流程就可以实现动图中的效果。好了,说完原理,还是看代码。

代码实现

SUp实现

?
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778 public classUpScrollView extendsScrollView { /** * 屏幕高度 */ privateintmScreenHeight; /** * 上一次的坐标 */ privatefloatmLastY; /** * 当前View滑动距离 */ privateintmScrollY; /** * 当前View内子控件高度 */ privateintmChildH;  publicUpScrollView(Context context) { super(context); init(context); }  publicUpScrollView(Context context, AttributeSet attrs) { super(context, attrs); init(context); }  publicUpScrollView(Context context, AttributeSet attrs, intdefStyleAttr) { super(context, attrs, defStyleAttr); init(context);  }  privatevoidinit(Context context) { WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); DisplayMetrics dm = newDisplayMetrics(); wm.getDefaultDisplay().getMetrics(dm); mScreenHeight = dm.heightPixels; }  @Override publicbooleanonTouchEvent(MotionEvent ev) { //默认设定顶层View不拦截  getParent().getParent().requestDisallowInterceptTouchEvent(true);  switch(ev.getAction()) {  caseMotionEvent.ACTION_DOWN:  mLastY = (int) ev.getY();  break;  caseMotionEvent.ACTION_MOVE:  floaty = ev.getY();  floatdeltaY = y - mLastY;   mChildH = this.getChildAt(0).getMeasuredHeight();   inttranslateY = mChildH - mScrollY;   //向上滑动时,如果translateY等于屏幕高度时,即表明滑动到底部,可又顶层View控制滑动  if(deltaY < 0 && translateY == mScreenHeight) {   getParent().getParent().requestDisallowInterceptTouchEvent(false);  }  break;  default:  break;  }  returnsuper.onTouchEvent(ev); }  @Override protectedvoidonScrollChanged(intl, int t, intoldl, intoldt) { super.onScrollChanged(l, t, oldl, oldt); mScrollY = t; }}

这里在ACTION_MOVE里做了减法,其实道理是一样的。

onScrollChanged 是在View类中实现,查看其API可以看到其第二个参数t解释

?
1 @param t Current vertical scroll origin.

即为当前View此次滑动的距离

SDown实现

?
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859 public classMyWebView extendsWebView { publicfloatoldY; privateintt;  publicMyWebView(Context context) { super(context); init(); }  publicMyWebView(Context context, AttributeSet attrs) { super(context, attrs); init(); }  publicMyWebView(Context context, AttributeSet attrs, intdefStyleAttr) { super(context, attrs, defStyleAttr); init(); }  privatevoidinit() { WebSettings settings = getSettings(); settings.setJavaScriptEnabled(true); setWebViewClient(newWebViewClient() {  @Override  publicbooleanshouldOverrideUrlLoading(WebView view, String url) {  view.loadUrl(url);  returntrue;  } }); }  @Override publicbooleanonTouchEvent(MotionEvent ev) { getParent().getParent().requestDisallowInterceptTouchEvent(true); switch(ev.getAction()) {  caseMotionEvent.ACTION_DOWN:  oldY = ev.getY();  break;  caseMotionEvent.ACTION_MOVE:   floatY = ev.getY();  floatYs = Y - oldY;  if(Ys > 0 && t == 0) {   getParent().getParent().requestDisallowInterceptTouchEvent(false);  }  break;  }  returnsuper.onTouchEvent(ev); }  @Override protectedvoidonScrollChanged(intl, int t, intoldl, intoldt) { this.t = t; super.onScrollChanged(l, t, oldl, oldt); } }

以上看到,这里底部的View并没有继承ScrollView,而是选择继承了WebView,这里只是为了方便,当然继承ScrollView也是没有问题。这里只是需要按实际情况考虑,因为底部图文详情的内容就是一个WebView加载数据。

这个类的实现,按照之前说的原理应该很好理解。

外部ScrollView

?
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596 public classCustomerScrollViews extendsScrollView { /** * 屏幕高度 */ privateintmScreenHeight;  privateUpScrollView upScrollView; privateMyWebView myWebView; privatebooleaninit = false;  privatefloatfator = 0.2f; privateintfactorHeight;  privatebooleanupShow = true;  publicCustomerScrollViews(Context context) { super(context); init(context); }  publicCustomerScrollViews(Context context, AttributeSet attrs) { super(context, attrs); init(context); }  publicCustomerScrollViews(Context context, AttributeSet attrs, intdefStyleAttr) { super(context, attrs, defStyleAttr); init(context);  }  privatevoidinit(Context context) { WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); DisplayMetrics dm = newDisplayMetrics(); wm.getDefaultDisplay().getMetrics(dm); mScreenHeight = dm.heightPixels; factorHeight = (int) (mScreenHeight * fator); }  @Override protectedvoidonMeasure(intwidthMeasureSpec, intheightMeasureSpec) { if(!init) {   LinearLayout parentView = (LinearLayout) getChildAt(0);  //获得内部的两个子view  upScrollView = (UpScrollView) parentView.getChildAt(0);  myWebView = (MyWebView) parentView.getChildAt(2);//  //并设定其高度为屏幕高度  upScrollView.getLayoutParams().height = mScreenHeight;  myWebView.getLayoutParams().height = mScreenHeight;  init = true; }  super.onMeasure(widthMeasureSpec, heightMeasureSpec); }  @Override protectedvoidonLayout(booleanchanged, int l, intt, intr, intb) { super.onLayout(changed, l, t, r, b); if(changed) {  scrollTo(0, 0); } }  @Override publicbooleanonTouchEvent(MotionEvent ev) { switch(ev.getAction()) {  caseMotionEvent.ACTION_UP:  intscrollY = getScrollY();  if(upShow) {   if(scrollY <= factorHeight) {   smoothScrollTo(0, 0);   } else{   smoothScrollTo(0, mScreenHeight);   upShow = false;    }  } else{   intscrollpadding = mScreenHeight - scrollY;   if(scrollpadding >= factorHeight) {   this.smoothScrollTo(0, 0);   upShow = true;    } else{   this.smoothScrollTo(0, mScreenHeight);   }   }   returntrue;  } returnsuper.onTouchEvent(ev); } }

这个类的实现,就很灵活了,在onMeasure方法中初始化完内部的View之后,在OnTouch方法中就可以根据实际需求完成不同的逻辑实现,这里只是为了仿照查看图文详情的效果,对整个视图通过ScrollView的smoothScrollTo方法进行位移变化,这个逻辑很简单。

这里重点说一下一个地方:

?
12 upScrollView = (UpScrollView) parentView.getChildAt(0);myWebView = (MyWebView) parentView.getChildAt(2);

你可能会奇怪中间的child(1)去了哪里?这里还要从MainActivity的布局文件说起

dual_scrollview_activity_layout1.xml

?
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061 <?xmlversion="1.0"encoding="utf-8"?><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical">  <com.example.dreamwork.activity.superscrollview.CustomerScrollViews android:layout_width="match_parent" android:layout_height="match_parent">  <LinearLayout  android:layout_width="match_parent"  android:layout_height="wrap_content"  android:orientation="vertical">   <com.example.dreamwork.activity.superscrollview.UpScrollView   android:layout_width="match_parent"  android:layout_height="wrap_content"  android:scrollbars="vertical">   <LinearLayout   android:layout_width="match_parent"   android:layout_height="wrap_content"   android:orientation="vertical">    <ImageView   android:layout_width="match_parent"   android:layout_height="wrap_content"   android:scaleType="fitXY"   android:src="@drawable/taobao"/>   <ImageView   android:layout_width="match_parent"   android:layout_height="wrap_content"   android:scaleType="fitXY"   android:src="@drawable/taobao"/>    <TextView   android:textSize="20sp"   android:padding="10dp"   android:gravity="center"   android:layout_marginTop="20dp"   android:layout_marginBottom="60dp"   android:text="查看图文详情"   android:layout_width="match_parent"   android:layout_height="wrap_content"/>   LinearLayout>  com.example.dreamwork.activity.superscrollview.UpScrollView>   <includelayout="@layout/selector_tab_items"/>   <com.example.dreamwork.activity.superscrollview.MyWebView  android:id="@+id/web"  android:layout_width="match_parent"  android:layout_height="match_parent"/>  LinearLayout> com.example.dreamwork.activity.superscrollview.CustomerScrollViews> LinearLayout>

整个布局文件可以看出,我们在CustomerScrollViews这个最外层的自定义ScrollView内部又放置了两个自定义的ScrollView(就如我们看到的原理图那样),只不过在这两个ScrollView类控件的中间通过layout又放置一个LinearLayout,里面的内容就是在动图中看到的那个中间的写着qq,baidu字样的用于切换WebView内容的一个View。这里就不贴代码了。

这样,你就可以理解之前的child(1)为什么被跳过了吧。

使用

?
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465 public classDualScrollViewActivity1 extendsActivity implementsView.OnClickListener {  privateMyWebView webView;  privateTextView sinaTv, qqTv, baiduTv; privateView line1, line2, line3;  privatefinalString BAIDU = "http://www.baidu.com"; privatefinalString QQ = "http://www.qq.com"; privatefinalString SINA = "http://sina.cn";  @Override protectedvoidonCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); InitView(); sinaTv.performClick(); }  privatevoidInitView() { setContentView(R.layout.dual_scrollview_activity_layout1); webView = V.f(this, R.id.web);  sinaTv = V.f(this, R.id.one); sinaTv.setOnClickListener(this); qqTv = V.f(this, R.id.two); qqTv.setOnClickListener(this); baiduTv = V.f(this, R.id.three); baiduTv.setOnClickListener(this);  line1 = V.f(this, R.id.line1); line2 = V.f(this, R.id.line2); line3 = V.f(this, R.id.line3); }  @Override publicvoidonClick(View v) { reset(); String url = ""; switch(v.getId()) {  caseR.id.one:  line1.setVisibility(View.VISIBLE);  url = SINA;  break;  caseR.id.two:  line2.setVisibility(View.VISIBLE);  url = QQ;  break;  caseR.id.three:  line3.setVisibility(View.VISIBLE);  url = BAIDU;  break;  default:  break; }  webView.loadUrl(url); }  privatevoidreset(){ line1.setVisibility(View.GONE); line2.setVisibility(View.GONE); line3.setVisibility(View.GONE); }}

关于底部View内容更新,WebView 通过加载不同URL实现不同视图效果,只是作为Demo测试,实际中应考虑通过fragment切换实现。

这里对滑动冲突的解决方法,是由内而外的展开,默认使顶层View失去拦截能力,在由底部View的滑动距离,做出不同逻辑判断控制了顶层的拦截与否;这也是比较容易理解和实现的思路。当然,对于此类滑动冲突,有很多不同思路,这里只是列举其一。

实际开发开发中,这种带有同方向滑动特性的控件嵌套时,产生的问题不只是滑动冲突,有时候会有内容显示不全或不显示的情况。

最常见如ScrollView嵌套ListView,这种情况只需自定义ListView使其高度计算为一个很大的值,某种意义上让其失去了滑动的特性,但是这样也让ListView貌似失去了视图回收机制,这种时候如果加载很多很多很多图片,效果就会很不理想。对于这种情况,通过对ListView添加headView及footView也是一种解决的办法,但也得实际UI情况允许。

ScrollView嵌套RecyclerView时稍微麻烦一点,需要自定义ScrollView,还需要自定义实现LinearLayoutManager。

不同方向滑动冲突

比如ScrollView嵌套ViewPager,或者是ViewPager嵌套ScrollView,这种情况其实很典型。现在大部分应用最外层都是ViewPager+Fragment 的底部切换(比如微信)结构,这种时候,就很容易出现滑动冲突。不过ViewPager里面无论是嵌套ListView还是ScrollView,滑动冲突是没有的,毕竟是官方的东西,可能已经考虑到了这些,所以比较完善。

复杂一点的滑动冲突,基本上就是这两个冲突结合的结果。

滑动冲突解决思路
滑动冲突,就其本质来说,两个不同方向(或者是同方向)的View,其中有一个是占主导地位的,每次总是抢着去处理外界的滑动行为,这样就导致一种很别扭的用户体验,明明只是横向的滑动了一下,纵向的列表却在垂直方向发生了动作。就是说,这个占主导地位的View,每一次都身不由己的拦截了这个滑动的动作,因此,要解决滑动冲突,就是得明确告诉这个占主导地位的View,什么时候你该拦截,什么时候你不应该拦截,应该由下一层的View去处理这个滑动动作。

这里不明白的同学,可以去了解一下Android Touch事件的分发机制,这也是解决滑动冲突的核心知识。

滑动冲突

这里,说一下背景情况。之前做下拉刷新、上拉加载更多时一直使用的是PullToRefreshView这个控件,因为很方便,不用导入三方工程。在其内部可以放置ListView,GridView及ScrollView,非常方便,用起来可谓是屡试不爽。但是直到有一天,因项目需要,在ListView顶部加了一个轮播图控件BannerView(这个可以参考之前写的一篇学习笔记)。结果发现轮播图滑动的时候,和纵向的下拉刷新组件冲突了。

如之前所说,解决滑动冲突的关键,就是明确告知接收到Touch的View,是否需要拦截此次事件。

解决方法

解决方案1,从外部拦截机制考虑

这里,相当于是PullToRefreshView嵌套了ViewPager,那么每次优先接收到Touch事件的必然是PullToRefreshView。这样就清楚了,看代码:

在PullToRefreshView中:

?
1234567891011121314151617181920212223242526272829303132 @Overridepublic booleanonInterceptTouchEvent(MotionEvent e) {int y = (int) e.getRawY();int x = (int) e.getRawX();boolean resume = false;switch (e.getAction()) { caseMotionEvent.ACTION_DOWN: // 发生down事件时,记录y坐标 mLastMotionY = y; mLastMotionX = x; resume = false; break; caseMotionEvent.ACTION_MOVE: // deltaY > 0 是向下运动,< 0是向上运动 intdeltaY = y - mLastMotionY; intdeleaX = x - mLastMotionX;  if(Math.abs(deleaX) > Math.abs(deltaY)) {  resume = false; } else{ //当前正处于滑动  if(isRefreshViewScroll(deltaY)) {  resume = true;  } } break; caseMotionEvent.ACTION_UP: caseMotionEvent.ACTION_CANCEL: break;}return resume;}

这里最关键的代码就是这行

?
123 if (Math.abs(deleaX) > Math.abs(deltaY)) {   resume = false;  }

横向滑动距离大于纵向时,无须拦截这次滑动事件。其实,就是这么简单,但前提是你必须明确了解Android Touch事件的传递机制,期间各个方法执行的顺序及意义。

解决方案2,从内容逆向思维分析

有时候,我们不想去修改引入的第三方控件,或者说是无法修改时。就必须考虑从当前从Touch传递事件中最后的那个View逆向考虑。首先,由Android中View的Touch事件传递机制,我们知道Touch事件,首先必然由最外层View拦截,如果无法更改这个最外层View,那么是不是就没辙了呢?其实不然,Android这么高大上的系统必然考虑到了这个问题,好了废话不说,先看代码

?
1234567891011121314151617181920212223242526272829303132333435363738394041 private BannerView carouselView;private Context mContext; private PullToRefreshView refreshView; ......... refreshView.setOnTouchListener(newView.OnTouchListener() { @Override publicbooleanonTouch(View v, MotionEvent event) { carouselView.getParent().requestDisallowInterceptTouchEvent(false); returnfalse; }}); carouselView.setOnTouchListener(newView.OnTouchListener() { @Override publicbooleanonTouch(View v, MotionEvent event) { carouselView.getParent().requestDisallowInterceptTouchEvent(true); intx = (int) event.getRawX(); inty = (int) event.getRawY();  switch(event.getAction()) {  caseMotionEvent.ACTION_DOWN:  lastX = x;  lastY = y;  break;  caseMotionEvent.ACTION_MOVE:  intdeltaY = y - lastY;  intdeltaX = x - lastX;  if(Math.abs(deltaX) < Math.abs(deltaY)) {   carouselView.getParent().requestDisallowInterceptTouchEvent(false);  } else{   carouselView.getParent().requestDisallowInterceptTouchEvent(true);  }  default:  break; } returnfalse; }});

首先说一下这个方法

?
1 public abstractvoidrequestDisallowInterceptTouchEvent (booleandisallowIntercept)

API里的意思很明确,子View如果不希望其父View拦截Touch事件时,可调用此方法。当disallowIntercept这个参数为true时,父View将不拦截。

PS:这个方法的命名和其参数的使用逻辑,让我想到了一句很有意思的话,敌人的敌人就是朋友,真不知道Google的大神们怎么想的,非要搞一个反逻辑。

言归正传。这里拦截直接也很明确,在carouselView的onTouch方法中每次进入就设定父View不拦截此次事件,然后在MOTION_MOVE时候,根据滑动的距离判断再决定是父View是否有权利拦截Touch事件(即滑动行为)。

总结

好了,本文内容到这基本就结束了,本篇文章只是提供一种解决方法的思路,在具体的场景下,交互往往是贴合具体业务需求的。但是不管怎么样,找出点击事件截断和处理的时机是最重要的,围绕这个关键点,总能找出相应的解决方法。









二、Android View的滑动冲突解决方法

注意:

  1. 阅读本文需要了解《Android事件分发机制》
  2. 在此知识点,本人也有部分困惑尚未完全解决,也会在文中标出出来。

常见的滑动冲突场景及对应的处理规则

  1. 外部滑动方向和内部滑动方向不一致
    面对这种情况的滑动冲突,解决规则是:根据滑动是水平滑动还是竖直滑动来判断由谁来拦截事件。判断滑动方向的方法是:比较水平方向和竖直方向滑动距离的大小,或者滑动路径和水平方向的夹角,或者根据水平方向和竖直方向的速度差。
  2. 外部滑动方向和内部滑动方向一致
    这种情况的滑动冲突,无法根据滑动的角度判断,一般都是根据业务需要来进行判断。
  3. 上面两种情况的结合
    这种情况比较复杂,一般也需要从业务上找到突破点。

场景一

场景一:假如打算做个像ViewPager一样的效果,父容器如horizonal方向的LinearLayout一样,容纳了三个ListView。

外部拦截法

就是指点击事件都先经过父容器的拦截处理,如果不需要此事件就不拦截,这样就可以解决滑动冲突的问题。外部拦截法需要重写父容器的onInterceptTouchEvent()方法,在内部完成相应的拦截即可。

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;                                 //注解1                  if (!mScroller.isFinished()){                       //注解2                        mScroller.abortAnimation();                        intercepted = true;                  }                  break;            case MotionEvent.ACTION_MOVE:                 //注解3                  int deltaX = x - mLastXIntercept;                  int deltaY = y - mLastYintercept;                  if (Math.abs(deltaX) > Math.abs(deltaY)){     //在这里的if中加入是否拦截的判断                        intercepted = true;                  } else {                        intercepted = false;                  }                  break;            case MotionEvent.ACTION_UP:                  intercepted = fasle;                       //注解4                  break;            default:                  break;               }      mLastXIntercept = x;      mLastYintercept = y;            return intercepted; }

注解1:
为什么要在ACTION_DOWN时,intercepted = false?因为如果在ACTION_DOWN时,intercepted == true,那么根据Android事件分发机制,后面的MOVE事件和UP都会无条件的交给父容器去处理。这样的话,事件永远无法传递给子View。
此处的困惑:如果仅仅在父容器中设置ACTION_DOWN时,intercepted = false还是不够的。intercepted = false,然后DOWN事件传递给了子View,按照Android事件分发机制,如果子View没有成功处理DOWN事件(即返回了false),最终还是会调用父容器的处理方法。如果这样的话,后面的MOVE事件和UP依然会无条件的交给父容器去处理。
注解2:
这个if内的语句,针对的是下面的情况:如果用户此时在进行父容器的滑动方向(这里是水平滑动),但是在水平滑动之前如果用户再迅速进行竖直滑动,就会导致界面在水平方向无法滑动到终点从而处于一种中间状态。为了避免这种情况,当水平滑动时,下一个序列的点击事件仍然交给父容器处理(哪怕竖直方向滑动距离大于水平方向滑动距离,此时仍然判定是水平滑动)。
注解3:
这一块代码是解决滑动冲突的关键。在MOVE事件中,判断这一滑动事件是水平滑动还是竖直滑动,如果是水平滑动,作为父容器就拦截事件,如果是水平滑动,就不拦截事件,交给子view去处理。
此处的困惑:如果在一系列的MOVE事件中,前部分是水平移动,后部分是竖直移动的,那怎么办?因为没有拦截DOWN事件,所有很有可能事件拦截过程中的mFirstTouchTarget != null,所以后部分的MOVE事件仍然要调用onInterceptTouchEvent(),此时,intercept = false;,那么接着交给子View处理?
如何解答这个困惑呢?有一个结论是:

一旦父容器开始拦截任何一个事件,那么后续的事件都会交给它来处理。

这个结论先记住吧,暂时还没有搞明白为什么会这样。
注解4:
如果父容器在UP事件中返回了true,就会导致子View无法接受到UP事件,这个时候子元素中的onClick事件就无法处罚法。同样的,

因为一旦父容器开始拦截任何一个事件,那么后续的事件都会交给它来处理,所以UP作为最后一个事件也必定可以传递给父容器,即便父容器的onInterceptTouchEvent方法在UP时返回了false

但是依然没有弄明白为什么有这个结论。

内部拦截法

内部拦截法是指父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交由父容器进行处理。这种方法需要配合requestDisallowInterceptTouchEvent()方法才能正常工作。
第一步,修改父容器的onInterceptTouchEvent(),让其在DOWN事件返回false,其他情况下返回true。

//父容器内public boolean onInterceptTouchEvent(MotionEvent event){      int x = (int)event.getX();      int y = (int)event.getY();      int action = event.getAction();      if (action == MotionEvent.ACTION_DOWN){                  //注解5            mLastX = x;            mLastY = y;            if (!mScroller.isFinished()){                  mScroller.abortAnimation()                  return true;            }            return true;      } else {            return true;      } }

注解5:
父容器拦截了除了DOWN事件以外的其他事件,这样当子元素调用parent.requestDisallowInterceptTouchEvent(false)方法时,父元素才能继续拦截所需的事件。
第二步,修改子元素的dispatchTouchEvent()方法

//在子View中public boolean dispatchTouchEvent(MotionEvent event){      int x = (int) event.getX();      int y = (int) event.getY();      switch(event.getAction()){      case MotionEvent.ACTION_DOWN:            XXX.requestDisallowInterceptTouchEvent(true);            break;      case MotionEvent.ACTION_MOVE:            int deltaX = x - mLastX;            int deltaY = y - mLastY;            if (Math.abs(deltaX) > Math.abs(deltaY)){                              //在这里的if中加入是否拦截的判断                  XXX.requestDisallowInterceptTouchEvent(false);            }            break;      case MotionEvent.ACTION_UP:            break;      default:            break;      }      mLastX = x;      mLastY = y;      return super.dispatchTouchEvent(event);               //注解6}

注解6:因为子view是自定义view,重写的dispatchTouchEvent()方法,在解决了滑动冲突后,调用父类的dispatchTouchEvent()方法来进行原来的事件分发。

场景二和场景三

在总体的实现方法和场景一是一样的,仅仅是在MOVE事件中判断的条件不一样,场景一仅仅是通过滑动方向来进行判断,而场景二和场景三需要判断业务逻辑。这里就不详细介绍了。
















注:1、转载原文地址:http://www.jb51.net/article/90032.htm
https://www.jianshu.com/p/78560c968637

更多相关文章

  1. Android(安卓)activity简单的跳转
  2. 浅谈android view事件分发机制
  3. Android(安卓)事件传递机制的理解
  4. [置顶] 那些你应该知道却不一定知道的——View坐标分析汇总
  5. Android学习11--事件处理
  6. Android稳定性测试-- Monkey源码分析
  7. 单击事件(onClick())与触摸事件(onTouch())的区别
  8. 再论Android中的OnTouch事件和MotionEvent
  9. 都在说EventBus,我也来一波EventBus

随机推荐

  1. listView 中relativeLayout 布局的 andro
  2. Android中.9.png图片的使用过程和原理
  3. Android之emulator: ERROR及logcat不能提
  4. Android编译环境 相关
  5. android发送/解析彩信的几篇文章
  6. 很实用的android按键处理
  7. Android Tip1:获取 android 每个 app 内存
  8. android 中ScrollView的使用
  9. Android 笔记:Android将图像转换成流存储
  10. 【Android 初学】10、Intent对象的使用