Android从零开搞系列:自定义View(9)事件分发+事件拦截(滑动冲突)
转载请注意:http://blog.csdn.net/wjzj000/article/details/55224843
我和一帮应届生同学维护了一个公众号:IT面试填坑小分队。旨在帮助应届生从学生过度到开发者,并且每周树立学习目标,一同进步!
写在前面
今天用了一天的时间去再一次梳理了一遍,事件分发和事件拦截。用了这么长时间倒不是因为理解深刻,,而是顺便看了3部电影。
关于分发和拦截这一块其实百度一下会出来很多很高能很不错的文章。
那么这里更多是自己不靠任何参考,直接整理一遍并且记录下来。
事件分发
一些概念
当我们直接或间接继承ViewGroup
- 涉及事件的方法有:
- dispatchTouchEvent:事件分发
- onInterceptTouchEvent:事件拦截
- onTouchEvent:事件处理
因为是控件组,所有就有了事件分发这么一个概念,因为只有有了层级关系,那么才存在分发。
先让我们想象这么一个场景:控件A中包含控件B,控件B包含控件C。重写每个控件中的那三个方法,并且返回值都不做特别处理(简单直接调用super)。
然后我们就可以看到相应的调用顺序。
我们会看到:首先A的拦截事件,然后事件到达了B,B同样一次调用拦截事件,然后传给了C,而这里C依次完成了拦截,和onTouch以及onTouchEvent。
当然这里我们也可以setOnCLick。调用onClick比较有意思。各位看官看可以自己试一试,通过不同的返回值可以控制是否触发onClick。
我们知道onTouch的调用和我们手指的移动有着直接的关系,按下(down),移动(move),抬起(up)。
每一次和手指有关的动作正常情况下都会触发onTouch。而我们的onClick只会在抬起(up)的时候才会触发,因此我们可以通过这一点解释很多的现象。
比如我经常使用的一个操作:触到一个不想按的区域,不抬起,移开点击区域再抬起,那么不会回触发点击效果。这就是上诉的那个原因。
如何理解这三个方法
这里非常的好理解,接下来我们看一看。如果我们手动控制返回值,会是什么效果。
我们只需要结合这3个方法的名字就可以很好的理解:
我们把分发,拦截和处理分别想象某次战役中的通讯兵,指挥官和前线士兵。首先是从A中的通讯兵,也就是我们的分发(dispatchTouchEvent)获得前线情报(方法返回值为true),它作为A兵团的通讯兵当然要先报告给A兵团的指挥官( 拦截 onInterceptTouchEvent),A团指挥官发现这个问题太简单,不应该是我们高级兵团A所执行的任务,因此决定不拦截这个情报。
因此这个情报就被传给了下级兵团B兵团,那么这个过程将被在B兵团重复,这里B兵团的指挥官也感觉任务太简单不做拦截。
那么任务就传给了C兵团,不过这里的C兵团不是直接或间接的继承ViewGroup因此他们就输入最前线的作战兵团,没有指挥官。
C团通讯兵接到B团指挥官的命令之后,只能传递给C团的前线士兵( 事件处理 onTouchEvent)。这里前线士兵如果返回true,那么表示接受上级命,立刻开赴召唤师峡谷执行任务。接下来和这个任务有关的事件将会源源不断的通过各级通讯兵传递过来。如果返回false,表示这个锅前线战士不替上级指挥官背锅,那么这个时候就由上级兵团(B兵团)的前线战士决定是不是处理这个任务。如果返回true,没毛病,处理任务,人在塔在。
如果返回false,任务事件依旧往上传,传给上级兵团(A兵团)的前线士兵…那如果依旧false不处理,那么……老铁没毛病,还得给你双击转发评论666。这个事件就丢失了…
当某个前线士兵选择了处理也就是返回true,那么接下来的事件便会通过层层的指挥官下达到这个前线士兵的身上。
当我们直接或间接继承View
- 涉及事件的方法有:
- dispatchTouchEvent:事件分发
- onTouchEvent:事件处理
区别仅仅是没了拦截事件,很好理解。你就一个前线士兵你还要拦截?要啥自行车,咋不上天呢。
各方法的效果基本和上边很类似,所以就不多累述了。
滑动冲突
关于滑动冲突,其实也是比较好理解:
核心很直接就是这个名词的拆分:滑动+冲突。想象一个最简单的情况:如果我们有一个允许上下滑动的控件,而它的内部包含了一个看可以上下滑动的控件。那么这里必然就出现矛盾了!都是上下滑动,你说我们先滑动那个?因此我们的滑动冲突就产生了。
知道这么个原因,那么解决方法也就应际而生:结合我们上述的事件分发。在合适的时机对事件进行合适的分发与处理。
这里我们通过一个效果来,看处理:
代码如下:
public class MyLinearLayout extends LinearLayout { private int mMove; private int yDown, yMove; private int i = 0; private boolean isIntercept = false; public MyLinearLayout(Context context) { super(context); } public MyLinearLayout(Context context, AttributeSet attrs) { super(context, attrs); } public MyLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } private ScrollView scrollView; @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); scrollView = (ScrollView) getChildAt(0); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { onInterceptTouchEvent(ev); return super.dispatchTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { int y = (int) ev.getY(); int mScrollY = scrollView.getScrollY(); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: yDown = y; break; case MotionEvent.ACTION_MOVE: yMove = y; if (yMove - yDown > 0 && mScrollY == 0) { if (!isIntercept) { isIntercept = true; } }else{ isIntercept= false; } break; } return isIntercept; } @Override public boolean onTouchEvent(MotionEvent event) { int y = (int) event.getY(); int mScrollY = scrollView.getScrollY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: yDown = y; break; case MotionEvent.ACTION_MOVE: yMove = y; if (yMove - yDown > 0 && mScrollY == 0) { mMove = yMove - yDown; i += mMove; layout(getLeft(), getTop() + mMove, getRight(), getBottom() + mMove); } break; case MotionEvent.ACTION_UP: layout(getLeft(), getTop() - i, getRight(), getBottom() - i); i = 0; break; } return true; }}
XML:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <ImageView android:layout_width="match_parent" android:layout_height="200dp" android:scaleType="fitXY" android:src="@drawable/bg" /> <com.example.mbenben.studydemo.layout.intercepttouchevent.view.MyLinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="150dp" android:background="@color/white" android:orientation="vertical"> <ScrollView android:layout_width="match_parent" android:layout_height="wrap_content"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:layout_width="match_parent" android:layout_height="80dp" android:gravity="center" android:text="模拟一条操作界面" android:textColor="@color/black" android:textSize="18sp" /> //省略很多同样内容 <TextView android:layout_width="match_parent" android:layout_height="80dp" android:gravity="center" android:text="模拟一条操作界面" android:textColor="@color/black" android:textSize="18sp" /> <TextView android:layout_width="match_parent" android:layout_height="80dp" android:gravity="center" android:text="模拟一条操作界面" android:textColor="@color/black" android:textSize="18sp" /> <TextView android:layout_width="match_parent" android:layout_height="80dp" android:gravity="center" android:text="模拟一条操作界面" android:textColor="@color/black" android:textSize="18sp" /> LinearLayout> ScrollView> com.example.mbenben.studydemo.layout.intercepttouchevent.view.MyLinearLayout>RelativeLayout>
参考博客同效果代码:
public class MyParentView extends LinearLayout { private int mMove; private int yDown, yMove; private int i = 0; private boolean isIntercept = false; public MyParentView(Context context) { super(context); } public MyParentView(Context context, AttributeSet attrs) { super(context, attrs); } public MyParentView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } private ScrollView scrollView; @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); scrollView = (ScrollView) getChildAt(0); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { onInterceptTouchEvent(ev); return super.dispatchTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { int y = (int) ev.getY(); int mScrollY = scrollView.getScrollY(); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: yDown = y; break; case MotionEvent.ACTION_MOVE: yMove = y; if (yMove - yDown > 0 && mScrollY == 0) { if (!isIntercept) { yDown = (int) ev.getY(); isIntercept = true; } } break; case MotionEvent.ACTION_UP: layout(getLeft(), getTop() - i, getRight(), getBottom() - i); i = 0; isIntercept = false; break; } if (isIntercept) { mMove = yMove - yDown; i += mMove; layout(getLeft(), getTop() + mMove, getRight(), getBottom() + mMove); } return isIntercept; }}
滑动冲突处理代码就不做解释了。就是事件分发的一种应用。如果感觉有点懵逼的话,可以自己写个demo。跑一跑不同返回值的不同效果。
写在后边
这篇博客就暂时写这么多吧,以后有更好的收获在补上。
最后希望各位看官可以star我的GitHub,三叩九拜,满地打滚求star:
https://github.com/zhiaixinyang/PersonalCollect
https://github.com/zhiaixinyang/MyFirstApp
更多相关文章
- android平台上编程实现事件注入 part1
- 实现点击Item可让Item跳到屏幕中间的HorizontalScrollView
- android跨进程事件注入(程序模拟用户输入)
- android Button用户交互——监听机制调用过程
- setOntouchListener和setOnKeyListener
- Android监听ScrollView的滚动事件
- android ui事件处理分析
- React native 与Android原生交互方式(一)
- Android实现仿QQ5.0的侧滑效果