㈠ android 如何获取一个界面最顶层的view并处理单击事件的分发机制
android事件分发机制 就是一个触摸事件发生了,从一个窗口传递到一个视图,再传递到另外一个视图,最后被消费的过程,在android中还是比较复杂的传递流程如下:
(1) 事件从Activity.dispatchTouchEvent()开始传递,只要没有被停止或拦截,从最上层的View(ViewGroup)开始一直往下(子View)传递。子View可以通过onTouchEvent()对事件进行处理。
(2) 事件由父View(ViewGroup)传递给子View,ViewGroup可以通过onInterceptTouchEvent()对事件做拦截,停止其往下传递。
㈡ Android的handler机制的原理
Android的handler机制的原理分为异步通信准备,消息发送,消息循环,消息处理。
1、异步通信准备
在主线程中创建处理器对象(Looper)、消息队列对象(Message Queue)和Handler对象。
2、消息入队
工作线程通过Handler发送消息(Message) 到消息队列(Message Queue)中。
3、消息循环
消息出队: Looper循环取出消息队列(Message Queue) 中的的消息(Message)。
消息分发: Looper将取出的消息 (Message) 发送给创建该消息的处理者(Handler)。
4、消息处理
处理者(Handler) 接收处理器(Looper) 发送过来的消息(Message),根据消息(Message) 进行U操作。
handler的作用
handler是android线程之间的消息机制,主要的作用是将一个任务切换到指定的线程中去执行,(准确的说是切换到构成handler的looper所在的线程中去出处理)android系统中的一个例子就是主线程中的所有操作都是通过主线程中的handler去处理的。
Handler的运行需要底层的 messagequeue和 looper做支撑。
㈢ android activity怎样分发事件
现在让我们创建一个简单的Activity,创建一个TestLinearLayout继承自LinearLayout,创建一个Test继承自Button。
在TestLineaLayout类中,重写了和事件相关的代码,整个代码如下:
public class TestLinearLayout extends LinearLayout {
public TestLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.d("TestLinerLayout", "onInterceptTouchEvent action = " + ev.getAction());
return super.onInterceptTouchEvent(ev);
㈣ android 事件分发方法怎么走
当一个Touch事件(触摸事件为例)到达根节点,即Acitivty的ViewGroup时,它会依次下发,下发的过程是调用子View(ViewGroup)的dispatchTouchEvent方法实现的。简单来说,就是ViewGroup遍历它包含着的子View,调用每个View的dispatchTouchEvent方法,而当子View为ViewGroup时,又会通过调用ViwGroup的dispatchTouchEvent方法继续调用其内部的View的dispatchTouchEvent方法。上述例子中的消息下发顺序是这样的:①-②-⑤-⑥-⑦-③-④。dispatchTouchEvent方法只负责事件的分发,它拥有boolean类型的返回值,当返回为true时,顺序下发会中断。在上述例子中如果⑤的dispatchTouchEvent返回结果为true,那么⑥-⑦-③-④将都接收不到本次Touch事件。来个简单版的代码加深理解:
/**
* ViewGroup
* @param ev
* @return
*/
public boolean dispatchTouchEvent(MotionEvent ev){
....//其他处理,在此不管
View[] views=getChildView();
for(int i=0;i<views.length;i++){
//判断下Touch到屏幕上的点在该子View上面
if(...){
if(views[i].dispatchTouchEvent(ev))
return true;
}
}
...//其他处理,在此不管
}
/**
* View
* @param ev
* @return
*/
public boolean dispatchTouchEvent(MotionEvent ev){
....//其他处理,在此不管
return false;
}
㈤ 谁可以解释下,android事件分发为什么要设计成从根view到子view,而不是从子vie
Android事件传递流程在网上可以找到很多资料,FrameWork层输入事件和消费事件,可以参考:
Touch事件派发过程详解
这篇blog阐述了底层是如何处理屏幕输,并往上传递的。Touch事件传递到Activity的DecorView时,往下走就是ViewGroup和子View之间的事件传递,可以参考郭神的这两篇博客
Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
郭神的两篇博客清楚明白地说明了View之间事件传递的大方向,但是具体的一些晦暗的细节阐述较少,本文主要是总结这两篇博客的同时,侧重于两点:
事件分发过程中一些细节到底如何实现的?
子view到底如何和父View抢事件,父View又是如何拦截事件不发送给子View,以及如果我们需要处理这种混乱的关系才能让两者和谐相处?。
MotionEvent抽象
要明白View的事件传递,很有必要先说一下Touch事件是如何在Android系统中抽象的,这主要使用的就是MotionEvent。这个类经历了几次重大的修改,一次是在2.x版本支持多点触摸,一次是4.x将大部分代码甩给native层处理。
一次简单的事件
我们先举个栗子来说明一次完整的事件,用户触屏 滑动 到手机离开屏幕,这认为是一次完整动作序列(movement traces)。一个动作序列中包含很多动作Action,比如在用户按下时,会封装一个MotionEvent,分发给视图树,我们可以通过motionevent.getAction拿到这个动作是ACTION_DOWN。同样,在手指抬起时,我们可以接收到Action类型是Action_UP的MotionEvent。对于滑动(MOVE)这个操作,Android为了从效率出发,会将多个MOVE动作打包到一个MotionEvent中。通过getX getY可以获取当前的坐标,如果要访问打包的缓存数据,可以通过getHistorical**()函数来获取。
加入多点触摸
对于单点的操作来看,MotionEvent显得比较简单,但是考虑引入多点触摸呢?我们定义一个接触点为(Pointer)。每一个触摸点Pointer都会有一个当次动作序列的唯一Id和Index.MotionEvent中多个手指的操作API大部分都是通过pointerindex来进行的,如:获取不同Pointer的触碰位置,getX(int pointerIndex);获取PointerId等等。大部分情况下,pointerid == pointeridex。
我们从onTouch接受到一个MotionEvent,怎么拿到多个触碰点的信息?为了解开笔者刚开始学习这部分知识时的困惑,我们首先树立起一种概念:一个MotionEvent只允许有一个Action(动作),而且这个Action会包含触发这次Action的触碰点信息,对于MOVE操作来说,一定是当前所有触碰点都在动。只有ACTION_POINTER_DOWN这类事件事件会在Action里面指定是哪一个POINTER按下。
在MotionEvent的底层实现中,是通过一个16位来存储Action和Pointer信息(PointerIndex)。低8位表示Action,理论上可以表示255种动作类型;高8位表示触发这个Action的PointerIndex,理论上Android最多可以支持255点同时触摸,但是在上层代码使用的时候,默认多点最多存在32个,不然事件在分发的时候会有问题。
ACTION_DOWN OR ACTION_POINTER_DOWN:
这两个按下操作的区别是ACTION_DOWN是一个系列动作的开始,而ACTION_POINTER_DOWN是在一个系列动作中间有另外一个触碰点触碰到屏幕。
这部分详细的描述,请参考:
android触控,先了解MotionEvent
到这里,铺垫终于结束了,我们开始直奔主题。
View的事件传递
Android的Touch事件传递到Activity顶层的DecorView(一个FrameLayout)之后,会通过ViewGroup一层层往视图树的上面传递,最终将事件传递给实际接收的View。下面给出一些重要的方法。如果你对这个流程比较熟悉的话,可以跳过这里,直接进入第二部分。
dispatchTouchEvent
事件传递到一个ViewGroup上面时,会调用dispatchTouchEvent。代码有删减
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Attention 1 :在按下时候清除一些状态
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
//注意这个方法
resetTouchState();
}
// Attention 2:检查是否需要拦截
final boolean intercepted;
//如果刚刚按下 或者 已经有子View来处理
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// 不是一个动作序列的开始 同时也没有子View来处理,直接拦截
intercepted = true;
}
//事件没有取消 同时没有被当前ViewGroup拦截,去找是否有子View接盘
if (!canceled && !intercepted) {
//如果这是一系列动作的开始 或者有一个新的Pointer按下 我们需要去找能够处理这个Pointer的子View
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down
//上面说的触碰点32的限制就是这里导致
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
//对当前ViewGroup的所有子View进行排序,在上层的放在开始
final ArrayList<View> preorderedList = buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder
? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)
? children[childIndex] : preorderedList.get(childIndex);
// canViewReceivePointerEvents visible的View都可以接受事件
// isTransformedTouchPointInView 计算是否落在点击区域上
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
//能够处理这个Pointer的View是否已经处理之前的Pointer,那么把
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
} }
//Attention 3 : 直接发给子View
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
= true;
break;
}
}
}
}
}
// 前面已经找到了接收事件的子View,如果为NULL,表示没有子View来接手,当前ViewGroup需要来处理
if (mFirstTouchTarget == null) {
// ViewGroup处理
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
if() {
//ignore some code
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
}
}
return handled;
}
上面代码中的Attention在后面部分将会涉及,重点注意。
这里需要指出一点的是,一系列动作中的不同Pointer可以分配给不同的View去响应。ViewGroup会维护一个PointerId和处理View的列表TouchTarget,一个TouchTarget代表一个可以处理Pointer的子View,当然一个View可以处理多个Pointer,比如两根手指都在某一个子View区域。TouchTarget内部使用一个int来存储它能处理的PointerId,一个int32位,这也就是上层为啥最多只能允许同时最多32点触碰。
看一下Attention 3 处的代码,我们经常说view的dispatchTouchEvent如果返回false,那么它就不能系列动作后面的动作,这是为啥呢?因为Attention 3处如果返回false,那么它不会被记录到TouchTarget中,ViewGroup认为你没有能力处理这个事件。
这里可以看到,ViewGroup真正处理事件是在dispatchTransformedTouchEvent里面,跟进去看看:
dispatchTransformedTouchEvent
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
//没有子类处理,那么交给viewgroup处理
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
return handled;
}
可以看到这里不管怎么样,都会调用View的dispatchTouchEvent,这是真正处理这一次点击事件的地方。
dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) {
if (onFilterTouchEventForSecurity(event)) {
//先走View的onTouch事件,如果onTouch返回True
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
return result;
}
我们给View设置的onTouch事件处在一个较高的优先级,如果onTouch执行返回true,那么就不会去走view的onTouchEvent,而我们一些点击事件都是在onTouchEvent中处理的,这也是为什么onTouch中返回true,view的点击相关事件不会被处理。
㈥ android 事件分发中 为什么down事件不消费 后续事件也得不到消费
事件都是从dispatchTouchEvent开始的, 如果down事件不消费, 那么在dispatchTouchEvent源码中的mFirstTouchTarget就是空的. mFirstTouchTarget这个变量是记录消费down事件的. 没有消费down 那么mFirstTouchTarget就是空. 其他事件进入dispatchTouchEvent后 会先判断mFirstTouchTarget为空, 就判定事件被拦截了. 就不会继续事件分发了. 也不会去执行onTouchEvent方法了.
至于Android这样设计的道理? 因为如果down事件不接受, 就默认其他事件也不要了. 这也是为了提高性能, 毕竟move是一大串的. 如果不在down中这样设计. 每个move都去判断, 势必会影响触摸响应性能.
㈦ android怎么主动分发事件
Android中,所有的操作类型事件都由如下三个部分作为基础:
按下(ACTION_DOWN)
移动(ACTION_MOVE)
抬起(ACTION_UP)
这三部分都寄生于onTouch事件中,由MontionEvent类中定义的三个常量进行区分。
Android中与Touch事件相关的方法为:
removeTapCallback();
removeLongPressCallback();
break;
case MotionEvent.ACTION_MOVE:
final int x = (int) event.getX();
final int y = (int) event.getY();
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
removeTapCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();
setPressed(false);
}
}
break;
}
return true;
}
return false;
}
㈧ android事件分发中 怎么确定点击自己想点击的界面
android监听事件添加动作的三种方式:
第一种也是最开始就接触的方式,通常在activity组件的oncreate事件中直接定义,直接动作。这种方式每个控件都定义一次,通常不方便.
Button btn = (Button) findViewById(R.id.myButton);
btn .setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
//do something
}
});
第二种通常是在activity组件实现其接口,这样可以多外控件共享一个接口,这样相对方便
public class TestMedia extends Activity implements View.OnClickListener{
Button btn1 = (Button) findViewById(R.id.myButton1);
Button btn2 = (Button) findViewById(R.id.myButton2);
btn1 .setOnClickListener();
btn2 .setOnClickListener();
}
public void onClick(View v) {
switch (v.getId()) {
case R.id. myButton1:
//do something
break;
case R.id. myButton2:
//do something
break;
}
第三种类似第二种,这样的好处在于如果需要实现多个监听接口,这样更清晰
public class TestMedia extends Activity {
Button btn1 = (Button) findViewById(R.id.myButton1);
Button btn2 = (Button) findViewById(R.id.myButton2);
btn1 .setOnClickListener(new ClickEvent());
btn2 .setOnClickListener(new ClickEvent());
}
class ClickEvent implements View.OnClickListener
public void onClick(View v) {
switch (v.getId()) {
case R.id. myButton1:
//do something
break;
case R.id. myButton2:
//do something
break;
}
㈨ Android事件分发机制是怎么判断手势类型,并决定分发给哪个view的
可以参考如下内容:
dispatchTouchEvent -> onTouch -> onTouchEvent
等上述的up事件分发完结后,再调用onClick
看起来一目了然,事实上,我只是用了默认的调用,在TextView的dispatchTouchEvent和onTouchEvent都是让它返回super.xxx。而且在onTouch监听里返回的是false。
所以,实际的情况更多,基于此,为了更清晰地熟悉事件分发机制,我们只能通过看源码了。
源码分析
我目前查看的源码是API 22环境下的。这部分关于View的事件分发的源码和之前的有很大的区别,但是,万变不如其中,有些根本的逻辑流程一般是不会改变的。
我们直接看View的代码,因为控件都继承自View。
㈩ android点击事件分发机制中为什么不能直接通过拦截来确定是否执行当前View的onTouch方法
我们根据拦截返回的布尔值判断是否拦截,那么,你该在哪里写if else呢,不还是得另写一个方法判断吗,那不就是dispatchtouchevent