最近年末了,打算把本身的Android知识都整理一下。git
Android技能书系列:github
Android基础知识面试
Android技能树 — Android存储路径及IO操做小结app
数据结构基础知识
算法基础知识
此次是讲View的事件体系。特别是不一样状况下的事件分发,我会用很简单的方式教会你们。
仍是老样子,先上脑图,而后具体一块块详细说明。
脑图连接:View事件体系
咱们经过具体案例来学习
好比咱们如今的需求是这样的:界面上有一个按钮,咱们的手指点击这个按钮后滑动,这个按钮能够跟着咱们的手指一块儿滑动。(桌面的一些小的清理垃圾的悬浮窗的操做差很少,明白了吧)
具体实现能够看我之前写过的文章,十分简单: 小Demo大知识-控制Button移动来学Android坐标
咱们来分析,既然按钮能够跟着咱们手指滑动,咱们确定是不停告诉按钮,当前你的位置是哪里,既然涉及到一些基本知识点,好比View的位置参数等等。
这里我配上一张图,更清楚的来讲明这些获取各自参数的值的说明:
(!!!!!这里我多画了getRawX和getRawY方法,View是没有这二个方法的,请注意!!!!!)
(!!!!!这里我多画了getRawX和getRawY方法,View是没有这二个方法的,请注意!!!!!)
(!!!!!这里我多画了getRawX和getRawY方法,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()
和这里的motionvent.getX/Y()
弄混。这里是有差异的。我再画个图来明确下两者的区别。
因此区别是:
View的getX/Y()是指本身View的左上角相对于父View左上角的距离。MotionEvent的getX/Y()是指点击处离本身View的左上角的距离。
ps:因此面试官问你getX/Y()的时候,必定问清楚是问的哪一个。!否则很容易回答错误。
TouchSlop是系统所能识别出来的被认为滑动的最小的距离。若是你手指在屏幕上滑动的时候小于这个值,系统就认为你不是滑动。
滑动时候咱们可能还要监听速度,好比说咱们的需求就是滑动的快和滑动慢,移动的最终距离不一样等。这时候咱们必定要知道当前用户在N时间段内的速度究竟是什么。这时候咱们就须要速度(Velocity)追踪者(Tracker)。
咱们先来看看英文翻译:
没错,既然你在屏幕上操做,你多是划来划去,多是单击,多是双击。不少状况。因此这个类就能够帮咱们来监听不一样的操做。
在GestureDetector前面添加了一个Scale。
那就明显是比例的手势监测,通俗来讲就是放大缩小的手势监测。
好比咱们的需求是在查看图片的时候,能够二个手指放大缩小图片,那我恩就能够用这个ScaleGestureDetector
来监测。十分方便。
附上我之前写过的文章:图片操做系列 —(1)手势缩放图片功能
其实这二个算是基础知识。
接下去我会用一个真实的例子带大家更好的理解事件分发,若是讲的不合理,能够提出来哦✧(≖ ◡ ≖✿)
举个例子:
PS:(若是例子不适合,你们能够评论反馈。由于若是例子不适合反而误导了读者,反而是个人问题了。)
额外提到点:
大家老板收到了通知就是把这个任务分下去,不可能说第一反应先想一想说我要不要把这个任务拦下来本身作,不要叫手下的人去作了(否则还请大家干吗,请了大家还要每次想着要不要本身作)。因此他没有拦截功能,默认确定不会去拦截,确定第一反应就是直接给手下。
主管都是有权利把任务拦下来的,不给手下的人去作,能够本身处理,毕竟主管不仅是就分配下任务就够了,这么简单我也想去作主管,可能由于手下都有任务在作,忙不过来的时候,主管会本身去作一些开发任务。
最底层的开发人员,没有拦截功能,由于任务分到你这里了。你还能再给谁呢,拦了也是你作,不拦你又没有下级能够给背锅,仍是你作。
因此对比下知道是否是发现跟咱们的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》:
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;
}
}
复制代码
欢迎你们查看纠正,😉。。。。让吐槽来的更猛烈些吧。