在android 界一直流传着一句话,谁能掌握view,谁就能掌握android(这是我自己装比说的)
不过view 这一块在android中 确实是 由普通android 向高级android 进发的必须要走的一步路,我准备 一边学,一边记录自己 学习view 的历程,今天 先来讲讲 view 的事件分发机制--
先看一张图:
这张图大家应该都很熟悉, 所谓的事件分发机制 无非就是 view的 相关触摸事件的传递过程,
比如我点击一下屏幕,图中各view 的 触摸事件分别是如何 响应的,分别有哪些事件会得到响应,
废话不多话,代码撸起来:
Viewgroup:A
public class OneView extends LinearLayout {
public OneView(Context context) {
super(context);
}
public OneView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.e("TEST", "OneView dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.e("TEST", "OneView onInterceptTouchEvent");
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e("TEST", "OneView onTouchEvent");
return super.onTouchEvent(event);
}
}
Viewgroup:B
public class TwoView extends LinearLayout {
public TwoView(Context context) {
super(context);
}
public TwoView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.e("TEST", "TwoView dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.e("TEST", "TwoView onInterceptTouchEvent");
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e("TEST", "TwoView onTouchEvent");
return super.onTouchEvent(event);
}
}
View:C
public class ThreeView extends View {
private Paint paint;
private int Withs;
private int Hiths;
public ThreeView(Context context) {
super(context);
}
public ThreeView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
Withs = w / 2;
Hiths = h / 2;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
paint = new Paint();
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.FILL); //设置画笔模式为填充
paint.setStrokeWidth(10f); //设置画笔宽度为10px
paint.setTextSize(30);
canvas.drawText("我是帅比", Withs, Hiths, paint);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.e("TEST", "ThreeView dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e("TEST", "ThreeView onTouchEvent");
return super.onTouchEvent(event);
}
}
当我点金 C 的时候,事件的调用log分别为:
很清楚 ,调用流程依次为: 最外层view A:dispatchTouchEvent---> onInterceptTouchEvent ---->..........
如果这个点击事件从最外层到最里层都没有被 拦截消费(即return true),则会继续从最底层的view 的 onTouchEvent 事件 往上层的 onTouchEvent 事件传递,直到被消费,如果 上层也没有消费,则此次点击事件就被流放,说到这里,其实 这并不是 所有的布局,在我们 肉眼到的布局之外,还包着有另外的布局,比如 标题栏什么的,那么这些view 在哪里:
如下图:
Rootview 相当于我们布局的根布局 LinearLayout,在其之上 的View结构中莫名多出来的两个东西,PhoneWindow
和 DecorView
,这两个我们并没有在Layout文件中定义过,但是为什么会存在呢?
仔细观察上面的 layout 文件,你会发现一个问题,我在 layout 文件中的最顶层 View(Group) 的大小并不是填满父窗体的,留下了大量的空白区域,由于我们的手机屏幕不能透明,所以这些空白区域肯定要显示一些东西,那么应该显示什么呢?
有过安卓开发经验的都知道,屏幕上没有View遮挡的部分会显示主题的颜色。不仅如此,最上面的一个标题栏也没有在 layout 文件中,这个标题栏又是显示在哪里的呢?
你没有猜错,这个主题颜色和标题栏等内容就是显示在
DecorView
中的。
现在知道 DecorView
是干什么的了,那么PhoneWindow
又有什么作用?
要了解 PhoneWindow 是干啥的,首先要了解啥是 Window ,看官方说明:
Abstract base class for a top-level window look and behavior policy. An instance of this class should be used as the top-level view added to the window manager. It provides standard UI policies such as a background, title area, default key processing, etc.
简单来说,Window是一个抽象类,是所有视图的最顶层容器,视图的外观和行为都归他管,不论是背景显示,标题栏还是事件处理都是他管理的范畴,它其实就像是View界的太上皇(虽然能管的事情看似很多,但是没实权,因为抽象类不能直接使用)。
而 PhoneWindow 作为 Window 的唯一亲儿子(唯一实现类),自然就是 View 界的皇帝了,PhoneWindow 的权利可是非常大大,不过对于我们来说用处并不大,因为皇帝平时都是躲在深宫里面的,虽然偶尔用特殊方法能见上一面,但想要完全指挥 PhoneWindow 为你工作是很困难的。
而上面说的 DecorView 是 PhoneWindow 的一个内部类,其职位相当于小太监,就是跟在 PhoneWindow 身边专业为 PhoneWindow 服务的,除了自己要干活之外,也负责消息的传递,PhoneWindow 的指示通过 DecorView 传递给下面的 View,而下面 View 的信息也通过 DecorView 回传给 PhoneWindow。
这些额外的就就到这里,有兴趣的可以自行谷歌,我们继续 我们的事件分发机制,
如最上面在没有任何拦截的时候,事件分发可谓是一马平川,那么如果我们想搞点事情呢,
比如,我在B中拦截掉这个点击事件,我们看看 事件是如何分发的,
如图我在 onInterceptTouchEvent 中直接拦截了点击事件,那么现在的传递是怎么样呢?
如下:
果然 事件没有继续往下传递到C,直接触发了 B view 的onTouchEvent,由于 B中的 onTouchEvent 也没有消费此次点击事件,故会继续往上层的 onTouchEvent 事件传递,
如果我们 在B 的onTouchEvent 中 消费了此次事件呢,又会如何呢,
我们把 如图:
onTouchEvent 的返回改为true,表示消费此次点击事件,结果调用方法如下:
onTouchEvent 走了三次,为什么会走三次,因为
onTouchEvent有3个回掉,分别为
MotionEvent.ACTION_DOWN:
MotionEvent.ACTION_MOVE:
MotionEvent.ACTION_UP:
按下,移动,抬手,
讲到这里,我想大家基本明白事件的分发机制是如何的了