分享

android 事件分发,解决由于listview中实时刷新,导致子view点击事件失效

 DeepReading 2019-06-06

近期由于个人的某些因素作怪,导致没有很好地总结和积累,主要是最近一段时间,大多数接触的都是第三方的sdk ,在一些接口问题上造成了很多困扰,很是麻烦,并且说明文档也不详细,所以每每遇到一些问题都要等待很久才能解决。
好了,废话不多说了。下面开始今天的正文。(最近发现这个问题好像网上解决的并不多,啰嗦太多不好意思哈,想知道解决办法,可以直接看最后一段)android 之事件分发机制。并且结合本人开发中遇到的实际场景来说明一下解决办法。
本人近期在做文件的上传和下载,这个必定会用到progressBar 进度条,因为这个是描述下载和上传进度的最显著的体现。我将每条记录放入listview的item中,所以每个item中必定需要包含进度条。进度条这个东西当然是实时更新的。更新频率也非常的高,所以每次进度有更新我都会进行adapter.notifySetDatachange 这样才能实时看到界面变化,那么问题来了。我们在上传下载过程中肯定需要对这个任务进行暂停或开始的操作,这些按钮也是在每个item中都存在的。那么在任务进行中的时候,我想点击暂停按钮,把任务暂停。但是发现无论怎么点击,onclick中的代码都不会执行,这个让我感到很奇怪。排除了一些基本的问题,我想到了会不会是由于实时刷新页面导致点击事件失效。
看一下效果图吧:这里写图片描述
想到这里我就想写一个demo来测试一下。
首先我们都知道安卓的触屏事件其实都是通过

 public boolean dispatchTouchEvent(MotionEvent ev) {}

这个事件分发机制来实现。首先这个分发都是从activity中的windowManager来开始的,

  @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {

        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.e(TGA,"dispatchTouchEvent_ACTION_DOWN");

//                return  false;
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e(TGA,"dispatchTouchEvent_ACTION_MOVE");

                break;

            case MotionEvent.ACTION_UP:
                Log.e(TGA,"dispatchTouchEvent_ACTION_UP");

//                return  false;

            case MotionEvent.ACTION_CANCEL:
                Log.e(TGA,"dispatchTouchEvent_ACTION_CANCEL");

                break;
        }
        return super.dispatchTouchEvent(ev);
    }

事件的从actionDown开始的,如果在activity 中有View来接受这个down事件,那么这个事件就会传给view的分发,其中还有ViewGroup ,因为它有子View 所以此时又会有事件传递,查看是否有子View接受这个事件,如果没有那就自己消费掉。假设就是一个button那么就不用分发了,直接自己消费就可以,自己消费的话就是在View的ontouchEvent中进行触发。

 button.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()){
                    case MotionEvent.ACTION_DOWN:
                        Log.e(TGA,"ACTION_DOWN");

                        break;

                    case MotionEvent.ACTION_MOVE:
                        Log.e(TGA,"ACTION_MOVE");

                        break;

                    case MotionEvent.ACTION_UP:
                        Log.e(TGA,"ACTION_UP");

                        break;

                    case MotionEvent.ACTION_CANCEL:
                        Log.e(TGA,"ACTION_CANCEL");

                        break;

                }
                return false;
            }
        });

所以很容易理解,事件就是一件一件传递下去那么我们的Onclick事件是什么时候触发呢,这个就是在MotionEvent 中的Action_UP之后便会触发,就是当你的收抬起来之后click事件才真正执行。首先我们看一下demo的源码:

package com.example.szh.motioneventtest;

import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ListView;

public class MainActivity extends AppCompatActivity {
    private final  String TGA=MainActivity.this.getClass().getName();
    private   Context mContext;
    private Button button;
    private ListView listView;
    private ListAdapter adapter;


    private Handler mHandler=new Handler(){

    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mContext=this;

        initData();

        findViews();

        bindViews();

        setListener();

    }

    private void initData() {
        adapter=new ListAdapter();

    }

    private void findViews() {
        button=(Button)findViewById(R.id.button);
        listView=(ListView)findViewById(R.id.listview);
    }

