1. 程式人生 > >View Touch事件處理機制

View Touch事件處理機制

1、MotionEvent介紹

系統有一個執行緒在迴圈收集螢幕硬體資訊,當用戶觸控式螢幕幕的時候 ,這個執行緒就會把從裝置資訊收集到的資訊封裝成一個MotionEvent,然後把物件放到一個訊息佇列中。
系統另外一個執行緒迴圈讀取訊息佇列中的MotionEvent,然後派發給當前活動的activity。
這個時間,系統收集資訊是有時間間隔的,這個取決於硬體裝置,目前手機應該在20毫秒左右。
MotionEvent事件我就不作具體介紹了,ACTION_DOWN、ACTION_MOVE、ACTION_UP、ACTION_CANCEL 。

2、view touch事件處理

我們都知道android裡面view是一層層的,當有touch時間傳過來的時候,也會從父層到子層傳遞。這裡就需要onTouchEvent和onInterceptTouchEvent來控制touch事件是否往子層傳,dispatchTouchEvent是用於touch事件分發,決定事件是否由onInterceptTouchEvent來攔截處理。
我們看一下ViewGroup中的dispatchTouchEvent原始碼(由於程式碼太多太繁瑣,所以我就挑著看一下重要程式碼):

if (!canceled && !intercepted) {
    for (int i = childrenCount - 1; i >= 0; i--) {
    …
    if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                     handled = true;
             }
    …
    return handled;
    }
}

這裡是事件處理流程的關鍵迴圈,但是for迴圈之前有個判斷條件,就是這個intercepted,然後我們看一下intercepted如何被定義的。

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; }

由此可以看出,dispatch其實就是否則touch事件分發,決定事件是否由onInterceptTouchEvent來攔截處理。返回super.dispatchTouchEvent時,由onInterceptTouchEvent來決定事件的流向。返回false時,會繼續分發事件,自己內部只處理了ACTION_DOWN。返回true時,不會繼續分發事件,自己內部處理了所有事件(ACTION_DOWN,ACTION_MOVE,ACTION_UP)

3、onInterceptTouchEvent(事件攔截)
攔截事件,用來決定事件是否傳向子View
返回true時,攔截後交給自己的onTouchEvent處理
返回false時,攔截後交給子View來處理

4、onTouchEvent(事件處理):事件最終到達這個方法
返回true時,內部處理所有的事件,換句話說,後續事件將繼續傳遞給該view的onTouchEvent()處理
返回false時,事件會向上傳遞,由onToucEvent來接受,如果最上面View中的onTouchEvent也返回false的話,那麼事件就會消失

5、驗證
ViewA.java

public class ViewA extends FrameLayout {

    public ViewA(Context context) {
        super(context);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        // TODO Auto-generated method stub
        Log.d(Constant.LOGCAT, "Group1 onInterceptTouchEvent觸發事件:"+Constant.getActionTAG(ev.getAction()));
        return false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // TODO Auto-generated method stub
        Log.d(Constant.LOGCAT, "Group1 onTouchEvent觸發事件:"+Constant.getActionTAG(event.getAction()));
        return true;
    }
}

ViewB.java

public class ViewB extends FrameLayout{

    public ViewB(Context context) {
        super(context);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        // TODO Auto-generated method stub
        Log.d(Constant.LOGCAT, "Group2 onInterceptTouchEvent觸發事件:"+ Constant.getActionTAG(ev.getAction()));
        return false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // TODO Auto-generated method stub
        Log.d(Constant.LOGCAT, "Group2 onTouchEvent觸發事件:"+Constant.getActionTAG(event.getAction()));
        return true;
    }
}

MyTextView.java

public class MyTextView extends TextView{

    public MyTextView(Context context) {
        super(context);
        this.setGravity(Gravity.CENTER);
        this.setText("點選我!");
        // TODO Auto-generated constructor stub
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // TODO Auto-generated method stub
        Log.d(Constant.LOGCAT, "MyTextView onTouchEvent觸發事件:"+ Constant.getActionTAG(event.getAction()));
        return true;
    }
}

Constant.java

public class Constant {
    public static final String LOGCAT = "logcat";

    public static String getActionTAG(int action) {
        switch (action) {
            case 0:
                return "ACTION_DOWN";
            case 1:
                return "ACTION_UP";
            case 2:
                return "ACTION_MOVE";
            default:
                return "NULL";
        }
    }
}

MainActivity.java

public class MainActivity extends AppCompatActivity {

    ViewA viewA;
    ViewB viewB;
    MyTextView myTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        viewA = new ViewA(this);
        viewB = new ViewB(this);
        myTextView = new MyTextView(this);
        viewB.addView(myTextView, new ActionBar.LayoutParams(ActionBar.LayoutParams.FILL_PARENT,
                ActionBar.LayoutParams.FILL_PARENT));
        viewA.addView(viewB, new ActionBar.LayoutParams(ActionBar.LayoutParams.FILL_PARENT,
                ActionBar.LayoutParams.FILL_PARENT));
        setContentView(viewA);
    }
}

然後更改onTouch和InterceptTouch返回值就可以看到log啦,具體大家去試一下就可以啦!