要解決這個問題,首先要弄清楚幾個問題:
1、onThouch事件的觸發原理是怎樣的?
2、GestureOverlayView的繪製手勢的事件是在什麼時候觸發的?
3、父子嵌套的控件觸發事件的順序是怎樣的?
4、父子控件獲取焦點的順序是怎樣的?
第一個問題,安卓中任何控件的onThouch事件觸發的時候,都經過了以下過程:
public boolean dispatchTouchEvent(MotionEvent ev) ,該方法如果返回true,則事件在該位置被消費掉,不再向下傳遞,返回false則繼續傳遞至
public boolean onInterceptTouchEvent(MotionEvent ev)這個方法,該方法是事件攔截器,如果返回true,則觸發該控件的onThouch事件,否則就將事件傳遞給該控件的子控件,
public boolean onTouchEvent(MotionEvent ev),返回true的話就消費掉該事件,返回false就傳遞到該控件的父控件的onThouch事件
在網上找到一個圖片,能簡潔明瞭的反映以上關係:
第二個問題,查看GestureOverlayView源碼可知道,手勢繪製的監聽觸發時間是在dispatchTouchEvent(MotionEvent ev)這個事件中就完成的,所以只要有觸摸屏幕的動作,就必然會被它先消費掉,這也是手勢事件和其他控件衝突的根本原因~源碼如下:
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
if (isEnabled()) {
final boolean cancelDispatch = (mIsGesturing || (mCurrentGesture != null &&
mCurrentGesture.getStrokesCount() > 0 && mPreviousWasGesturing)) &&
mInterceptEvents;
processEvent(event);
if (cancelDispatch) {
event.setAction(MotionEvent.ACTION_CANCEL);
}
super.dispatchTouchEvent(event);
return true;
}
return super.dispatchTouchEvent(event);
}
private boolean processEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touchDown(event);
invalidate();
return true;
case MotionEvent.ACTION_MOVE:
if (mIsListeningForGestures) {
Rect rect = touchMove(event);
if (rect != null) {
invalidate(rect);
}
return true;
}
break;
case MotionEvent.ACTION_UP:
if (mIsListeningForGestures) {
touchUp(event, false);
invalidate();
return true;
}
break;
case MotionEvent.ACTION_CANCEL:
if (mIsListeningForGestures) {
touchUp(event, true);
invalidate();
return true;
}
}
return false;
}
基於這個原因,可能很多人會考慮通過重寫GestureOverlayView控件來解決衝突問題,我也試過了,但是依然不能解決較爲複雜的問題,所以在次就不再贅述了。
第三個問題,借用一篇博客來說明問題,在此感謝博主 @淺秋http://blog.csdn.net/hyp712/article/details/8777835(博文寫的很詳細)
借用第三個問題,第四個問題就迎刃而解了,可以簡單的理解爲,正常情況下,最內層的控件是最先獲取焦點的,
最外層的是最後獲取焦點的;但是最外層的獲取焦點的優先級是最高的,一旦它決定攔截並消費事件,那麼它的子控件就不能再獲取該事件;
解決衝突的方法:
我的需求是在一個ListView頁面啓用手勢功能,
用戶如果畫了手勢,則根據手勢內容做不同的反應,但是手勢不能影響ListView的滾動、點擊、選中的操作;
ListView中包含文本框;如果點擊的是文本框,不能影響文本框獲取焦點進行編輯;
我重寫GestureOverlayView的時候解決了前2個需求,但是文本框死活獲取不了焦點;
佈局文件如下,需要說明是,因爲之前是通過重寫來做的,但是最後沒解決,於是重寫的文件直接調用了super,等於沒重寫:
<ListView
android:id="@+id/list_aj"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:clickable="true"
android:longClickable="true"
/>
<TextView
android:id="@+id/textViewModel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
/>
<EditText
android:id="@+id/textViewValueModel"
android:layout_width="300dp"
android:layout_height="40dip"
android:layout_centerVertical="true"
android:gravity="center_vertical"
android:layout_marginLeft="110dip"
android:textSize="17sp"
android:background="@null"
android:inputType="text"
android:focusable="false"
android:visibility="gone"
/>
<ImageView
android:id="@+id/imageViewModel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:contentDescription="@string/descTask"
android:layout_centerVertical="true"/>
<com.zbtc_it.tcis.Util.MGestureOverlayView
calss="com.zbtc_it.tcis.Util.MGestureOverlayView"
android:id="@+id/gesture"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
>
</com.zbtc_it.tcis.Util.MGestureOverlayView>
重寫的時候,我用MGestureOverlayView包含了ListView,
但是後來一想,因爲MGestureOverlayView每次都會最先攔截,並且它必然會消費事件,所以這樣肯定是不行的,
於是就換成並列的,解決方法就是,在MGestureOverlayView的onThouch事件中,手動賦予ListView的事件源,代碼如下
overlays = (MGestureOverlayView) layout.findViewById(R.id.gesture);
overlays.setGestureStrokeType(GestureOverlayView.GESTURE_STROKE_TYPE_MULTIPLE);
overlays.setFadeOffset(1000);// 多筆畫2筆之間的時間間隔
overlays.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
itemListView.dispatchTouchEvent(event);//賦予ListView事件源
return false;//消費掉事件
}
});
同時重寫ListView的onInterceptTouchEvent方法,讓其直接返回false
(這步好像不是必須的,忘記了 >_< ,默認的貌似就是返回false)