安卓事件分發框架

  1. dispatchTouchEvent
    1. 代碼不長,重要的地方有兩個,分別是調用mOnTouchListener.onTouch(this, event)和onTouchEvent(event)
    2. 12行默認返回false
    3. 29到34行:如果mOnTouchListener不爲空,執行mOnTouchListener.onTouch(this, event)方法,這個方法就是public void setOnTouchListener(OnTouchListener l) {    getListenerInfo().mOnTouchListener = l;},如果onTouch(this, event)方法返回true,result=true
    4. 36到38,如果上面onTouch方法返回true,不會執行下面的onTouchEvent(event)方法,接下來看onTouchEvent方法
  2. onTouchEvent
    1. 這個代碼稍微長一點,還是抓重點分析
    2. 代碼中兩次出現了下面的判斷
      (((viewFlags & CLICKABLE) == CLICKABLE
              || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)       
      || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
      第一次:7到16行,狀態爲DISABLE即代碼中調用view.setEnable(true)時,返回上面的結果
      第二次:23行到133行,即函數結束前的一個大括號,滿足上面的條件返回true這裏說明只要View滿足clickable、longClickable和contextClickable(api23開始支持)的任何一項,onTouchEvent返回true,返回true意味着可以消費onTouchEvent事件,使得事件不再繼續向上傳遞。
      舉個例子:下面的代碼onTouchEvent事件的
      case MotionEvent.ACTION_MOVE:不會執行,後面會說明原因。
      public class MyView extends TextView {
          protected static final String TAG "MyButton";
          public MyView(Context context) {
              super(context);
          }

          public MyView(Context context, AttributeSet attrs) {
              super(context, attrs);
          }

          public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
              super(context, attrs, defStyleAttr);
          }

          @Override
          public boolean onTouchEvent(MotionEvent event) {
              int action = event.getAction();
              switch (action)
              {
                  case MotionEvent.ACTION_DOWN:
                      Log.e(TAG"MyView onTouchEvent ACTION_DOWN");
                      break;
                  case MotionEvent.ACTION_MOVE:
                      Log.e(TAG"MyView onTouchEvent ACTION_MOVE");
                      break;
                  case MotionEvent.ACTION_UP:
                      Log.e(TAG"MyView onTouchEvent ACTION_UP");
                      break;
                  default:
                      break;
              }
              return super.onTouchEvent(event);
          }
      }
    3. 26到131行對event三種動作的解析,核心代碼出來了
    4. 先說ACTION_DOWN,Touch的第一個動作計劃司ACOIN_DOWN,80到105行.這裏面設計三個動作分別是onclick,longclick和CheckForTap
      1. 88行,判斷view是否在可以滑動的組件裏面,例如:scrollview和listview
        public boolean isInScrollingContainer() {
            ViewParent p = getParent();
            while (p != null && p instanceof ViewGroup) {
                if (((ViewGroup) p).shouldDelayChildPressedState()) {
                    return true;
                }
                p = p.getParent();
            }
            return false;
        }
      2. 94到99行,如果發生touch事件的view在可以滑動的組件裏面,執行postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());這個方法的意思是延遲 TAP_TIMEOUT(100毫秒)後,執行CheckForTap()方法,如下:
        private final class CheckForTap implements Runnable {
            public float x;
            public float y;

            @Override
            public void run() {
                mPrivateFlags &= ~PFLAG_PREPRESSED;
                setPressed(truexy);
                checkForLongClick(ViewConfiguration.getTapTimeout(), xy);
            }
        }
        三行代碼:
        1. 設置狀態mPrivateFlags爲false,這個後面後用到
        2. 設置press狀態
        3. checkForLongClick,longClick事件,注意這裏的參數傳的是ViewConfiguration.getTapTimeout(),  即檢查滑動延遲的TAP_TIMEOUT(100毫秒) 這個函數下面詳細說明
      3. 繼續向下103行checkForLongClick(0, x, y)有調用了這個函數
        1. 很簡單就是調用mOnLongClickListener.onLongClick方法,注意下面的代碼做了對LONG_CLICKABLE的判斷,也即是說如果設置了view.setOnLongClickListener(false)則無法執行onLongClick事件
          private void 
          checkForLongClick(int delayOffset, float x, float y) {
              if ((mViewFlags LONG_CLICKABLE) == LONG_CLICKABLE) {
                  mHasPerformedLongPress false;

                  if (mPendingCheckForLongPress == null) {
                      mPendingCheckForLongPress new CheckForLongPress();
                  }
                  mPendingCheckForLongPress.setAnchor(x, y);
                  mPendingCheckForLongPress.rememberWindowAttachCount();
                  postDelayed(mPendingCheckForLongPress,
                          ViewConfiguration.getLongPressTimeout() - delayOffset);
              }
          }
        2. onLongClick觸發的條件是執行完ACTION_DOWN動作後500毫秒,這裏的兩個動作都採用了postDelay的方式執行,這裏是爲了方便ACTION_UP或ACTION_MOVE動作發生時,可以直接將未執行的延遲任務remove掉
      4. ACTION_DOWN動作說完了,總結一下就是:
        1. 如果執行ACTION_DOWN的view在可以滑動的組件內,猶豫不能確定是不是滑動作,給了一個100毫秒的延遲動作
        2. 延遲500毫秒執行longClick事件
    5. 27到78行下面看最複雜的ACTION_UP事件
      1. 注意看33行的focusTaken判斷,再看50行,連起來就是如果view可以獲得焦點並且已經獲得了焦點則不能再執行click事件(例如edittext不能執行onClick)
      2. 51到53說明了即使是立即執行的onClick方法也推薦使用post,目的就是使view點擊後視圖狀態先發生變化,真是煞費苦心啊
      3. 45到47行,removeLongPressCallback();此時如果postDelay500ms的longClock事件沒有發生,將其remove掉
    6. 剩下的兩個時間ACTION_CANCEL和ACTION_MOVE兩個事件比較簡單,做了一些動作處理,設置Press狀態,移除longClick和TapCallBack動作
    7. 至此onTouchEvent解析完畢,有點羅嗦,總結一下
      1. 代碼中setEnable和setClickable的作用相信很多人都曾困擾過,setEnable爲false不能點擊,setLongClickable爲false不能長按,setClickable爲false對onClick事件沒有直接影響,就是名字唬人而已
      2. View的onClickable、onLongClickable,onContextClickable任一成立,該View會消費此onTouchEvent事件,這個非常重要
      3. longClick事件在TOUCH_DOWN中連續摁下500ms執行,onClick在TOUCH_UP中執行,兩者可以同時執行,longClick返回false時。處於焦點狀態的View不能執行onClick事件
  3. 未完待續
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章