Android事件分發機制一

創建Demo

MainActivity.java

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //初始化控件
        Button btn = (Button) findViewById(R.id.btn);
        //btn添加onClick監聽
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.i("chuyibo","onClick事件執行");
            }
        });
        //btn添加onTouch監聽
        btn.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                Log.i("chuyibo","onTouch事件執行了"+motionEvent.getAction());
                return false;
            }
        });
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#fff">
    <Button
        android:layout_width="150dp"
        android:layout_height="50dp"
        android:id="@+id/btn"
        android:layout_centerInParent="true"
        android:background="#761598"
        android:text="點擊"
        android:textSize="16sp"
        android:textColor="#fff"/>
</RelativeLayout>

運行

這裏寫圖片描述

點擊Button查看Log日誌

這裏寫圖片描述

根據日誌得出onTouch()方法執行了三次(第一次是DOWN事件,第二次是MOVE事件,第三次是UP事件),onClick()方法執行了一次。而且,onTouch()方法執行完後纔去執行的onClick()方法。onTouch()方法有一個返回值,默認爲false。如果把該返回值設置爲true,再次點擊Button查看Log日誌。

btn.setOnTouchListener(new View.OnTouchListener() {
     @Override
     public boolean onTouch(View view, MotionEvent motionEvent) {
         Log.i("chuyibo","onTouch事件執行了"+motionEvent.getAction());
         return true;//返回值設置爲true
     }
 });

點擊Button查看Log日誌

這裏寫圖片描述

根據日誌得出onTouch()方法執行了兩次(第一次是DOWN事件,第二次是UP事件),而onClick()方法並沒有執行。

源碼分析

對Demo的代碼執行結果通過源碼進行分析,點擊Button時,會先調用Button的dispatchTouchEvent()方法來處理觸摸事件。該方法在Button的父類View中,源碼如下:

dispatchTouchEvent()

public boolean dispatchTouchEvent(MotionEvent event) {
        if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
                mOnTouchListener.onTouch(this, event)) {
            return true;
        }
        return onTouchEvent(event);
    }

(mViewFlags & ENABLED_MASK) == ENABLED判斷控件是否可點擊,Button默認是可點擊狀態,條件成立。通過查找發現mOnTouchListener 對象是通過setOnTouchListener()方法進行賦值的,源碼如下:

setOnTouchListener()

public void setOnTouchListener(OnTouchListener l) {
    mOnTouchListener = l;
  }

因此當Button設置了onTouch監聽,onTouch()方法返回true時,dispatchTouchEvent()方法返回true,返回false時,執行onTouchEvent()方法,dispatchTouchEvent()方法返回onTouchEvent()方法的返回值。查看onTouchEvent()源碼如下:

onTouchEvent()

public boolean onTouchEvent(MotionEvent event) {
      final int viewFlags = mViewFlags;

      if ((viewFlags & ENABLED_MASK) == DISABLED) {
          // A disabled view that is clickable still consumes the touch
          // events, it just doesn't respond to them.
          return (((viewFlags & CLICKABLE) == CLICKABLE ||
                  (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
      }

      if (mTouchDelegate != null) {
          if (mTouchDelegate.onTouchEvent(event)) {
              return true;
          }
      }

      if (((viewFlags & CLICKABLE) == CLICKABLE ||
              (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
          switch (event.getAction()) {
              case MotionEvent.ACTION_UP:
                  if ((mPrivateFlags & PRESSED) != 0) {
                      // take focus if we don't have it already and we should in
                      // touch mode.
                      boolean focusTaken = false;
                      if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                          focusTaken = requestFocus();
                      }

                      if (!mHasPerformedLongPress) {
                          // This is a tap, so remove the longpress check
                          if (mPendingCheckForLongPress != null) {
                              removeCallbacks(mPendingCheckForLongPress);
                          }

                          // Only perform take click actions if we were in the pressed state
                          if (!focusTaken) {
                              performClick();
                          }
                      }

                      if (mUnsetPressedState == null) {
                          mUnsetPressedState = new UnsetPressedState();
                      }

                      if (!post(mUnsetPressedState)) {
                          // If the post failed, unpress right now
                          mUnsetPressedState.run();
                      }
                  }
                  break;

              case MotionEvent.ACTION_DOWN:
                  mPrivateFlags |= PRESSED;
                  refreshDrawableState();
                  if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
                      postCheckForLongClick();
                  }
                  break;

              case MotionEvent.ACTION_CANCEL:
                  mPrivateFlags &= ~PRESSED;
                  refreshDrawableState();
                  break;

              case MotionEvent.ACTION_MOVE:
                  final int x = (int) event.getX();
                  final int y = (int) event.getY();

                  // Be lenient about moving outside of buttons
                  int slop = ViewConfiguration.get(mContext).getScaledTouchSlop();
                  if ((x < 0 - slop) || (x >= getWidth() + slop) ||
                          (y < 0 - slop) || (y >= getHeight() + slop)) {
                      // Outside button
                      if ((mPrivateFlags & PRESSED) != 0) {
                          // Remove any future long press checks
                          if (mPendingCheckForLongPress != null) {
                              removeCallbacks(mPendingCheckForLongPress);
                          }

                          // Need to switch from pressed to not pressed
                          mPrivateFlags &= ~PRESSED;
                          refreshDrawableState();
                      }
                  } else {
                      // Inside button
                      if ((mPrivateFlags & PRESSED) == 0) {
                          // Need to switch from not pressed to pressed
                          mPrivateFlags |= PRESSED;
                          refreshDrawableState();
                      }
                  }
                  break;
          }
          return true;
      }

      return false;
  }

當事件爲UP事件時,會執行到switch中的MotionEvent.ACTION_UP項,然後執行performClick()方法。源碼如下:

performClick()

public boolean performClick() {
      sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

      if (mOnClickListener != null) {
          playSoundEffect(SoundEffectConstants.CLICK);
          mOnClickListener.onClick(this);
          return true;
      }

      return false;
  }

mOnClickListener對象是通過setOnClickListener()方法進行賦值的,Demo中Button設置了onClick監聽,所以該對象不爲null。if判斷爲true,執行mOnClickListener.onClick()方法。

注意事項

dispatchTouchEvent()方法返回值

dispatchTouchEvent()方法是有返回值的,當Button設置了onTouch監聽,並且onTouch()方法返回true,該方法返回值爲true。當Button沒有設置onTouch監聽或者onTouch()方法返回false,該方法的返回值有onTouchEvent()方法的返回值決定。onTouchEvent()方法始終會執行到第94行返回true。因此不重寫dispatchTouchEvent()方法,該方法始終會返回true。

表格

這裏寫圖片描述

Button事件流程圖

這裏寫圖片描述

結論

1、當View同時設置了onTouch監聽和onClick監聽,onTouch()方法優先於onClick()方法執行,點擊一次Button時會觸發多個事件(UP事件,DOWN事件,如果光標劃定會有多個MOVE事件),會多次執行onTouch()方法,最後再執行onClick()方法。

2、當View同時設置了onTouch監聽和onClick監聽,onTouch()方法返回true,onClick()方法不執行。onTouch()方法返回false,onClick()方法執行。

3、不重寫onTouchEvent()方法,Button的dispatchTouchEvent()方法始終返回true。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章