Android事件機制深入探討(四)

《Android事件機制深入探討(一)》
《Android事件機制深入探討(二)》
《Android事件機制深入探討(三)》
閱讀本文前,請先閱讀上三篇文章,本文是以上的擴展、深入講解,老司機請忽略。
接下來本文主要圍繞requestDisallowInterceptTouchEvent這個方法展開,闡述它對事件分發的影響及其如何使用。
requestDisallowInterceptTouchEvent是接口ViewParent裏面的一個方法,ViewGroup繼承該接口並實現了該方法,源碼如下:

    @Override
    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {

        if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
            // We're already in this state, assume our ancestors are too
            return;
        }

        if (disallowIntercept) {
            mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
        } else {
            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        }

        // Pass it up to our parent
        if (mParent != null) {
            mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
        }
    }

簡單來講就是遞歸告訴父ViewGroup不要攔截我的事件(disallowIntercept爲true的時),這個方法就做了兩件事,一是設置了mGroupFlags的值,另一個則遞歸調用父ViewGroup的requestDisallowInterceptTouchEvent的方法。遞歸方法好容易理解,就是一層層告訴ViewGroup不要攔截我的事件,那到底是怎麼實現的呢?
就是這個mGroupFlags這個變量,我在dispatchTouchEvent這個方法裏找到如下代碼:
事件分發源碼
子View是通過讓父類不執行onInterceptTouchEvent來實現不攔截事件的,從2502-2508行代碼就可以看出來。這邊有個知識點提一下,我們看2495行有個resetTouchState方法,這個方法會把mGroupFlags重新設置爲false,而這個方法會在action爲ACTION_DOWN的時候被執行,也就說ACION_DOWN都會執行onInterceptTouchEvent方法。
我們來看一個例子,ScrollView嵌套ListView,當你手指在ListView的區域向上滑動時,ListView並不會滾動,反之時ScrollView滾動,先看下面的代碼:
佈局文件(activity_scroll_view_list_view.xml):

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/scrollView"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <ListView
            android:id="@+id/list_view"
            android:layout_width="match_parent"
            android:layout_height="400dp">
        </ListView>

        <TextView
            android:layout_width="match_parent"
            android:layout_height="400dp"
            android:background="@color/red" />

    </LinearLayout>
</ScrollView>

Activity代碼(ScrollViewListViewActivity):

public class ScrollViewListViewActivity extends AppCompatActivity {

    private ListView mListView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_scroll_view_list_view);
        mListView = (ListView) findViewById(R.id.list_view);
        setAdapter();
    }

    private void setAdapter() {
        List<String> dataList = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            dataList.add("這是第" + i + "個TextView");
        }
        MyAdapter adapter = new MyAdapter(this, dataList);
        mListView.setAdapter(adapter);
    }

    class MyAdapter extends BaseAdapter {
        protected Context mContext;
        private List<String> mDataList;

        public MyAdapter(Context context, List<String> dataList) {
            mContext = context;
            mDataList = dataList;
        }

        @Override
        public int getCount() {
            return null == mDataList ? 0 : mDataList.size();
        }

        @Override
        public Object getItem(int position) {
            return null == mDataList ? null : mDataList.get(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder viewHolder;
            if (null == convertView) {
                convertView = LayoutInflater.from(mContext).inflate(R.layout.adapter_view_pager_list_view, null);
                viewHolder = new ViewHolder(convertView);
                convertView.setTag(viewHolder);
            } else {
                viewHolder = (ViewHolder) convertView.getTag();
            }
            String value = (String) getItem(position);
            viewHolder.mTvTitle.setText(value);
            return convertView;
        }

        class ViewHolder {
            TextView mTvTitle;

            public ViewHolder(View view) {
                this.mTvTitle = (TextView) view.findViewById(R.id.textView);
            }
        }
    }
}

ListView的Item佈局文件(adapter_view_pager_list_view):

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/textView"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:gravity="left|center_vertical"
        android:text="TextView"
        android:textSize="18sp" />
</LinearLayout>

運行效果圖如下:
事件分發效果圖
當我們手指在黃色區域(ListView,且有20條數據)向上滾動時,效果如下圖:
事件分發效果圖
我們看到是的ScrollView向滾動了而不是ListView,我查看了下ScrollView的源碼發現,ScrollView攔截了ACTION_MOVE事件(不然怎麼讓自身向上滾動呢),ListView並沒有接收到事件,所以導致了這樣的效果,現在我們想實現這樣的一個效果:就是我手指在ListView區域向上滑動的時候,先滾動ListView,如果ListView滾動最底部了(這裏是第20個Item可見)再滾動ScrollView呢,這個很簡單,我們自定義ListView,替換系統ListView,在dispatchTouchEvent方法裏告訴ScrollView不要攔截我的事件,等我滾到底部了,你再攔截,自定義的ListView代碼如下:

public class CustomListView extends ListView {

    ...

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {

        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                executeRequestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                if (getLastVisiblePosition() == getAdapter().getCount() - 1) {
                    executeRequestDisallowInterceptTouchEvent(false);
                } else {
                    executeRequestDisallowInterceptTouchEvent(true);
                }
                break;
            case MotionEvent.ACTION_UP:
                executeRequestDisallowInterceptTouchEvent(false);
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

    private void executeRequestDisallowInterceptTouchEvent(boolean value) {
        ViewParent viewParent = getParent();
        if (null != viewParent) {
            viewParent.requestDisallowInterceptTouchEvent(value);
        }
    }
}

關鍵代碼塊在case中的MotionEvent.ACTION_MOVE分支,意思是當我最後一個item還不可見時,ScrollView不要攔截事件,傳遞給我處理,當最有一個item可見的時候,你該幹嘛就幹嘛,這樣決解了剛纔我們的那個問題。(備註:這裏只是一個例子說明下requestDisallowInterceptTouchEvent的用法而已,我們在向上滑動手指的時候還是會出現衝突的,ListView無法滾動,讀者可自行研究尋找如何決解)

這裏有篇文章介紹了requestDisallowInterceptTouchEvent的其它用法,《requestDisallowInterceptTouchEvent的用法》,一個大神寫的。

關於事件的分發傳遞,在這裏就畫上句號了(以後如有新的知識點,還會出現第五、第六篇的,哈哈),由於本人水平有限,難免出現錯誤,歡迎留言指正,或加Q:252624617一起交流。

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