創建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。