这是事件学习的第三篇文章了,以下是之前的文章:
之前分析点击事件从view.setOnClickListener源码一路查找,发现最终处理的地方是在onTouchEvent,但是仅仅获取到这些信息还是无法解释对于触屏事件的理解,还是会有很多疑惑,而且点击事件也没有完全的解读,不着急一点点来,先来看看一下疑问:
如何区分各种触屏手势?
如何区分手指在当前哪个视图内(例如:手指是点击按钮,还是点击屏幕上列表中某一项)?
何处调用onTouchEvent方法?(这个问题先暂时存着,之后会涉及到,先一步步解决其他疑惑)
现在尝试着从之前获取的信息onTouchEvent上,看能否有哪些蛛丝马迹可以解决以上问题的没有,首先来看下Android View类的文档中对此方法的定义,
public boolean onTouchEvent (MotionEvent event)

public class MainActivity extends Activity {
// ===========================================================
// Constants
// ===========================================================
// ===========================================================
// Fields
// ===========================================================
private LinkedHashMap<String, Integer> mHashMap =
new LinkedHashMap<String, Integer>();
private OnLogListener mOnLogListener;
// ===========================================================
// Constructors
// ===========================================================
// ===========================================================
// Public Methods
// ===========================================================
// ===========================================================
// Methods for/from SuperClass/Interfaces
// ===========================================================
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initLog();
initCancelButton();
initTouchListener();
}
private void initLog() {
final TextView output = (TextView) findViewById(R.id.txt_output);
// 初始化Log监听器
mOnLogListener = new OnLogListener() {
@Override
public void output(String outputValue) {
output.setText( outputValue );
}
};
// 初始化输出信息状态
initState();
}
private void initCancelButton() {
final CustomFrameLayout layout = (CustomFrameLayout) findViewById(R.id.layout_touch);
final Button button = (Button) findViewById(R.id.btn_cancel);
button.setText("是否触发ACTION_CANCEL : " + layout.getExecuteCancel());
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
boolean executeCancel = layout.getExecuteCancel();
layout.setExecuteCancel(!executeCancel);
button.setText("是否触发ACTION_CANCEL : " + !executeCancel);
}
});
}
private void initTouchListener() {
// 设置中间图片视图的触摸事件
View view = findViewById(R.id.view_touch_two);
view.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
initState();
updateState("ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
updateState("ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
updateState("ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
updateState("ACTION_CANCEL");
break;
case MotionEvent.ACTION_OUTSIDE:
updateState("ACTION_OUTSIDE");
break;
default:
break;
}
return true;
}
});
}
// ===========================================================
// Private Methods
// ===========================================================
private void initState() {
if (mHashMap != null) {
mHashMap.put("ACTION_DOWN", 0);
mHashMap.put("ACTION_MOVE", 0);
mHashMap.put("ACTION_UP", 0);
mHashMap.put("ACTION_CANCEL", 0);
mHashMap.put("ACTION_OUTSIDE", 0);
}
updateState("");
}
private void updateState(String pActionName) {
if (!TextUtils.isEmpty(pActionName)) {
int value = mHashMap.get( pActionName );
mHashMap.put(pActionName, ++value);
}
StringBuilder outputStr = new StringBuilder();
Set<Entry<String, Integer>> entrySet = mHashMap.entrySet();
for (Iterator<Entry<String, Integer>> iterator = entrySet.iterator(); iterator.hasNext();) {
Entry<String, Integer> entry =
(Entry<String, Integer>) iterator.next();
outputStr.append("\n");
outputStr.append("Action Name = ");
outputStr.append(entry.getKey());
outputStr.append(", 触发次数 = ");
outputStr.append(entry.getValue());
}
mOnLogListener.output(outputStr.toString());
}
private interface OnLogListener {
public void output(String output);
}
// ===========================================================
// Inner and Anonymous Classes
// ===========================================================
}
再来看看布局文件
<?xml version="1.0" encoding="utf-8"?>
<touch.two.action.CustomFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="@+id/layout_touch" >
<TextView
android:id="@+id/txt_output"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
<View
android:id="@+id/view_touch_two"
android:layout_width="100dip"
android:layout_height="100dip"
android:layout_gravity="center"
android:background="@drawable/ic_launcher" />
<Button
android:id="@+id/btn_cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|bottom" />
</touch.two.action.CustomFrameLayout>public class CustomFrameLayout extends FrameLayout {
public CustomFrameLayout(Context context) {
super(context);
}
public CustomFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomFrameLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
private boolean mExecuteCancel = false;
public void setExecuteCancel(boolean flag) {
mExecuteCancel = flag;
}
public boolean getExecuteCancel() {
return mExecuteCancel;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (MotionEvent.ACTION_MOVE == ev.getAction()) {
return mExecuteCancel;
}
return super.onInterceptTouchEvent(ev);
}
}
从上图可以得出以下结论:
1. ACTION_DOWN与 : 在一次操作中仅会触发一次,是在手指刚接触屏幕时触发
2. ACTION_UP : 在一次操作中仅会触发一次,是在手指离开屏幕时触发
3. ACTION_MOVE:操作过程中会有0个、1个或者多个ACTION_MOVE,如果手指一直在屏幕上移动,就会一直触发ACTION_MOVE,而且都是在ACTION_DOWN之后与ACTION_UP之前
4. ACTION_CANCEL:再来说说这个比较特别的不太容易触发,例子里面是我添加一个按钮来开关是否执行此ACTION。
子视图执行ACTION_DOWN时父视图oninterceptTouchEvent 返回false,当子视图执行ACTION_MOVE时父视图onInterceptTouchEvent 返回true,此时子视图会接收到ACTION_CANCEL。
如果偶尔翻看Android View onTouchEvent源码的话,会发现里面对 ACTION_UP与ACTION_CANCEL基本一致都是进行事件清理操作,有些干脆执行相同的操作,所以对于这个ACTION只需要了解什么时候出发,至于该哪些操作干脆和ACTION_UP处理逻辑一样即可。
简单一句话总结ACTION_CANCEL是父视图接管子视图控制权时使用。
5. ACTION_OUTSIDE:文档说是在UI元素之外触发。未找到何处使用,以下四种都尝试了
(1)在子视图设置事件,在其中移动并滑动到其他视图,不会触发
(2)在Dialog视图中设置事件,在其中移动并滑动到Dialog外,不会触发
(3)在WindowManager视图内设置事件,在其视图内滑动到视图外,不会触发
(4)在View与ViewGroup类中都未搜索到“ACTION_OUTSIDE”相关的代码
总之Android系统把人类的触摸行为分类很多种动作(ACTION),单点触摸分别为ACTION_DOWN、ACTION_MOVE、ACTION_UP、ACTION_CANCEL 和 ACTION_OUTSIDE,而且这些信息可以从MotionEvent.getAction()获取,一些系统支持的手势是在相应视图的onTouchEvent中通过这些动作上添加判断获得,如果自定义视图也需要灵活是用这些动作,当然这里仅仅是解释系统提供了哪些信息便于分别各种手势,如何判断手势与这些ACTION具体从哪来的之后再慢慢分析学习。
当前仅解决了一个疑惑,下一篇文章接着解决下一个问题“如何区分手指在当前哪个视图内(例如:手指是点击按钮,还是点击屏幕上列表中某一项)?”
补第16天 Android Touch事件学习 3 区分各种手势基础知识
原文:http://blog.csdn.net/androiddevelop/article/details/18910367