Example
Customize a MyCustomView
public class MyCustomView extends View { private String TAG = "MyButton"; public MyCustomView(Context context) { super(context); } public MyCustomView(Context context, AttributeSet attrs) { super(context, attrs); } public MyCustomView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean dispatchTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: Log.e(TAG, "dispatchTouchEvent----->>ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.e(TAG, "dispatchTouchEvent----->>ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.e(TAG, "dispatchTouchEvent----->>ACTION_UP"); break; } return super.dispatchTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: Log.e(TAG, "onTouchEvent----->>ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.e(TAG, "onTouchEvent----->>ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.e(TAG, "onTouchEvent----->>ACTION_UP"); break; } return super.onTouchEvent(event); } }
Override the dispatchTouchEvent and onTouchEvent methods, and add print logs for three touch events.
MainActivity is called as follows:
public class MainActivity extends Activity { private MyCustomView button; private String TAG = "MainActivity"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate (savedInstanceState); setContentView(R.layout.activity_main); button = (MyCustomView) findViewById(R.id.button); Log.e(TAG, "the view is clickable " + button.isClickable()); button.setClickable(true); button.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: Log.e(TAG, "onTouch------->>ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.e(TAG, "onTouch------->>ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.e(TAG, "onTouch------->>ACTION_UP"); break; } return false; } }); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.e(TAG, "onClick------->>onClick"); } }); } }
Click to customize MyCustomView, and the result is printed as follows:
07-29 11:03:51.714 20278-20278/com.xjp.toucheventdemo E/MainActivity﹕ the view is clickable false 07-29 11:03:54.877 20278-20278/com.xjp.toucheventdemo E/MyButton﹕ dispatchTouchEvent----->>ACTION_DOWN 07-29 11:03:54.878 20278-20278/com.xjp.toucheventdemo E/MainActivity﹕ onTouch------->>ACTION_DOWN 07-29 11:03:54.878 20278-20278/com.xjp.toucheventdemo E/MyButton﹕ onTouchEvent----->>ACTION_DOWN 07-29 11:03:54.929 20278-20278/com.xjp.toucheventdemo E/MyButton﹕ dispatchTouchEvent----->>ACTION_MOVE 07-29 11:03:54.929 20278-20278/com.xjp.toucheventdemo E/MainActivity﹕ onTouch------->>ACTION_MOVE 07-29 11:03:54.929 20278-20278/com.xjp.toucheventdemo E/MyButton﹕ onTouchEvent----->>ACTION_MOVE 07-29 11:03:54.929 20278-20278/com.xjp.toucheventdemo E/MyButton﹕ dispatchTouchEvent----->>ACTION_UP 07-29 11:03:54.931 20278-20278/com.xjp.toucheventdemo E/MainActivity﹕ onTouch------->>ACTION_UP 07-29 11:03:54.931 20278-20278/com.xjp.toucheventdemo E/MyButton﹕ onTouchEvent----->>ACTION_UP 07-29 11:03:54.936 20278-20278/com.xjp.toucheventdemo E/MainActivity﹕ onClick------->>onClick
Analysis: It can be seen from the above print
- There are three main touch events and the execution order is: ACTION_DOWN, ACTION_MOVE, ACTION_UP. That is to say, the action of pressing ACTION_DOWN is performed first. After pressing, the finger may move. When moving, the ACTION_MOVE action is triggered. When the finger is lifted, the ACTION_UP action is triggered, and the sequence execution of touch events ends. Of course, touch events are not limited to these three behaviors, but we mainly analyze these three here.
- The order of the methods executed by the touch event process is: dispatchTouchEvent, onTouch, onTouchEvent. Finally the onClick click event is executed. That is, the order should be: dispatchTouchEvent–>>onTouch–>>onTouchEvent–>>onClick
- The onClick click event is executed after the touch event ACTION_UP is executed.
Why do the above three phenomena and situations occur? Now we can only see the result from the print log, but we don't know the internal reason. To peek at the internal reason, read the fuck code. Based on Android2.0 source code analysis View. View's touch event dispatch is executed from the View#dispatchTouchEvent method. As for why from here, we will talk about it later.
View#dispatchTouchEvent touch event dispatch
public boolean dispatchTouchEvent(MotionEvent event) { if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)) { return true; } return onTouchEvent(event); }
Analysis: The method implementation is very simple. When the if condition is met, it returns true to exit the method. When the condition is not met, the onTouchEvent method is executed and the return value of the method is returned.
1. So under what circumstances is the mOnTouchListener != null condition satisfied? View the View source code and find that the following method is called:
public void setOnTouchListener(OnTouchListener l) { mOnTouchListener = l; }
When the developer sets the View#setOnTouchListener touch event for the corresponding View, the mOnTouchListener != null condition is established.
2.View is enabled by default, so the second condition is true.
3. The current two conditions are established, and the third condition interface method mOnTouchListener.onTouch(this, event) is executed. According to the return value of the method, it is determined whether the if condition is satisfied. This method is implemented when the developer sets the View#setOnTouchListener touch event. When the onTouch method returns false, the dispatchTouchEvent method will execute the onTouchEvent method, otherwise the onTouchEvent method will not be executed.
Summary:
1. The return value of the onTouch interface method determines whether to execute the onTouchEvent method.
2. As long as the return value of the onTouch interface method is true, the dispatchTouchEvent method must return true, otherwise the return value of dispatchTouchEvent is determined according to the return value of the onTouchEvent method.
View#onTouchEvent
Enter the onTouchEvent method:
public boolean onTouchEvent(MotionEvent event) { final int viewFlags = mViewFlags; ................ 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; }
Analysis: The code is a bit long, first enter the method to determine whether the if condition is true? If the View is clickable or can be clicked for a long time, the if condition is satisfied, enter the if judgment, and execute the ACTION_UP branch.
1. In line 26 of the code, the performClick method is called to execute the click event of the View
public boolean performClick() { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); if (mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); mOnClickListener.onClick(this); return true; } return false; }
This method judges if mOnClickListener! =null condition is established, then execute mOnClickListener.onClick(this); interface method. When is the condition established? When the click listener event is set for the current View, the condition is satisfied, so the interface onClick method is called. There are the following methods in the View class:
public void setOnClickListener(OnClickListener l) { if (!isClickable()) { setClickable(true); } mOnClickListener = l; }
First determine whether the current View is clickable? If it is not clickable, set it to be clickable first, and then assign an operation to mOnClickListener. Summary: As long as the setOnClickListener click listener event is set for any View, no matter whether the View is clickable or not, it is finally set to the clickable state.
2. Only when the current View is in a clickable or long-pressed state, it enters the if condition judgment, then performs the corresponding gesture operation, and finally returns true. That is, as long as the View is clickable, the onTouchEvent method returns true, so the dispatchTouchEvent method returns true.
3. As long as the current View is not clickable or long-pressed, the if condition is not established, no operation is performed, and false is returned directly. That is to say, when the View is not clickable, the onTouchEvent method returns false, so the dispatchTouchEvent method returns false.
4. The onClick method is executed in the ACTION_UP gesture, that is, the onClick method is executed when the gesture is raised.
At this point, the Android View touch event delivery has been analyzed. If the conditions are met, the entire touch event delivery process is: dispatchTouchEvent–>>onTouch–>>onTouchEvent–>>onClick. Now let's verify the following situations:
The return value of onTouch method is true
Change it to the following
button.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: Log.e(TAG, "onTouch------->>ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.e(TAG, "onTouch------->>ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.e(TAG, "onTouch------->>ACTION_UP"); break; } return true; } });
At this point, click View to print as follows:
07-29 14:42:22.969 2964-2964/com.xjp.toucheventdemo E/MyButton﹕ dispatchTouchEvent----->>ACTION_DOWN 07-29 14:42:22.970 2964-2964/com.xjp.toucheventdemo E/MainActivity﹕ onTouch------->>ACTION_DOWN 07-29 14:42:22.987 2964-2964/com.xjp.toucheventdemo E/MyButton﹕ dispatchTouchEvent----->>ACTION_MOVE 07-29 14:42:22.987 2964-2964/com.xjp.toucheventdemo E/MainActivity﹕ onTouch------->>ACTION_MOVE 07-29 14:42:22.987 2964-2964/com.xjp.toucheventdemo E/MyButton﹕ dispatchTouchEvent----->>ACTION_UP 07-29 14:42:22.988 2964-2964/com.xjp.toucheventdemo E/MainActivity﹕ onTouch------->>ACTION_UP
When the onTouch method returns true, the onTouchEvnet method is not executed, so the onClick event is not executed. It can be understood that at this time onTouch has consumed the touch event, and will not continue to pass the touch event down. So if you don't want your View to execute the onTouchEvent method, you can set the onTouch event and the return value is true.
View is not clickable
In the MainActivity at the beginning of the article, I added a line of code button.setClickable(true); The purpose is to make the current View clickable, but by default, except for a few controls of Button and TextView, most of the other View controls are not clickable by default. state unless you set View#setClickable(true) or View#setOnClickListener. Now I remove the button.setClickable(true); line of code in MainActivity and do not set the setOnClickListener event
public class MainActivity extends Activity { private MyCustomView button; private String TAG = "MainActivity"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate (savedInstanceState); setContentView(R.layout.activity_main); button = (MyCustomView) findViewById(R.id.button); Log.e(TAG, "the view is clickable " + button.isClickable()); // button.setClickable(true); button.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: Log.e(TAG, "onTouch------->>ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.e(TAG, "onTouch------->>ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.e(TAG, "onTouch------->>ACTION_UP"); break; } return false; } }); // button.setOnClickListener(new View.OnClickListener() { // @Override // public void onClick(View v) { // Log.e(TAG, "onClick------->>onClick"); // } // }); } }
The log prints the log as follows:
07-29 14:57:03.656 4896-4896/com.xjp.toucheventdemo E/MyButton﹕ dispatchTouchEvent----->>ACTION_DOWN 07-29 14:57:03.658 4896-4896/com.xjp.toucheventdemo E/MainActivity﹕ onTouch------->>ACTION_DOWN 07-29 14:57:03.658 4896-4896/com.xjp.toucheventdemo E/MyButton﹕ onTouchEvent----->>ACTION_DOWN
I don't know if you found wood there? The print here shows that only the ACTION_DOWN finger operation is performed. What about other gesture operations? Not implemented, why?
The situation is this: when the onTouch method returns false, the dispatchTouchEvent method will execute the onTouchEvent method, but since the View is not clickable, the onTouchEvent does not execute the if conditional body, that is, the onTouchEvent method returns false, which causes the dispatchTouchEvent method to return false, Since the dispatchTouchEvent method returns false, the subsequent gesture operations ACTION_MOVE and ACTION_UP cannot be executed.
Summary: If we divide the gesture operation into three processes: ACTION_DOWN, ACTION_MOVE, ACTION_UP. Only when the dispatchTouchEvent method returns true, the system will perform the gesture operation behind the corresponding process.
Summarize
So far, the Android View touch event delivery mechanism has been analyzed, and now it is reflected in a flowchart:
- Touch event delivery order: dispatchTouchEvent–>>onTouch–>>onTouchEvent–>>onClick.
- The difference between onTouch and onTouchEvent: the two methods are called successively in dispatchTouchEvent. The onTouch method is executed only if the touch event View#setOnTouchListener is set for the View; the return value of the onTouch method determines whether to execute the onTouchEvent method.
- The sequence of gesture operation execution is ACTION_DOWN, ACTION_MOVE, ACTION_UP, and the following gestures will be executed only when the dispatchTouchEvent method returns a true value.
- The invocation of the onClick method is performed in the ACTION_UP gesture of onTouchEvent, that is, when the gesture is raised, the invocation of the onClick method will not be triggered until the gesture operation ends.