1. 程式人生 > >android 事件分發,解決由於listview中實時重新整理,導致子view點選事件失效

android 事件分發,解決由於listview中實時重新整理,導致子view點選事件失效

近期由於個人的某些因素作怪,導致沒有很好地總結和積累,主要是最近一段時間,大多數接觸的都是第三方的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迴圈去掉不讓介面實時刷險,我們看一下點選事件日誌是怎麼走的。明白為什麼實時重新整理會影響我們的點選事件。