談到android事件處理,最複雜的就是對Touch事件的處理,因爲Touch事件包括:down, move, up, cancle和多點觸摸等多種情況,多點觸摸的情況先不討論,因爲Touch有這麼多的狀態,所以Touch相對來說是最難處理的,下面就來討論一下android系統是如何處理Touch事件的.
1.說到事件處理,首先我們要明白,爲什麼要處理事件,要了解android系統本身對事件的一個處理過程.在實際的開發中,我們如果都用系統的基本控件,那是不需要去處理事件的,但是如果我們用複雜的佈局嵌套去做一些特殊的需求,例如:ScrollView中嵌套ListView,ScrollView嵌套ViewPager等,則會產生事件衝突,所以,由於事件衝突的存在,我們要去處理這些衝突,只有瞭解android的事件處理機制,纔能有效的去處理事件衝突.還有就是如果我們要新開發一個組件,則組件的所有事件都要我們自己去做處理,這種情況也需要我們去處理事件.所以:由於存在以上說到的兩種情況,我們要自己處理事件.
2.有了處理事件的動機後,接下來就要了解android系統本身是如何處理複雜的事件的.android系統爲所有的事件提供了三個相關的方法,以下只以Touch事件爲例說明.
這三個方法分別是:
dispatchTouchEvent(MotionEvent ev); (Activity, ViewGroup, View都有此方法)
onInterceptTouchEvent(MotionEvent ev); (只有ViewGroup有)
onTouchEvent(MotionEvent); (Activity, ViewGroup, View都有此方法)
要想了解android系統是如何對事件進行一步一步的處理,這三個方法是必須要掌握的.其中:dispatchTouchEvent(MotionEvent ev);方法是用來對事件進行分發的,即將事件分發到目標控件,onInterceptTouchEvent(MotionEvent ev)是用來過濾事件的,即進行事件的攔截,也就是是否要向下傳遞事件,onTouchEvent(MotionEvent ev)纔是最終用來處理事件的,也就是說我們平常重寫onTouchEvent時,其實,系統已經默認幫我們調用了前兩個方法.下面就來詳細分析一下三個方法.
首先要提的是,android系統對本件的處理是一層一層向下傳遞處理(樹形處理).那這棵樹是從那來的呢..就是我們的佈局樹,一個佈局,無論是代碼編寫的佈局還是xml生成的佈局,android系統對它進行解析時都是將其組裝成一棵UI樹,最外層佈局是整個UI樹的根.知道這個以後,再來分析事件的處理.
處理流程:當我們的手指觸摸到手機屏幕時,當前處於onStart()狀態的Activity最先接收到此Touch事件下的ACTON_DOWN,然後開始調用它自己dispatchTouchEvent()開始進行DOWN事件分發,如果此方法返回true,則Activity不向下分發事件,則整個佈局都不會收到DOWN事件,TouchEvent直接到Activity的onTouchEvent()方法進行事件處理.如果返回false,則表示DOWN是要被分發到下層的,此時DOWN事件被直接分發(因爲沒有過濾方法)到UI樹的根佈局(即最外層的佈局),根佈局拿到DOWN事件時,執行自己的dispatchTouchEvent方法,返回true,則事件直接交到根佈局的onTouchEvent()中進行處理,false則表示還得向下分發,此時事件被傳遞到根佈局的onInterceptTouchEvent()方法中,如果此方法返回true,表示要對此事件進行過濾,則此DOWN事件又直接進行到根佈局的onTouchEvent()方法直接處理,false則,要根佈局不對事件進行過濾,DOWN事件繼續向下傳遞,直到達到目標組件後,目標組件調用自己的dispatchTouchEvent()方法,由於是目標組件,直接分發事件到自己的onTouchEvent方法中,目標組件如果處理完這個DOWN事件後返回true,表示該事件被消費完畢,不再向上層傳遞,如果返回false,則表示沒有消費完這個DOWN事件,DOWN向上傳遞到自己的父組件中,父組件再進行DOWN事件的處理.一直向上傳遞直到事件被扔到虛擬機.DOWN事件纔算處理完成,接着調用MOVE,MOVE完了UP,整個流程與DOWN是一樣的.
這裏要強調一點的是:如果一個組件沒有接收到DOWN事件,那麼一定接收不到MOVE,UP事件。
通過以上的流程,我們可以明白:android系統對任何一個事件的處理都是這樣的,分發事件,過濾事件,處理事件,下一個事件, 分發事件,過濾事件,處理事件……一直這樣循環去處理所有的事件的。即:事件的分發,過濾是從根到葉的,處理則是從葉再到根的。
下面是我將上面的文字流程畫的一張處理流程圖:
從圖上看,我們可以更直觀的感受整個Touch事件的處理流。
3.講了android系統是如何處理事件的整個流程,那我們實際工作中如何去處理事件呢……下面將我之前處理過的一個例子來分析。
首先我們從根到葉去處理事件,代碼如下:
package com.micen.buyers.view.category;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.ScrollView;
import com.micen.buyers.util.Util;
public class MyScrollView extends ScrollView {
private float mLastMotionY;
private float mLastMotionX;
public MyScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
/**
* 通過重寫此方法,達到對事件的處理
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
final float x = ev.getX();
final float y = ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastMotionX = x;
mLastMotionY = y;
break;
case MotionEvent.ACTION_MOVE:
if (Math.abs(y - mLastMotionY) > Util.dip2px(20)
&& Math.abs(x - mLastMotionX) < Util.dip2px(5)) {
return true; // 如果MOVE事件的縱座標超過20px, 橫向小於5dp
// 則認爲是滑動scrollview,返回true,則事件不向下分發,直接傳入到
// onTouchEvent方法中,否則,認爲滑動事件不屬於scrollview處理,允許分發
}
break;
case MotionEvent.ACTION_UP:
break;
case MotionEvent.ACTION_CANCEL:
break;
}
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
Log.e("--------->", "user want to scroll");
return super.onTouchEvent(ev);
}
}
上述代碼,我們是重寫了ScrollView的dispatchTouchEvent()達到對事件的一個特殊處理,如果滿足了我們的規則,則直接到onTouchEvent()中處理,否則,事件被髮送到 onInterceptTouchEvent()中執行過濾,由於此方法中沒有過濾,則下發傳遞事件.同樣的效果,我們從葉子開始處理事件衝突,代碼如下:
package com.micen.buyers.view.category;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.MotionEvent;
import com.micen.buyers.util.Util;
public class MyViewPager extends ViewPager
{
private boolean flag = true;
private float mLastMotionY;
private float mLastMotionX;
public MyViewPager(Context context)
{
super(context);
}
public MyViewPager(Context context, AttributeSet attrs)
{
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev)
{
final float x = ev.getX();
final float y = ev.getY();
switch (ev.getAction())
{
case MotionEvent.ACTION_DOWN:
setPullToScrollViewStatus(true); //先默認父控件不接收滑動事件,事件直接傳遞到子滑動組件
flag = true;
mLastMotionX = x;
mLastMotionY = y;
mHandler.sendEmptyMessage(1);
break;
case MotionEvent.ACTION_MOVE:
if (flag)
{
if (Math.abs(y - mLastMotionY) > Util.dip2px(20) && Math.abs(x - mLastMotionX) < Util.dip2px(5))
{
flag = false;
setPullToScrollViewStatus(false); //滿足條件,父滑動控件將事件過濾掉了,不再傳到ViewPager中了.
}
}
break;
case MotionEvent.ACTION_UP:
setPullToScrollViewStatus(false);
case MotionEvent.ACTION_CANCEL:
setPullToScrollViewStatus(false);
break;
}
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event)
{
return super.onInterceptTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event)
{
return super.onTouchEvent(event);
}
private void setPullToScrollViewStatus(boolean disallowIntercept)
{
//調用父控件的requestDisallowInterceptTouchEvent()方法,傳入true,則等將於父控件的onTInterceptTouchEvent返回false,
//不過濾,否則,父控件過濾掉事件,不再向下傳遞.
getParent().getParent().requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
總結:android事件處理流程,是每一個搞android的人應該熟練掌握的.