近期由于个人的某些因素作怪,导致没有很好地总结和积累,主要是最近一段时间,大多数接触的都是第三方的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_DOWN08-12 02:00:26.526 19966- ACTION_DOWN08-12 02:00:26.646 19966-dispatchTouchEvent_ACTION_UP08-12 02:00:26.646 19966-dispatchTouchEvent_ACTION_CANCEL08-12 02:00:26.646 19966- ACTION_UP08-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循环去掉不让界面实时刷险,我们看一下点击事件日志是怎么走的。明白为什么实时刷新会影响我们的点击事件。

更多相关文章

  1. Android(安卓)用户界面---拖放(Drag and Drop)
  2. Android(安卓)四种线程池
  3. android线程 Handler Message Queue AsyncTask线程模型 线程交互
  4. Android(安卓)4.0按键事件以及系统流程分析
  5. Android(安卓)桌面组件【app widget】 进阶项目②--心情记录器
  6. Android(安卓)数独解码器 初级版(只能解简单数独)
  7. 新建Android(安卓)AVD,点击start、launch,出现进度条后无任何反应,
  8. Android中Tabhost既可以点击切换又可滑动切换不同Activity的View
  9. android调用震动的例子

随机推荐

  1. Ubuntu16.04配置ADB调试环境
  2. 获取系统语言的方法
  3. Android使用okhttp框架实现带参数Get和Po
  4. android强制横屏息屏后重新打开时会先显
  5. android 详细解答json解析与生成 JSONObj
  6. only the original thread that created
  7. 如何直接使用Android internal and hidde
  8. 关于android中进行http通信的几个问题
  9. json连接中央气象台api异常
  10. Android下如何计算要显示的字符串所占的