《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一起交流。