Event Distribution Mechanism of Android Custom View (I)

Event Distribution Mechanism of Android Custom View (I)

Say what I said before.

In the development, we write a custom control, an inescapable topic is event distribution. And this is also the most common interview question in an interview, but I found that many interviewers even answer this question vaguely. Not knowing why it is so. So I wrote this blog post specifically. Maybe I am not so official, I just want to say my own understanding. Although I can't say that I can help you, at least I won't mistake my children. Ha ha ha.

  • brief introduction
  • View Event Distribution

brief introduction

I found that much of what I talked about was the event distribution of ViewGroup. And here we start with the simplest, here we start with the event distribution of View.

Touch is a very common event in Android, especially in custom controls, to achieve some dynamic effects, Touch is often processed. There are three main places in Android to handle Touch events:

1. In View, there are two callback functions

@Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.e("View Event distribution:","View->dispatchTouchEvent");
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e("View Event distribution:","View->onTouchEvent:"+event.getAction());
        return super.onTouchEvent(event);
    }

2. In ViewGroup, there are three callback functions

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return super.onTouchEvent(event);
    }

3. In Activity, there are two callback functions

@Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.e("View Event distribution:","View->dispatchTouchEvent");
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e("View Event distribution:","View->onTouchEvent:"+event.getAction());
        return super.onTouchEvent(event);
    }

View Event Distribution

Three scenarios involving View distribution are described above, which are similar to Touch event processing, but also have different aspects.
In this section, let's start with the distribution of View events.

Let's start with a little demo, look at the printed logs, and understand the most basic execution process.

Step 1: Customize View

Note: There is no system control here, in order to make the process shorter and clearer. We inherit View directly

public class TouchView extends View{


    public TouchView(Context context) {
        super(context);
    }

    public TouchView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public TouchView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }


    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.e("View Event distribution:","View->dispatchTouchEvent");
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e("View Event distribution:","View->onTouchEvent:"+event.getAction());
        return super.onTouchEvent(event);
    }
}

Step 2: Set up listeners for Touch and Onclick events in Activity

public class MainActivity extends AppCompatActivity {

    private TouchView costom_view;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        costom_view = (TouchView) findViewById(R.id.costom_view);

        costom_view.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                Log.e("View Event distribution:","View->onTouch:"+event.getAction());
                return false;
            }
        });

        costom_view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.e("View Event distribution:","View->onClick");
            }
        });
    }
}

Step 3: View print logs

The basic process of discovering Touch events from our logs is:

1. Down event: dispatch TouchEvent - - onTouch - - onTouchEvent
2. Move events: dispatch TouchEvent - - onTouch - - onTouchEvent
3. Up Event: dispatch TouchEvent - - onTouch - - onTouchEvent

It is not difficult to find that, first, the Touch event arrives at dispatchTouchEvent(). Let's look at the source code of View.dispatchTouchEvent(), which involves onTouchListener of View and onTouchEvent().

public boolean dispatchTouchEvent(MotionEvent event) {
        // If the event should be handled by accessibility focus first.
        if (event.isTargetAccessibilityFocus()) {
            // We don't have focus or no virtual descendant has it, do not handle the event.
            if (!isAccessibilityFocusedViewOrHost()) {
                return false;
            }
            // We have focus and got the event, then use normal event dispatch.
            event.setTargetAccessibilityFocus(false);
        }

        boolean result = false;

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }

        final int actionMasked = event.getActionMasked();
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // Defensive cleanup for new gesture
            stopNestedScroll();
        }

        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }

        if (!result && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }

        // Clean up after nested scrolls if this is the end of a gesture;
        // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
        // of the gesture.
        if (actionMasked == MotionEvent.ACTION_UP ||
                actionMasked == MotionEvent.ACTION_CANCEL ||
                (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
            stopNestedScroll();
        }

        return result;
    }

From the source code:

1. We can see that onTouchListener and onTouchEvent of View are called here.

2. onTouchListener takes precedence over onTouchEvent. If the onTouchListener method consumes the event by returning true, onTouchEvent will not execute again.

3. There are two prerequisites for onTouch to be executed. The value of the first mOnTouchListener cannot be empty, and the second control that is currently clicked must be enable. So if you have a control that is not enabled, registering an onTouch event for it will never be executed. For this kind of control, if we want to listen for its touch event, we must implement it by overwriting the onTouchEvent method in the control.

4. We can also see that if onTouchEvent() is executed, the return value of dispatchTouchEvent() is the return value of onTouchEvent(). In fact, what really works is dispatchTouchEvent(), onTouchEvent() is just called by dispatchTouchEvent().

Through the above summary, we now do some experiments:
1. Set the return value of onTouchListener to true. From the dispatchTouchEvent() source code, we find that onTouchEvent will not be executed again. The onClick event will not execute either. We also looked at the source code of onTouchEvent and found that the onClick event was executed in the Up event.

2. Set the return value of dispatchTouchEvent() to true, and find that it has no effect. Now, let's change the return value of dispatch TouchEvent to false to see what happens.

So in order for a subsequent method to execute, the dispatchTouchEvent() method must return true. We also got the answer through onTouchEvent.

summary

1. View's onTouchListener and onTouchEvent methods are called in dispatchTouchEvent, and onTouch takes precedence over onTouchEvent. If the event is consumed in the onTouch method by returning true, the onTouchEvent will no longer execute.

2. There are two prerequisites for onTouch to be executed. The value of the first mOnTouchListener cannot be empty, and the second control that is currently clicked must be enable. So if you have a control that is not enabled, registering an onTouch event for it will never be executed. For this kind of control, if we want to listen for its touch event, we must implement it by overwriting the onTouchEvent method in the control.

3. To listen for View events, the dispatchTouchEvent() method must return true.

4. As a programmer, don't build a car behind closed doors. Sharing is also a kind of learning.

Source code

Source Download

Keywords: Android REST

Added by caaronbeasley on Wed, 19 Jun 2019 22:15:49 +0300