`

Android中对GridView, ListView等滚动控件的Touch事件onInterceptTouchEvent,onTouchEvent理解

阅读更多

       在开始正文之前,首先得感谢http://blog.csdn.net/guitk/article/details/7057155提供的一篇转载文章,里面说的内容和插图让我对TouchEvent的事件传递迅速有了一个大致的印象。但是文章里面有说的不全面不详细的地方,特发此博客记录个人对于这类问题的理解,用来补充和完善,有不妥的地方,也欢迎大家批评讨论。

 

       正如命名一样,onInterceptTouchEvent用来拦截事件,onTouchEvent用来处理事件,网上大部分的文章中也都对这两类方法的使用情况进行了简单的说明。

 

       事件传递如下图,逐级向下看onInterceptTouchEvent()是否需要截断事件,如果没发生截断,则逐级向上寻找能够处理该事件的onTouchEvent()。

      源码上RelativeLayout等布局是继承ViewGroup,看源码上ViewGroup中的onInterceptTouchEvent却非常简单:

public boolean onInterceptTouchEvent(MotionEvent ev) {
        return false;
}

       如果不重写该方法直接就会return false。
 
       如果在其中一层截断事件,让onInterceptTouchEvent()返回true,例如在上图中ChildLayout中发生截断,则事件传递则会向跳过MyView,直接从这一层进入onTouchEvent。若ChildLayout的onTouchEvent返回false,则传递流程如下图


 

 

       很重要的一点:事件传递是这样的,你可以在传递到响应的方法里面做任何你想做的事情,这些事情不会改变事件传递,能影响事件传递的是这些方法的返回值。例如你在ChildLayout的onTouchEvent做了很多事情,只要返回了false,事件还是会进入到ParentLayout的onTouchEvent。

 

       若保持ChildLayout的onInterceptTouchEvent返回true,将它的onTouchEvent返回true,则事件不会再进入其他组件的onTouchEvent,后续的时间会依次进入这个返回了true的onTouchEvent,传递则变为如下图:



 

 

       自此说的内容都跟博客开头那个链接里面的文章类似。下面写一些那篇文章里面没有谈及或者不够全面的地方。下文中举例进行的操作都是拖动,即ACITON_DOWN -> ACTION_MOVE -> ACTION_UP

 

       那篇文章里面说在事件发生截断的时候,会像子View发出ACTION_CANCEL,但是我在ChildLayout的onInterceptTouchEvent里面return true之后,却没有收到ACTION_CANCEL,经实验发现,谈谈关于ACTION_CANCEL的事情。ACTION_CANCEL的发出是有条件的:如果子一层曾经处理过事件,即事件进入到onTouchEvent中,则此时截断事件,上一层会向下一层发出ACTION_CANCEL。例如在ChildLayout的onInterceptTouchEvent中对event.getAction()进行判断,如果是ACTION_DOWN,返回false,在ACTION_MOVE的时候,返回true进行截断。则可以发现MyView接到了action为ACTION_DOWN的事件,但是当出现ACTION_MOVE在ChildLayout截断发生时,MyView则收到了ACTION_CANCEL的消息。代码如下:

 

       ChildLayout

 

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean result = super.onInterceptTouchEvent(ev);
        switch(ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            Log.i("ZZZZ", "ChildLayout onInterceptTouchEvent ACITON_DOWN:");
            break;
        case MotionEvent.ACTION_MOVE:
            Log.i("ZZZZ", "ChildLayout onInterceptTouchEvent ACITON_MOVE:");
            result = true;
            break;
        case MotionEvent.ACTION_CANCEL:
            Log.i("ZZZZ", "ChildLayout onInterceptTouchEvent ACITON_CANCEL:");
            break;
        case MotionEvent.ACTION_UP:
            Log.i("ZZZZ", "ChildLayout onInterceptTouchEvent ACITON_UP:");
            break;
        }
        Log.i("ZZZZ", "ChildLayout onInterceptTouchEvent return "+result);
        return result;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        boolean result = super.onTouchEvent(ev);
        switch(ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            Log.i("ZZZZ", "ChildLayout onTouchEvent ACTION_DOWN");
            break;
        case MotionEvent.ACTION_MOVE:
            Log.i("ZZZZ", "ChildLayout onTouchEvent ACTION_MOVE");
            break;
        case MotionEvent.ACTION_CANCEL:
            Log.i("ZZZZ", "ChildLayout onTouchEvent ACTION_CANCEL");
        case MotionEvent.ACTION_UP:
            Log.i("ZZZZ", "ChildLayout onTouchEvent ACTION_UP");
            break;
        }

        result = true;
        Log.i("ZZZZ", "ChildLayout onTouchEvent return "+result);
        return result;
   }

 

      为了增加事件的复杂性,挑选了有滚动效果的GridView在ChildLayout下一层的MyView作为实验对象

      MyGridView

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean result = super.onInterceptTouchEvent(ev);
        switch(ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            Log.d("ZZZZ", "MyGridView onInterceptTouchEvent ACITON_DOWN:");
            break;
        case MotionEvent.ACTION_MOVE:
            Log.d("ZZZZ", "MyGridView onInterceptTouchEvent ACITON_MOVE:");
            break;
        case MotionEvent.ACTION_CANCEL:
            Log.d("ZZZZ", "MyGridView onInterceptTouchEvent ACITON_CANCEL:");
            break;
        case MotionEvent.ACTION_UP:
            Log.d("ZZZZ", "MyGridView onInterceptTouchEvent ACITON_UP:");
            break;
        }
        Log.d("ZZZZ", "MyGridView onInterceptTouchEvent return "+result);
        return result;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        boolean result = super.onTouchEvent(ev);
        switch(ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            Log.d("ZZZZ", "MyGridView onTouchEvent ACTION_DOWN");
            break;
        case MotionEvent.ACTION_MOVE:
            Log.d("ZZZZ", "MyGridView onTouchEvent ACTION_MOVE");
            break;
        case MotionEvent.ACTION_CANCEL:
            Log.d("ZZZZ", "MyGridView onTouchEvent ACTION_CANCEL");
            break;
        case MotionEvent.ACTION_UP:
            Log.d("ZZZZ", "MyGridView onTouchEvent ACTION_UP");
            break;
        }

        Log.d("ZZZZ", "MyGridView onTouchEvent return "+result);
        return result;
  }

 
 运行日志:



 

       再次修改代码,取消ChildLayout里onInterceptTouchEvent对事件的拦截返回false,让MyGridView的onTouchEvent方法return super.onTouchEvent(ev)默认返回true。

 

       在同样尝试拖动效果的时候,按照上面的说法,理论上在ParentLayout,ChildLayout,MyGridView都不拦截事件return false的状态下,事件会通过依次通过ParentLayout,ChildLayout,MyGridView的onInterceptTouchEvent,然后到达MyGridView的onTouchEvent,但是我们发现事实并不是如此,移动一段距离后,就没有任何onInterceptTouchEvent执行了。运行效果如下:



         说好的依次传递并没有发生,无奈只能去源码中寻找原因,GridView继承于AbsListView,与之前ChildLayout,ParentLayout继承与ViewGroup不同,在AbsListView中的onTouchEvent的ACTION_MOVE的这个case中看到有个startScrollIfNeeded方法,点进去才发现有方法说明“Check if we have moved far enough that it looks more like a scroll than a tap”, 在我们tap屏幕的时候有一段距离touchSlop,小于这个距离的ACTION_MOVE是会被判定成为tap效果的,所以在这段源码里面能看到

if (overscroll || distance > mTouchSlop) {
            createScrollingCache();
            if (overscroll) {
                mTouchMode = TOUCH_MODE_OVERSCROLL;
                mMotionCorrection = 0;
            } else {
                mTouchMode = TOUCH_MODE_SCROLL;
                mMotionCorrection = deltaY > 0 ? mTouchSlop : -mTouchSlop;
            }
            final Handler handler = getHandler();
            // Handler should not be null unless the AbsListView is not attached to a
            // window, which would make it very hard to scroll it... but the monkeys
            // say it's possible.
            if (handler != null) {
                handler.removeCallbacks(mPendingCheckForLongPress);
            }
            setPressed(false);
            View motionView = getChildAt(mMotionPosition - mFirstPosition);
            if (motionView != null) {
                motionView.setPressed(false);
            }
            reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
            // Time to start stealing events! Once we've stolen them, don't let anyone
            // steal from us
            final ViewParent parent = getParent();
            if (parent != null) {
                parent.requestDisallowInterceptTouchEvent(true);
            }
            scrollIfNeeded(y);
            return true;
        }

       里面有代码,对parent判空了执行requestDisallowInterceptTouchEvent(true);看到注释则说明的很清楚了,如果满足了条件,被判定成为looks more like a scroll than a tap,则start stealing events, once we've stolen them, don't let anyone steal from us,好傲娇的样子,直接屏蔽了各种有可能阻截这些事件的情况,然后能够阻截这个MotionEvent的就只有onInterceptTouchEvent了,这也就是为什么在有GridView的情况下,"ACTION_DOWN -> ACTION_MOVE -> ACTION_UP"操作一小距离之后却看不到任何onInterceptTouchEvent被执行的原因了。

 

       前几天写代码里面涉及到了GridView和ListView的处理,看到打出来的LOG并不是如之前理解的,故有了这篇博客,用来给自己加深印象,也给有需要的同学提供帮助。

 

       如上文中有不妥的地方欢迎各位批评讨论,谢谢。

 

转载请注明出处:

http://waynehu16.iteye.com/blog/1926741

2
2
分享到:
评论

相关推荐

    Android ListView嵌套GridView(GridView实现横向滑动)

    在Android开发中,ListView和GridView是两种常用的布局控件,用于展示大量的数据。ListView通常用于垂直滚动,而GridView则用于网格状的布局,通常支持垂直和水平滚动。本篇文章将详细探讨如何在ListView中嵌套...

    android ListView和GridView拖拽移位实现代码

    在Android开发中,...总之,实现ListView和GridView的拖拽移位功能需要对触摸事件、视图缓存、数据源操作和滚动有深入理解。通过以上步骤,你可以创建一个流畅、直观的拖放体验,提升你的Android应用的用户体验。

    android 解决ScrollView和listView嵌套冲突问题(保证在ScrollView中滑动listView只响应listView的滑动)

    在Android开发中,ScrollView和ListView的嵌套是一个常见的需求,但同时也伴随着许多技术挑战,主要问题在于两者都具有滚动功能,导致冲突,使得用户在滚动时可能会遇到不确定的行为。本篇将详细介绍如何解决这种...

    listview_gridview

    在Android开发中,ListView和GridView是两种常用的列表控件,它们可以用来展示大量数据并支持滚动操作。然而,当ScrollView这种可滚动的容器试图嵌套ListView或GridView时,就会遇到滑动冲突的问题。ScrollView默认...

    ScrollView,嵌套ListView,ListView并同时嵌套GridView

    3. 自定义滚动行为:通过重写ListView或GridView的onInterceptTouchEvent和onTouchEvent方法,来手动处理滑动事件。 4. 使用Headerview和Footerview:如果只是在ListView的顶部或底部需要添加ScrollView,可以考虑...

    ScrollView和ListView和GridView冲突解决之自定义ListView和GridView

    2. **自定义ListView和GridView**:通过重写onInterceptTouchEvent和onTouchEvent方法,实现事件分发,让ListView或GridView优先处理触摸事件,只有在其无法处理时才传递给NestedScrollView。 3. **限制ScrollView的...

    ListView嵌套GridView问题

    在Android开发中,ListView和GridView是两种常用的列表控件,它们可以用来展示大量的数据并提供良好的用户体验。然而,当ListView嵌套GridView时,可能会遇到一些挑战。本篇将深入探讨这个话题,帮助开发者理解并...

    android_dragdropable_gridviewAndroid源代码

    `BaseAdapter`是用于为ListView、GridView等控件提供数据的接口,而`DragDropGridAdapter`则增加了拖放功能。它需要维护一个数据模型,并在拖放过程中更新数据项的顺序,同时通过调用`notifyDataSetChanged()`通知...

    ScrollView和ListView和GridView冲突解决

    3. 重写onInterceptTouchEvent和onTouchEvent方法:通过重写这两个方法,我们可以自定义事件分发逻辑,使得ScrollView在特定条件下不拦截触摸事件,让事件传递给ListView或GridView处理。 4. 使用Header或Footer:...

    ScrollView嵌套listview(gridview)

    当在一个ScrollView中嵌套ListView或GridView时,可能会遇到一些挑战和问题,这通常涉及到滚动冲突、性能优化和用户体验等方面。以下将详细介绍这些知识点。 ### 1. 滚动冲突 当ScrollView和ListView/GridView同时...

    ListView的下拉刷新源码

    首先,下拉刷新功能的核心在于一个叫做PullToRefresh的库,它提供了对ListView、GridView等多种滚动视图的下拉刷新支持。这个库通过监听ListView的滚动事件,判断用户的滑动行为是否触发刷新动作。当用户下拉到...

    ViewFlipper and ListView

    然而,直接在 `ViewFlipper` 中嵌套 `GridView`、`ListView` 或 `ScrollView` 会遇到一些问题,比如触摸事件的处理不当,导致视图无法正常滑动。 本文将详细介绍如何解决在 `ViewFlipper` 中使用 `ListView` 时可能...

    横向滑动的ListView

    在Android开发中,ListView是一种非常常见的控件,用于展示大量数据列表。然而,标准的ListView是垂直滚动的,不支持横向滑动。但通过一些特殊的技术手段,我们可以将其改造为横向滑动的ListView,这就是所谓的"横向...

    Scollview与Listview的嵌套使用

    在Android开发中,ScrollView和ListView是两种常用的布局控件,分别用于实现滚动视图和可滚动列表。然而,当它们需要被嵌套使用时,可能会遇到一些常见的问题,如显示异常(只显示第一行)和滑动冲突。本文将详细...

    ScrollView GradView ListView 嵌套滑动冲突、item内容显示不全

    在Android开发中,嵌套滚动视图是一个常见的需求,但同时也常常伴随着一些技术挑战,比如ScrollView、GridView和ListView之间的滑动冲突以及ListView item内容显示不全的问题。本文将深入探讨这些问题,并提供有效的...

    listview上下左右滑动效果,类似一个excel表格

    在Android开发中,ListView是一种常见的视图组件,用于展示可滚动的列表数据。标题提到的“listview上下左右滑动效果,类似一个excel表格”实际上是在描述一个扩展了ListView功能的高级组件,使得用户不仅可以垂直...

    Android ScrollView 嵌套解决方案

    在Android开发中,ScrollView是一个常用的布局控件,用于展示可滚动的内容。然而,当一个ScrollView内嵌套另一个ScrollView时,可能会出现冲突和不期望的行为,因为两个可滚动的区域可能会相互干扰。这个问题在实际...

    解决ScrollView嵌套ListView问题的几种方案

    在Android开发中,ScrollView和ListView都是非常常见的视图组件。ScrollView用于提供可滚动的单个视图,而ListView则用于展示可滚动的多行数据。然而,当在一个ScrollView中嵌套一个ListView时,会遇到一系列的问题...

    Android中手势基础

    本文将深入探讨Android中手势基础,特别是如何在ScrollView、ListView、GridView和WebView等常见组件中实现左右滑动切换屏幕的功能。 一、Android手势基础 Android提供了一套完整的手势检测框架,主要包括 ...

    Android Scroll分析

    通常,ListView、RecyclerView等列表视图支持垂直滚动,而GridView或HorizontalScrollView则支持水平滚动。这些组件的核心是ViewGroup的onMeasure()和onLayout()方法,它们负责测量和布局子视图,以及...

Global site tag (gtag.js) - Google Analytics