View详解之二View的事件体系
2016-07-16 19:18
609 查看
这周接着上次的学习,继续来学习View相关知识,View的事件体系。
首先,掌握几个知识点。
左图中left 、top、right、bottom分别是四个顶点相对于父容器的值,
右图中当view反生平移时,view的top值是不会改变的x、y、transitionX、transitionY会反生改变,并且相对于父容器而言。
y = top + transitionY;x= left + transitionX;
当在屏幕上点击或者滑动时,可以得到手指的坐标,getX()getY()得到的是相对View的内部坐标,getRawX()getRawY()得到的是相对于整个屏幕的坐标。
VelocityTracker是用于 监听滑动速度的类,用法:
1.在action_down中获得VelocityTracker对象,并追踪该事件,
2.computeCurrentVelocity(1000)设定时间间隔,该类就是根据改时间间隔内手指滑动的像素点来计算速度。
3.不需要使用时,clear(),recycle();
GestureDector用于处理用户的单击,滑动,长按,双击等。
在View的ontouchevent中接管event事件,即可 处理触摸事件了。
作用和自己实现ontouchevent的事件处理类似。如果仅处理滑动可以自己处理ontouchevent,处理双击等事件可以使用GestureDector。
Scroller用于View的内部内容的滑动,使用有固定的模式,具体原理请看下文。
ScrollBy内部是调用ScrollTo实现基于现在位置滑动一定距离来实现相对滑动的,滑动的仅仅是view的内容
适用于内容滑动情况
前者实现的是绝对滑动,后者实现的是相对滑动,滑动的距离都是View内部的内容边界相对于view的边界的距离值,如果view边界在内容边界的正方向则mScrollX,mScrollY为正值,否则为负直。具体看图
所以Scrollto()参数的正负和坐标系一样,而View的mScrollX属性则是相反的。
2.动画
transitionX、transitionY属性实现平移,Vew的补间动画是不能真实移动View的,使用兼容库依然不能实现属性动画的真实移动
适合于复杂一点的滑动动画
3.改变View的布局参数
比如marginLeft属性等
适用于需要View进行交互的滑动
Scroller 本身不能实现滑动,他只是保存了滑动需要的参数,调用startScroll()然后invalidate()进行重绘,View的重绘会调用computeScroll()方法,该方法需要自己实现,需要在里面调用scroller的computeScrollOffset使用插值器根据持续时间和目的距离,计算当前需要滑动的滑动的距离,然后再次调用postinvalidate()实现重绘然后再次滑动小段距离,直到结束。
2.动画
动画本身具有弹性滑动的效果
可以使用属性动画的事件监听器实现View的滑动,在事件监听中使用scroll实现滑动
3.使用延时策略
延时可以用,handler、view的postdelayed()方法发送延时消息,并在消息处理中再次发送延时消息,实现循环。也可以使用线程的sleep()(一般是子线程),
比如幻灯片,
更多滑动方法请看
View和Viewgroup的事件处理中有这么几个重要的步骤:
diapatchTouchEvent(),
进行事件分发,如果时间传递到该view那么该方法一定会被调用,返回结果表示是否消耗该事件,
onInterceptTouchEvent()
返回结果用于判断是否拦截某个事件,如果当前View拦截了事件,那么在同一个事件序列中,此方法不会再次被调用。
onTouchevent()
用来处理点击事件,返回结果表示是否消耗当前事件。,如果不消耗那么同一事件序列,gaiview不会再次接收到事件,View默认是返回true,即消耗当前事件。
三个方法的处理过程如下伪代码:
ontouch()
用于在外部处理touch事件,优先级高于onTouchevent,如果返回true,则不会执行onTouchevent。
onclick在onTouchevent的action_up事件中执行。
点击事件的传递过程遵循的顺序:Activity->Window->ViewGroup->…->View;
如果最底层的View在onTouchevent中返回false,即不消耗该事件,那么该事件会返回到父容器的onTouchevent中处理,如果最后没被不消耗那么会返回到activity的onTouchevent中处理。
activity的dispatchTouchEvent将事件分发交个window处理,如果返回true则表示事件被消耗了,返回false则调用调用其onTouchevent()。
window将事件分发给DecorView处理,
结论:
1.如果View==拦截==了onIntercept()某事件包括action_down,那么后续的事件都直接由它处理onTouchevent。onIntercept也不会在被调用是否需要拦截了。
2.如果某个view==不消耗==action_down事件,那么后续事件都不会传递给该View,而是有父容器的onTouchevent处理。默认是会消耗的。
3.view可以干预通过requestDisAllowIntercepttouchevent()方法干预父容器的事件拦截,但不能作用于ViewGroup对Action_down事件的拦截,因为每次都会重置该标志位。拦截了action_down,后续的事件都会被拦截
4.onIntercept不是一定会被调用的,ontouchevent也是,如果要提前对事件处理应该在dispatch中进行。
1.水平和竖直方向交叉,冲突
2.水平和水平同方向冲突
3.多层嵌套冲突
viewpager 处理了类似场景一的情况,重点在==滑动规则的确定==
场景一,可以采用判断滑动方向的方式进行拦截,比如,与水平方向的夹角,水平和竖直方向的距离差,或者速度差
场景二,可以根据业务上判断,在某个条件下需要内部拦截滑动,某些条件需要外部拦截。
场景三,则复用前两者。
这里用到事件分发机制,进行内部和外部事件处理的切换。
思想:父容器在需要时拦截事件,不需要时不拦截,由子View处理,子view不需要判断
实现:
复写父容器的onInterceptTouchEvent()方法。
action_down返回false,否则将拦截子view的全部事件,
action_move,中判断是否需要拦截,需要的话返回true,
action_up,返回false,父容器只要拦截了up事件之前的事件就可以得到up事件,所以不需要拦截up,如果拦截子view的up事件得不到处理,不能处理click事件。
伪代码:
2.内部反拦截法
思想:父容器拦截move和up事件不能拦截action_down,该事件不受flag_disallow_intercept标志位控制,子view根据需要使用requestDisallowInterepttouchEvent()允许和禁止父容器的拦截。
实现:
子View复写dispatchTouchEvent(),在move事件中判断事件有子容器还是父容器需要,如果子View需要则调用parent.requestDisallowInterceptEvent(true),否则传入false。
父View复写onInterceptTouchEvent()拦截除down以外的事件。
好了,原理先到这。
首先,掌握几个知识点。
View的位置参数
先上图,左图中left 、top、right、bottom分别是四个顶点相对于父容器的值,
右图中当view反生平移时,view的top值是不会改变的x、y、transitionX、transitionY会反生改变,并且相对于父容器而言。
y = top + transitionY;x= left + transitionX;
当在屏幕上点击或者滑动时,可以得到手指的坐标,getX()getY()得到的是相对View的内部坐标,getRawX()getRawY()得到的是相对于整个屏幕的坐标。
touchslop 、velocityTracker、GestureDetector、Scroller
touchslop是系统认定触摸事件为滑动的最小距离VelocityTracker是用于 监听滑动速度的类,用法:
1.在action_down中获得VelocityTracker对象,并追踪该事件,
2.computeCurrentVelocity(1000)设定时间间隔,该类就是根据改时间间隔内手指滑动的像素点来计算速度。
3.不需要使用时,clear(),recycle();
GestureDector用于处理用户的单击,滑动,长按,双击等。
在View的ontouchevent中接管event事件,即可 处理触摸事件了。
作用和自己实现ontouchevent的事件处理类似。如果仅处理滑动可以自己处理ontouchevent,处理双击等事件可以使用GestureDector。
Scroller用于View的内部内容的滑动,使用有固定的模式,具体原理请看下文。
View的滑动的几种方法
1.ScrollTo、ScrollByScrollBy内部是调用ScrollTo实现基于现在位置滑动一定距离来实现相对滑动的,滑动的仅仅是view的内容
适用于内容滑动情况
前者实现的是绝对滑动,后者实现的是相对滑动,滑动的距离都是View内部的内容边界相对于view的边界的距离值,如果view边界在内容边界的正方向则mScrollX,mScrollY为正值,否则为负直。具体看图
所以Scrollto()参数的正负和坐标系一样,而View的mScrollX属性则是相反的。
2.动画
transitionX、transitionY属性实现平移,Vew的补间动画是不能真实移动View的,使用兼容库依然不能实现属性动画的真实移动
适合于复杂一点的滑动动画
3.改变View的布局参数
比如marginLeft属性等
适用于需要View进行交互的滑动
滑动之实现弹性滑动
1.ScrollerScroller 本身不能实现滑动,他只是保存了滑动需要的参数,调用startScroll()然后invalidate()进行重绘,View的重绘会调用computeScroll()方法,该方法需要自己实现,需要在里面调用scroller的computeScrollOffset使用插值器根据持续时间和目的距离,计算当前需要滑动的滑动的距离,然后再次调用postinvalidate()实现重绘然后再次滑动小段距离,直到结束。
2.动画
动画本身具有弹性滑动的效果
可以使用属性动画的事件监听器实现View的滑动,在事件监听中使用scroll实现滑动
3.使用延时策略
延时可以用,handler、view的postdelayed()方法发送延时消息,并在消息处理中再次发送延时消息,实现循环。也可以使用线程的sleep()(一般是子线程),
比如幻灯片,
更多滑动方法请看
View的事件分发机制
View的事件分发是View的核心也是难点,弄懂了这个才能学会滑动事件的冲突处理。View和Viewgroup的事件处理中有这么几个重要的步骤:
diapatchTouchEvent(),
进行事件分发,如果时间传递到该view那么该方法一定会被调用,返回结果表示是否消耗该事件,
onInterceptTouchEvent()
返回结果用于判断是否拦截某个事件,如果当前View拦截了事件,那么在同一个事件序列中,此方法不会再次被调用。
onTouchevent()
用来处理点击事件,返回结果表示是否消耗当前事件。,如果不消耗那么同一事件序列,gaiview不会再次接收到事件,View默认是返回true,即消耗当前事件。
三个方法的处理过程如下伪代码:
boolean dispatchTouchEvent(){ boolean consume = false; if(onInterceptTouchEvent){ consume = onTouchEvent(); }else{ consume = child.dispatchTouchEvent(); } return consume; }
ontouch()
用于在外部处理touch事件,优先级高于onTouchevent,如果返回true,则不会执行onTouchevent。
onclick在onTouchevent的action_up事件中执行。
点击事件的传递过程遵循的顺序:Activity->Window->ViewGroup->…->View;
如果最底层的View在onTouchevent中返回false,即不消耗该事件,那么该事件会返回到父容器的onTouchevent中处理,如果最后没被不消耗那么会返回到activity的onTouchevent中处理。
activity的dispatchTouchEvent将事件分发交个window处理,如果返回true则表示事件被消耗了,返回false则调用调用其onTouchevent()。
window将事件分发给DecorView处理,
结论:
1.如果View==拦截==了onIntercept()某事件包括action_down,那么后续的事件都直接由它处理onTouchevent。onIntercept也不会在被调用是否需要拦截了。
2.如果某个view==不消耗==action_down事件,那么后续事件都不会传递给该View,而是有父容器的onTouchevent处理。默认是会消耗的。
3.view可以干预通过requestDisAllowIntercepttouchevent()方法干预父容器的事件拦截,但不能作用于ViewGroup对Action_down事件的拦截,因为每次都会重置该标志位。拦截了action_down,后续的事件都会被拦截
4.onIntercept不是一定会被调用的,ontouchevent也是,如果要提前对事件处理应该在dispatch中进行。
View对点击事件的处理
首先执行onTouch()如果返回false执行onTouchevent(),会执行performclick执行点击事件,设置了点击事件之后会自动将View设为clickable;View的滑动冲突
滑动冲突的场景:1.水平和竖直方向交叉,冲突
2.水平和水平同方向冲突
3.多层嵌套冲突
viewpager 处理了类似场景一的情况,重点在==滑动规则的确定==
场景一,可以采用判断滑动方向的方式进行拦截,比如,与水平方向的夹角,水平和竖直方向的距离差,或者速度差
场景二,可以根据业务上判断,在某个条件下需要内部拦截滑动,某些条件需要外部拦截。
场景三,则复用前两者。
这里用到事件分发机制,进行内部和外部事件处理的切换。
通用的滑动冲突处理方法
1.外部拦截法:思想:父容器在需要时拦截事件,不需要时不拦截,由子View处理,子view不需要判断
实现:
复写父容器的onInterceptTouchEvent()方法。
action_down返回false,否则将拦截子view的全部事件,
action_move,中判断是否需要拦截,需要的话返回true,
action_up,返回false,父容器只要拦截了up事件之前的事件就可以得到up事件,所以不需要拦截up,如果拦截子view的up事件得不到处理,不能处理click事件。
伪代码:
boolean onInterceptTouchEvent(){ boolean intercepted = false; int x = event.getx(); int y = event.getY(); switch(event.getAction()){ case action_down: intercepted = false; break; case action_move: if(需要父容器拦截) intercepted= true; else{ intercepted = false; } break; case action_up: intercepted = false; break; } lastX=x; lastY = y; return intercepted; }
2.内部反拦截法
思想:父容器拦截move和up事件不能拦截action_down,该事件不受flag_disallow_intercept标志位控制,子view根据需要使用requestDisallowInterepttouchEvent()允许和禁止父容器的拦截。
实现:
子View复写dispatchTouchEvent(),在move事件中判断事件有子容器还是父容器需要,如果子View需要则调用parent.requestDisallowInterceptEvent(true),否则传入false。
父View复写onInterceptTouchEvent()拦截除down以外的事件。
好了,原理先到这。
相关文章推荐
- AndroidStudio的安装配置&&注意要点
- Android实现静默安装与卸载
- android开源框架
- Android开发---Vitamio框架的实战应用
- Android开发学习笔记:5大布局方式详解
- Android RecyclerView 使用例子
- Android 计算器解析(三): 美化计算器界面
- Android异常-android.view.WindowManager$BadTokenException: Unable to add window -- token android.os.Bin
- android note
- Android开发笔记(一百一十一)聊天室中的Socket通信
- 自学之——SurfaceView
- Android 通过拍照或相册选择图片并裁剪(精简版)
- Android中拍照和从相册选择图片,并剪裁(二)
- Android-Parcelable理解与使用(对象序列化)
- 自定义ProgressBar
- Android电池检测(2)
- Android中xml文件的解析
- Android基础——NFC标签初始化设置、NFC标签读写数据和获取ID、NFC标签前台调度系统
- Android笔记--整理我所理解的Touch 事件分发机制
- Android入门——数据存储之SharedPreferences详解与应用