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