Android View touch event delivery mechanism

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:

write picture description here

  1. Touch event delivery order: dispatchTouchEvent–>>onTouch–>>onTouchEvent–>>onClick.
  2. 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.
  3. 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.
  4. 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.

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326341505&siteId=291194637