    private void bindViews() {
        listView.setAdapter(adapter);
        new Thread(new Runnable() {
            @Override
            public void run() {
                while(true){
                    mHandler.postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            adapter.notifyDataSetChanged();
                        }
                    },10);
                }
            }
        }).start();
    }

    private void setListener() {
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.e(TGA,"onClick()");
            }
        });
        button.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()){
                    case MotionEvent.ACTION_DOWN:
                        Log.e(TGA,"ACTION_DOWN");

                        break;

                    case MotionEvent.ACTION_MOVE:
                        Log.e(TGA,"ACTION_MOVE");

                        break;

                    case MotionEvent.ACTION_UP:
                        Log.e(TGA,"ACTION_UP");

                        break;

                    case MotionEvent.ACTION_CANCEL:
                        Log.e(TGA,"ACTION_CANCEL");

                        break;

                }
                return false;
            }
        });

        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Log.e(TGA,"onItemClick");

            }
        });

        listView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {

                switch (event.getAction()){
                    case MotionEvent.ACTION_DOWN:
                        Log.e(TGA,"listView_ACTION_DOWN");

                        break;

                    case MotionEvent.ACTION_MOVE:
                        Log.e(TGA,"listView_ACTION_MOVE");

                        break;

                    case MotionEvent.ACTION_UP:
                        Log.e(TGA,"listView_ACTION_UP");

                        break;

                    case MotionEvent.ACTION_CANCEL:
                        Log.e(TGA,"listView_ACTION_CANCEL");

                        break;
                }

                return false;
            }
        });

    }


    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {

        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.e(TGA,"dispatchTouchEvent_ACTION_DOWN");

//                return  false;
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e(TGA,"dispatchTouchEvent_ACTION_MOVE");

                break;

            case MotionEvent.ACTION_UP:
                Log.e(TGA,"dispatchTouchEvent_ACTION_UP");

//                return  false;

            case MotionEvent.ACTION_CANCEL:
                Log.e(TGA,"dispatchTouchEvent_ACTION_CANCEL");

                break;
        }
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.e(TGA,"Activity_ACTION_DOWN");

                break;

            case MotionEvent.ACTION_MOVE:
                Log.e(TGA,"Activity_ACTION_MOVE");

                break;

            case MotionEvent.ACTION_UP:
                Log.e(TGA,"Activity_ACTION_UP");

                break;

            case MotionEvent.ACTION_CANCEL:
                Log.e(TGA,"Activity_ACTION_CANCEL");

                break;
            }

        return super.onTouchEvent(event);


    }

    class ListAdapter extends BaseAdapter{

        @Override
        public int getCount() {
            return 5;
        }

        @Override
        public Object getItem(int position) {
            return null;
        }

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

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder;
            if(convertView ==null){
                holder=new ViewHolder();
                convertView= LayoutInflater.from(MainActivity.this).inflate(R.layout.tem_list,null);
                holder.itemBT=(Button)convertView.findViewById(R.id.button);
                holder.itemBT.setOnTouchListener(new View.OnTouchListener() {
                    @Override
                    public boolean onTouch(View v, MotionEvent event) {

                        switch (event.getAction()){
                            case MotionEvent.ACTION_DOWN:
                                Log.e(TGA,"Item_ACTION_DOWN");

                                break;

                            case MotionEvent.ACTION_MOVE:
                                Log.e(TGA,"Item_ACTION_MOVE");

                                break;

                            case MotionEvent.ACTION_UP:
                                Log.e(TGA,"Item_ACTION_UP");

                                break;

                            case MotionEvent.ACTION_CANCEL:
                                Log.e(TGA,"Item_ACTION_CANCEL");

                                break;

                        }



                        return false;
                    }
                });

                holder.itemBT.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        Log.e(TGA,"Item_onClick");
                    }
                });

               convertView.setTag(holder);
            }else{
                holder=(ViewHolder) convertView.getTag();
            }
            return convertView;
        }
    }


    class ViewHolder{
        Button itemBT;

    }


}

页面效果是这样的:这里写图片描述
很简单上面的5个button是在listview的item中的,而最后一个button是直属于activity。
其中为了模拟实时刷新,通过一个while循环,然后只做刷新的事件。
接下来我们看当我们按下activity中的button的日志:

08-12 02:00:26.526 19966-dispatchTouchEvent_ACTION_DOWN
08-12 02:00:26.526 19966-
 ACTION_DOWN
08-12 02:00:26.646 19966-
dispatchTouchEvent_ACTION_UP
08-12 02:00:26.646 19966-dispatchTouchEvent_ACTION_CANCEL
08-12 02:00:26.646 19966-
 ACTION_UP
08-12 02:00:26.726 19966-
 onClick()

从日志中我们可以很明显的分析出,每一个操作执行的顺序。这个日志看起好像没有影响,实则不然,因为当我多次测试就会发现onclick的执行有时会在Action_UP之后500ms以后才会执行,这个是很致命的。实际开发中其实不允许这种现象出现的。可以看下面我的测试日志:08-12 02:05:44.966 19966-dispatchTouchEvent_ACTION_DOWN
08-12 02:05:44.966 19966-
ACTION_DOWN
08-12 02:05:45.046 19966-
dispatchTouchEvent_ACTION_UP
08-12 02:05:45.046 19966-dispatchTouchEvent_ACTION_CANCEL
08-12 02:05:45.046 19966-
ACTION_UP
08-12 02:05:45.846 19966-
onClick()
可以看出来时间差了800ms。

而在listview的子View中这个现象就更明显了。此时我们点击item中的button 日志显示
08-12 02:11:33.776 19966-dispatchTouchEvent_ACTION_DOWN
08-12 02:11:33.776 19966-
Item_ACTION_DOWN
08-12 02:11:33.846 19966-
dispatchTouchEvent_ACTION_UP
08-12 02:11:33.846 19966-dispatchTouchEvent_ACTION_CANCEL
08-12 02:11:33.846 19966-
Item_ACTION_CANCEL

很明显Item中的事件走了down 就直接cancle ,没有走up 当然也不会执行onclick ,所以说,会导致我们的点击事件失效。但是通过日志我们其实发现,down是一定会执行的,所以针对这个问题,我们的解决办法就是,可以把点击事件写在down的事件中,这样就能确保可以执行了,但其实这样的效果不好,让用户觉得不像是点击事件。针对这个问题我的处理方式,是将notifysetDataChange 这个调用放在一个if(isclick){}中,每次我们down执行的时候我们将改变这个isclick 的值,让更新页面无法执行,然后通过handler.sendMsgdelay(what,500);这样再恢复isclick的值,这样在这500ms的时间中已经足够down执行到click中了。确保了click中的事件执行完毕,当然之后一切又恢复了,界面依然可以实时刷新,在这500ms界面会短暂的不刷新,但由于时间相对可以接受,所以并不影响体验。这样就完美解决我们的问题啦。
接下来我把while循环去掉不让界面实时刷险,我们看一下点击事件日志是怎么走的。明白为什么实时刷新会影响我们的点击事件。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多