(本文最早于2012-3-15 17:12日发表于QQ空间 进入我的空间并查找标题“玩转Android UI事件”)
研究了下 Android 可视组件的事件处理机制,本想用文字来阐述,但是发现太复杂,文字不太适合用来表达逻辑,遂改用程序代码来表述。读完本程序,你将会对 Android UI 事件处理机制有一个全新的认识。若能充分利用事件传递的这些特性,你写自定义组件就可以随心所欲,游刃有余了。
package com.test;
import android.view.MotionEvent;
import android.view.View;
/**
* Android可视组件的触摸事件传递是通过调用dispatchTouchEvent(MotionEvent event)
* 或dispatchKeyEvent(KeyEvent event)来实现事件的捕获、目标和冒泡三个阶段,调用是从最父层的组件开始,如Activity。
* 不过这里仅阐述了触摸点击事件,至于键盘事件则比较简单,暂不赘述。
*
* 这是一个对Android事件机制的模拟,读完本程序,你便明白了事件的处理过程。这里包含了几种不同组件中事件的传递和处理方式。
* 这里作了一个融合,而在实际API类当中,根据不同的类有不同的实现方法,具体请看源码,不过我没来得及看。
*
* 注意不要试图运行本程序,本程序只是表达了执行逻辑,个人认为用程序比用文字的方式表达的更加清晰。
* 程序是最好的语言嘛,对于程序员来说。
*/
public class Android事件模拟 {
/**是否应该向子组件传递事件**/
boolean childConsume = false;
/**本组件是Activity**/
boolean isActivity = true | false;
/**本组件是ViewGroup**/
boolean isViewGroup = true | false;
/**本组件是View而不是ViewGroup**/
boolean isView = true | false;
/**子组件是View(包括ViewGroup)**/
boolean childIsView = true | false;
/**有无子组件或者事件源是否在直接或间接子组件上**/
boolean hasChild = true | false;
/**模拟子视图组件**/
Child child = new Child();
/**外部监听器**/
View.OnTouchListener touchListener = null;
View.OnLongClickListener longClickListener = null;
View.OnClickListener clickListener = null;
/**本类对象,非Activity**/
View view = null;
/**默认状态下触摸事件的逻辑处理方法,当前对象层级视图组件触摸事件处理的入口**/
public boolean dispatchTouchEvent(MotionEvent event) {
/**本方法的返回值**/
boolean b = false;
if(isActivity) {//如果本对象是Activity。//完全由onTouchEvent()决定本方法的返回值
if(event.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
childConsume = hasChild ? true : false;
if(childConsume) {
if(child.dispatchTouchEvent(event)) {//注意子组件的dispatchTouchEvent(event)处理过程跟本方法相同
childConsume = true;
b = true;
}else {
childConsume = false;
b = onTouchEvent(event);
}
}else {
b = onTouchEvent(event);
}
}else if(childConsume) {//即使后续事件中child.dispatchTouchEvent(event)返回false,也继续传递
if(child.dispatchTouchEvent(event)) {
b = true;
}else {
b = onTouchEvent(event);
}
}else {
b = onTouchEvent(event);
}
}else if(isViewGroup) {//如果本对象是ViewGroup
boolean intercept = false;
if(event.getAction() == MotionEvent.ACTION_DOWN) {
intercept = onInterceptTouchEvent(event);//不管有没有子组件,本方法都将执行
childConsume = (hasChild && ! intercept) ? true : false;
if(childConsume) {
if(child.dispatchTouchEvent(event)) {
childConsume = true;
b = true;
}else {
childConsume = false;
if(touchListener != null) {
b = touchListener.onTouch(view, event);//调用外部监听器
}
if(!b) b = onTouchEvent(event);
}
}else {
childConsume = false;
if(touchListener != null) {
b = touchListener.onTouch(view, event);
}
if(!b) b = onTouchEvent(event);
}
}else if(event.getAction() == MotionEvent.ACTION_MOVE) {
if(childConsume) {
intercept = onInterceptTouchEvent(event);
if(intercept) {
childConsume = false;
event.setAction(MotionEvent.ACTION_CANCEL);//重点,后续事件被拦截,将变为取消事件并继续传递
child.dispatchTouchEvent(event);
b = true;//此时不论child.dispatchTouchEvent(event)返回什么值,本方法都直接返回true
}else {
b = child.dispatchTouchEvent(event);//即使返回false也直接返回,区别于Activity
}
}else {
if(touchListener != null) {
b = touchListener.onTouch(view, event);
}
if(!b) b = onTouchEvent(event);
}
}else if(event.getAction() == MotionEvent.ACTION_UP) {
if(childConsume) {
intercept = onInterceptTouchEvent(event);
if(intercept) {
childConsume = false;
event.setAction(MotionEvent.ACTION_CANCEL);//重点,后续事件被拦截,将变为取消事件并继续传递
child.dispatchTouchEvent(event);
b = true;//此时不管child.dispatchTouchEvent(event)返回什么值,本方法都直接返回true
}else {
b = child.dispatchTouchEvent(event);//即时返回false也直接返回,区别于Activity
}
}else {
if(touchListener != null) {
b = touchListener.onTouch(view, event);
}
if(!b) b = onTouchEvent(event);//注意onClick事件是在onTouchEvent()内部处理的
}
}
}else if(isView) {//如果本对象是不能添加子组件的View,如:Button、EditText
if(touchListener != null) {
b = touchListener.onTouch(view, event);
}
if(!b) b = onTouchEvent(event);
}
return b;
}
/**Activity类特有,重写它,可用于在事件捕获阶段,事件到达本层容器而未进行任何其他处理之前做些事情**/
public void onUserInteraction() {}
/**ViewGroup类特有,其返回值表示是否拦截事件,以决定事件是否继续传递**/
public boolean onInterceptTouchEvent(MotionEvent event) {
return true | false;
}
/**Activity默认返回false,View默认返回true。返回值会决定后续的事件传递情况。可以根据需要自定义返回值。**/
public boolean onTouchEvent(MotionEvent event) {
/*
* 当Activity收到MotionEvent.ACTION_DOWN事件的冒泡返回,则会启动一个LongClick计时线程,之后若View组件树中
* 有任何一个组件在延时之内收到MotionEvent.ACTION_UP或MotionEvent.ACTION_CANCEL事件,则计时终止。
* 因此LongClick事件在两种状况下触发:
*
* a、若父级Activity收到child.dispatchTouchEvent(event)的返回值为false时;
* b、长按某组件,MotionEvent.ACTION_UP或MotionEvent.ACTION_CANCEL事件发生在LongClick之后时。
*
* LongClick事件触发时,从叶子层组件向根层组件依次调用View.OnLongClickListener.onLongClick(View v)方法。
* 该方法优先于组件本身默认的longClick相关处理方法,若返回值为false,会使组件继续调用默认的处理方法,
* 否则不执行默认的处理方法并给View.OnClickListener.onClick(View v)一个不要执行的标识。
* 但返回值不会影响上层组件对该方法的调用。
*/
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN:
/* Post本对象到LongClick计时线程 */
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
/* 终止LongClick计时线程 */
/* 执行View.OnClickListener.onClick(View v)。若View.OnLongClickListener.onLongClick(View v)先执行,
* 则根据其返回值决定是否执行View.OnClickListener.onClick(View v) */
break;
}
return true | false;
}
/**添加外部监听器。View类特有(包括ViewGroup),注意无论调用多少次本方法,只有最后一个监听器起作用,即覆盖**/
public void setOnTouchListener(View.OnTouchListener l) {
touchListener = l;
}
/**添加外部监听器。View类特有(包括ViewGroup),注意无论调用多少次本方法,只有最后一个监听器起作用,即覆盖**/
public void setOnClickListener(View.OnClickListener l) {
clickListener = l;
}
/**添加外部监听器。View类特有(包括ViewGroup),注意无论调用多少次本方法,只有最后一个监听器起作用,即覆盖**/
public void setOnLongClickListener(View.OnLongClickListener l) {
longClickListener = l;
}
}
class Child extends Android事件模拟 {}