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。

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