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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值