最近年底了,打算把自己的Android知识都整理一下。
Android技能书系列:
Android基础知识
Android技能树 — 动画小结
Android技能树 — View小结
Android技能树 — Activity小结
Android技能树 — View事件体系小结
Android技能树 — Android存储路径及IO操作小结
Android技能树 — 多进程相关小结
Android技能树 — Drawable小结
数据结构基础知识
Android技能树 — 数组,链表,散列表基础小结
Android技能树 — 树基础知识小结(一)
算法基础知识
Android技能树 — 排序算法基础小结
这次是讲View的事件体系。特别是不同情况下的事件分发,我会用很简单的方式教会大家。
还是老样子,先上脑图,然后具体一块块详细说明。
脑图链接:View事件体系
View事件体系
我们通过具体案例来学习
比如我们现在的需求是这样的:界面上有一个按钮,我们的手指点击这个按钮后滑动,这个按钮可以跟着我们的手指一起滑动。(桌面的一些小的清理垃圾的悬浮窗的操作差不多,明白了吧)
具体实现可以看我以前写过的文章,十分简单: 小Demo大知识-控制Button移动来学Android坐标
我们来分析,既然按钮可以跟着我们手指滑动,我们肯定是不停告诉按钮,当前你的位置是哪里,既然涉及到一些基本知识点,比如View的位置参数等等。
这里我配上一张图,更清楚的来说明这些获取各自参数的值的说明:
看了这个图,是不是马上很清楚了。
注意点:
这里要说明一个误区,我面试一些初级水平安卓,我说ViewGroup里面有个View,这个View的getLeft(),getTop(),getTop(),getBottom()
是什么,让他画给我看下,有些人会给下面这个答案:
错误的回答
这是错误的答案,而且根据正确的描述图,我们可以通过getLeft(),getTop(),getTop(),getBottom()
来获取相应的View的宽高:
width = getRight() - getLeft();
height = getBottom() - getTop();
复制代码
MotionEvent是什么,单独问大家可能有点懵逼,我们来写下我们平常经常写的设置触摸的监听方法:
view.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return false;
}
});
复制代码
有没有发现,里面传递过来的参数就有MotionEvent
。
我们可以看到,MotionEvent是触屏事件。当用户触摸屏幕时将产生触屏事件,事件相关细节(发生触摸的位置、时间、历史记录、手势动作等)被封装成MotionEvent对象。
具体的介绍真的很多,百度一搜一大把。要细讲实在太多了。这里不多介绍了。
特别提示!!!
很多人会把上面我们提到过的view.getX/Y(),view.getRawX/Y()
和这里的motionvent.getX/Y(),motionevent.getRawX/Y()
弄混。这里是有差别的。我再画个图来明确下二者的区别。
所以区别是:
ps:所以面试官问你getX/Y()和getRawX/Y()的时候,一定问清楚是问的哪个。!不然很容易回答错误。
TouchSlop是系统所能识别出来的被认为滑动的最小的距离。如果你手指在屏幕上滑动的时候小于这个值,系统就认为你不是滑动。
滑动时候我们可能还要监听速度,比如说我们的需求就是滑动的快和滑动慢,移动的最终距离不同等。这时候我们一定要知道当前用户在N时间段内的速度到底是什么。这时候我们就需要速度(Velocity)追踪者(Tracker)。
我们先来看看英文翻译:
没错,既然你在屏幕上操作,你可能是划来划去,可能是单击,可能是双击。很多情况。所以这个类就可以帮我们来监听不同的操作。
在GestureDetector前面添加了一个Scale。
那就明显是比例的手势监测,通俗来说就是放大缩小的手势监测。
比如我们的需求是在查看图片的时候,可以二个手指放大缩小图片,那我恩就可以用这个ScaleGestureDetector
来监测。十分方便。
附上我以前写过的文章:图片操作系列 —(1)手势缩放图片功能
其实这二个算是基础知识。
接下去我会用一个真实的例子带你们更好的理解事件分发,如果讲的不合理,可以提出来哦✧(≖ ◡ ≖✿)
举个例子:
PS:(如果例子不适合,大家可以评论反馈。因为如果例子不适合反而误导了读者,反而是我的问题了。)
好比你们公司是一个软件外包公司,现在有个客户手点了一下鼠标发给你们老板一封邮件,说要开发这么一个APP。你们老板是不是会一层层的分发下去,老板 ——> 主管 ——>开发人员。
额外提到点:
所以对比下知道是不是发现跟我们的Activity,ViewGroup,View
很像:
PS:当收到触摸事件传递到某个层的时候,这个的dispactchEvent会被调用。(相当于上面接受到通知任务的时候会运行这个方法)
老板 - Activity: 有收到通知的能力,所以会调用dispatchTouchEvent(),然后因为他可以去通知主管,所以是
客户通知老板你有项目了。老板的dispatchEvent()会被调用。
老板.dispatchTouchEvent(){
//老板先通知主管去处理,
如果主管给的回复是:老板你不用管接下去的事。我们会处理的。
if(主管.dispatchTouchEvent()){
return true;//就直接结束了。
}
//手下的人说这个app开发不了,只能老板出马做事(跟客户去沟通去)
return 老板.onTouchEvent();
}
复制代码
所以只有dispatchEvent()
和onTouchEvent()
方法。
主管 - ViewGroup
老板通知了主管有个app要你们部门去开发。主管的dispatchTouchEvent()会被调用
主管.dispatchTouchEvent(){
//主管把这个活拦下来准备自己来开发这个app
if(主管.interceptTouchEvent()){
return 主管.onTouchEvent();//主管也有做事能力
}else{
//主管不拦截,主管也可以去通知开发人员,
//如果开发人员回馈说主管你别管了。我们这个app能做好
if(开发人员.dispatchTouchEvent()){
return true; //直接就结束了。
}else{
//如果手下的开发人员也反馈给主管说搞不定。
//就只能主管自己出来做事了。
return 主管.onTouchEvent();
}
}
}
复制代码
所以有dispatchTouchEvent()、interceptTouchEvent()、onTouchEvent()
。
开发人员 - View
主管通知了开发人员有个app要开发。开发人员的dispatchTouchEvent()会被调用
开发人员.dispatchTouchEvent(){
return 开发人员.onTouchEvent();
}
复制代码
所以有dispatchTouchEvent()、onTouchEvent()
。
我知道大家一定看到过类似下面的这种图:
很多人都会死记硬背的去记下来,说return true/false/super等不同情况下不同的调用流程。但是这样其实很不好记住的。很多人会问我是怎么记住的,我就是用伪代码来帮忙记住,什么事伪代码,上面那种表达方式就是伪代码。我们现在正是来看具体的伪代码。
Activity的真实代码:
public boolean dispatchTouchEvent(MotionEvent ev){
if(ev.getAction == MotionEvent.ACTION_DOWN){
onUserInteraction();
}
/**
调用window的superDispatchTouchEvent方法,
然后再调用下面的ViewGroup(DecorView)的dispatchTouchEvent()方法。
我们就直接这么想,这里就Activity通知了ViewGroup的dispatchTouchEvent方法。
1.如果这里getWindow.superDispatchTouchEvent()返回了true,
这时候就会执行return true语句。
2.如果这里getWindow.superDispatchTouchEvent()返回了false,
这时候就会执行return onTouchEvent(ev);这句,
所以只有当上面的if语句返回false,
才有机会调用Activity自己的onTouchEvent()方法。
*/
if(getWindow.superDispatchTouchEvent()){
return true;
}
return onTouchEvent(ev);
}
复制代码
所以很多人会所你重写Activity的dispatchTouchEvent()方法,返回true/false,都直接结束了事件。返回super才能正常分发,这个说法是不合理的。实际应该这么描述:
默认重写Activity的dispatchTouchEvent
方法:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
/**
其实是调用了super.dispatchTouchEvent方法,
才会调用上面我们贴出的Activity的dispatchTouchEvent方法,
才能继续把事件分发下去。
*/
return super.dispatchTouchEvent(ev);
}
复制代码
而大家通俗上说返回true/false
就事件结束,是因为没有调用了super.dispatchTouchEvent(ev);
。所以就不会分发下去,也就事件结束了。
那假如我这么写呢:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
super.dispatchTouchEvent(ev);
return true/false;
}
复制代码
没错,事件也是一样会分发下去。子View的方法也会被调用,而不会说直接结束了。
ViewGroup
因为上面我们已经说过了getWindow.superDispatchTouchEvent()
可以直接理解为是去调用了ViewGroup的dispatchTouchEvent()
;
ViewGroup的伪代码:
public boolean dispatchTouchEvent(MotionEvent ev){
/**
如果ViewGroup做了拦截,
则直接返回了ViewGroup的onTouchEvent()事件的结果。
*/
if(onInterceptTouchEvent(ev)){
return onTouchEvent(ev);
}else{
/**
如果ViewGroup不做拦截,则先分发给child,
看他们的反应,他们都不接受,则一定会返回false,
则只能ViewGroup自己去执行自己的onTouchEvent(ev);
*/
if(child.dispatchTouchEvent(ev)){
return true;
}else{
return onTouchEvent(ev);
}
}
}
复制代码
View的伪代码:
public boolean dispatchTouchEvent(MotionEvent ev){
/**
View 就返回自己的onTouchEvent()
*/
return onTouchEvent();
}
复制代码
可能很多人还是说我看了这些代码还是不懂啊,我连起来给你看,你就理解了。
这样,在不同情况下,返回不同的false/true,执行顺序就知道了。
额外补充:
《补充1》:
当然其实还有更复杂的情况,我们知道有ACTION_DOWN,ACTION_MOVE,ACTION_UP,ACTION_CANCEL
等,比如我们直接ViewGroup拦截Down事件,或者Down事件传递到了View后,我们在MOVE处再拦截,都会执行不同的:
《补充2》:
我们刚记不记得我们的View的伪代码是这样的:
public boolean dispatchTouchEvent(MotionEvent ev){
return onTouchEvent();
}
复制代码
其实上面是做了简化,其实除了onTouchEvent
,还有onTouch
事件和onClick
事件,我们继续用伪代码来说明规则:
public boolean dispatchTouchEvent(MotionEvent ev){
if(设置了TouchListener){
if(onTouch的返回值){
return true;
}else{
return onTouchEvent();
}
}
return onTouchEvent();
}
public boolean onTouchEvent(){
if(设置了ClickListener){
执行onClick;
}
.......
}
复制代码
既然我们学会了View的事件体系,很多人说那我学会了能怎么样,最明显的就是我们可以用来解决很多滑动冲突事件。因为我们可以根据实际需求,选择性的拦截,然后做自己的事件处理。
所以我们具体来看View的滑动有关的知识:
View的滑动的基本知识我就不特意提出来了。大家可以分别去搜索。
主要是第二块View的滑动冲突。我们就以最简单的外部左右滑动,内部上下滑动为例子。
外部左右滑动,内部上下滑动
比如我们规定,滑动的角度是N度以内的时候就是说明我们在内部滑动,角度是N度以外的时候是外部滑动。
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;
//必须不能拦截,否则后续的ACTION_MOME和ACTION_UP事件都会拦截。
break;
case MotionEvent.ACTION_MOVE:
if (父容器需要当前点击事件){
intercepted=true;
}else {
intercepted=false;
}
break;
case MotionEvent.ACTION_UP:
intercepted=false;
break;
default:
break;
}
mLastXIntercept=x;
mLastXIntercept=y;
return intercepted;
}
复制代码
子元素的dispatchTouchEvent()重写:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN: {
getParent().requestDisallowInterceptTouchEvent(true);
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastXIntercept;
int deltaY = y - mLastYIntercept;
if (父容器需要当前点击事件) {
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
}
case MotionEvent.ACTION_UP: {
break;
}
default:{
break;
}
}
mLastXIntercept = x;
mLastYIntercept = y;
return super.dispatchTouchEvent(ev);
}
复制代码
同时还要修改父容器的onInterceptTouchEvent()方法,不能做拦截,因为如果刚开始DOWN就拦截了,后面的MOVE,UP都没机会到子元素的上面的代码。
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getAction();
if (action == MotionEvent.ACTION_DOWN) {
return false;
} else {
return true;
}
}
复制代码
欢迎大家查看纠正,?。。。。让吐槽来的更猛烈些吧。