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啦,具體大家去試一下就可以啦!