開發筆記:解決安卓GestureOverlayView手勢和ListView點擊事件、文本框獲取焦點衝突的問題

要解決這個問題,首先要弄清楚幾個問題:

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)


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章