Android Event distribution(1)

Do you have such a question? What is the difference between onTouch and onTouchEvent and how should it be used? Why does the ListView introduce a sliding menu function, and the ListView can not scroll? Why is the picture in the picture carousel using the button instead of ImageView? And so on … for these questions. I did not give a very detailed answer. Because I know that if you want to completely understand these issues, master Android event distribution mechanism is essential, and Android event distribution mechanism is absolutely not a few words can be clear.

I am ready to take everyone from the source point of view of the analysis, I believe we can more profound understanding of the Android event distribution mechanism.

Read the source code from shallow to deep, step by step, so we also from the simple start, this first with everyone to explore the View event distribution, the next chapter to explore the more difficult ViewGroup event distribution.

Let’s start now! For example, you currently have a very simple project, only one Activity, and Activity only one button. You may already know that if you want to register a click event for this button, just call:

This is done in the onClick method, which can be executed when the button is clicked. You may already know that if you want to add a touch event to this button, just call:

button.setOnClickListener(new OnClickListener() {  
    @Override  
    public void onClick(View v) {  
        Log.d("TAG", "onClick execute");  
    }  
}); 

This is done in the onClick method, which can be executed when the button is clicked. You may already know that if you want to add a touch event to this button, just call:

button.setOnTouchListener(new OnTouchListener() {  
    @Override  
    public boolean onTouch(View v, MotionEvent event) {  
        Log.d("TAG", "onTouch execute, action " + event.getAction());  
        return false;  
    }  
});  

OnTouch method can do more things than onClick, such as judging finger press, lift, move and other events. So if both of my events are registered, which one will be executed first? We have to try to know, run the program click button, You can see the results.

You can see that onTouch takes precedence over onClick, and onTouch is executed twice, once is ACTION_DOWN, once ACTION_UP (you may have multiple ACTION_MOVE executions if you shake it). So the order of event delivery is first passed onTouch, and then passed to onClick

Careful friends should be able to note that the onTouch method has a return value, where we return is false, if we try to onTouch method in the return value into true, and then run once, the results are as follows:

2

We found that the onClick method is no longer executed! Why is this so? You can first understand that the onTouch method returns true to think that the event was consumed by the onTouch and that it will not continue to be passed down.

If so far, all the above knowledge points you are clear, then that your basic usage of the Android event should be mastered. But do not be satisfied with the status quo, let us from the source point of view, what is the principle of the above phenomenon.

First of all you need to know that, as long as you touch any of the controls, it will call the control of the dispatchTouchEvent method. That when we go to the button, it will call the Button class in the dispatchTouchEvent method, but you will find the Button class does not have this method, then go to its parent class TextView to find, you will find TextView There is no such method, it was no way, had no choice but to continue in the TextView Father search find, this time you finally found in the View this method.

Then let’s look at the source of the dispatchTouchEvent method in View:

public boolean dispatchTouchEvent(MotionEvent event) {  
    if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
            mOnTouchListener.onTouch(this, event)) {  
        return true;  
    }  
    return onTouchEvent(event);  
}  

This method is very simple, only a few lines of code! We can see that in this method, the first is to make a judgment, if mOnTouchListener! = Null, (mViewFlags & ENABLED_MASK) == ENABLED and mOnTouchListener.onTouch (this, event) these three conditions are true, return True, otherwise it will execute the onTouchEvent (event) method and return.

Look at the first condition, mOnTouchListener this variable is where the assignment? We found in the View after the discovery of the following methods:

public void setOnTouchListener(OnTouchListener l) {  
    mOnTouchListener = l;  
} 

Bingo! Found, mOnTouchListener is setOnTouchListener method in the assignment, that is, as long as we have registered to the control of the touch event, mOnTouchListener must be assigned.

The second condition (mViewFlags & ENABLED_MASK) == ENABLED is to determine whether the current click of the control is enabled, the button is enabled by default, so this condition is constant to true.

The third condition is more critical, mOnTouchListener.onTouch (this, event), in fact, is to callback control registration touch event onTouch method. That is, if we return true in the onTouch method, all three conditions are set, so that the entire method returns true directly. If we return false in the onTouch method, we will go to the onTouchEvent (event) method.

Now we can combine the previous examples to analyze, first of all in the dispatchTouchEvent is the first implementation of the onTouch method, so onTouch certainly have to take precedence over onClick implementation, but also confirms the just print results. And if it returns true in the onTouch method, the dispatchTouchEvent method returns true and does not continue. The print results also confirmed that if onTouch returns true, onClick will not be executed again.

According to the above analysis of the source code, the principle of our previous example to explain the results of the operation. The above analysis also revealed an important message, that is, onClick call is certainly in the onTouchEvent (event) method! Then we immediately look at the source code onTouchEvent, as follows:

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:  
                boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;  
                if ((mPrivateFlags & PRESSED) != 0 || prepressed) {  
                    // 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  
                        removeLongPressCallback();  
                        // Only perform take click actions if we were in the pressed state  
                        if (!focusTaken) {  
                            // Use a Runnable and post this rather than calling  
                            // performClick directly. This lets other visual state  
                            // of the view update before click actions start.  
                            if (mPerformClick == null) {  
                                mPerformClick = new PerformClick();  
                            }  
                            if (!post(mPerformClick)) {  
                                performClick();  
                            }  
                        }  
                    }  
                    if (mUnsetPressedState == null) {  
                        mUnsetPressedState = new UnsetPressedState();  
                    }  
                    if (prepressed) {  
                        mPrivateFlags |= PRESSED;  
                        refreshDrawableState();  
                        postDelayed(mUnsetPressedState,  
                                ViewConfiguration.getPressedStateDuration());  
                    } else if (!post(mUnsetPressedState)) {  
                        // If the post failed, unpress right now  
                        mUnsetPressedState.run();  
                    }  
                    removeTapCallback();  
                }  
                break;  
            case MotionEvent.ACTION_DOWN:  
                if (mPendingCheckForTap == null) {  
                    mPendingCheckForTap = new CheckForTap();  
                }  
                mPrivateFlags |= PREPRESSED;  
                mHasPerformedLongPress = false;  
                postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());  
                break;  
            case MotionEvent.ACTION_CANCEL:  
                mPrivateFlags &= ~PRESSED;  
                refreshDrawableState();  
                removeTapCallback();  
                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 = mTouchSlop;  
                if ((x < 0 - slop) || (x >= getWidth() + slop) ||  
                        (y < 0 - slop) || (y >= getHeight() + slop)) {  
                    // Outside button  
                    removeTapCallback();  
                    if ((mPrivateFlags & PRESSED) != 0) {  
                        // Remove any future long press/tap checks  
                        removeLongPressCallback();  
                        // Need to switch from pressed to not pressed  
                        mPrivateFlags &= ~PRESSED;  
                        refreshDrawableState();  
                    }  
                }  
                break;  
        }  
        return true;  
    }  
    return false;  
}  

Compared to just the dispatchTouchEvent method, the onTouchEvent method is a lot more complicated, but it does not matter, we can only pick it.

First in the 14th line we can see that if the control is clickable will enter the 16th line of the switch to judge, and if the current event is raised fingers, will enter into the MotionEvent.ACTION_UP this case The After all the judgments, the implementation of the 38th of the performClick () method, then we enter into this method to see:

public boolean performClick() {  
    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);  
    if (mOnClickListener != null) {  
        playSoundEffect(SoundEffectConstants.CLICK);  
        mOnClickListener.onClick(this);  
        return true;  
    }  
    return false;  
}  

You can see, as long as mOnClickListener is not null, it will call it onClick method, that mOnClickListener is where the assignment? After finding the following method to find:

public void setOnClickListener(OnClickListener l) {  
    if (!isClickable()) {  
        setClickable(true);  
    }  
    mOnClickListener = l;  
}  

Everything is so clear! When we call the setOnClickListener method to register a click event to the control, it will assign a value to mOnClickListener. And then whenever the control is clicked, will be in the performClick () method callback control in the onClick method.

So that the whole process of distribution of View let us find out! But do not happy too early, now not the end, there is a very important point of knowledge need to explain, is the touch event level delivery. We all know that if a control to register a touch event, each time you click on it will trigger a series of ACTION_DOWN, ACTION_MOVE, ACTION_UP and other events. It is important to note that if you return false after executing ACTION_DOWN, the latter series of other actions will no longer be executed. Simply put, that is, when dispatchTouchEvent in the event distribution, only the previous action returns true, will trigger the next action.

Speaking of which, a lot of friends must have a huge question. Is this not contradictory? In the previous example, obviously in the onTouch event which returned false, ACTION_DOWN and ACTION_UP not have been implemented? In fact, you are only confused by the false impression, let us carefully analyze, in the previous example, we in the end what is the return.
Reference to the analysis of the source code in front of us, first in the onTouch event returns false, it will enter the onTouchEvent method, and then we look at the details of the onTouchEvent method. Because we click on the button, it will enter the 14th line of this if the judge inside, and then you will find that no matter what the current action, and ultimately will go to the first 89 lines, return a true.

Is there a feeling of being deceived? Obviously in the onTouch event returned to false, the system or in the onTouchEvent method to help you return true. For this reason, in the previous example, ACTION_UP can be executed.

Then we can change a control, replace the button with ImageView, and then register it with a touch event and return false. As follows:

imageView.setOnTouchListener(new OnTouchListener() {  
    @Override  
    public boolean onTouch(View v, MotionEvent event) {  
        Log.d("TAG", "onTouch execute, action " + event.getAction());  
        return false;  
    }  
}); 

Run the program, click on ImageView, you will find the results are as follows:

3

After ACTION_DOWN execution, the latter series of actions will not be implemented. This is why? Because the ImageView and the button is different, it is the default can not click, so in the onTouchEvent line 14 judgment can not enter into the inside. Directly jump to the first 91 to return to false. This will lead to the subsequent other actions can not be implemented.

Well, about the event distribution of View, all the things I want to say are here. Now let’s take a look at the three questions mentioned at the beginning of the day, believing that everyone will have a deeper understanding.

What is the difference between onTouch and onTouchEvent, and how to use it?

As you can see from the source, both methods are called in the View dispatchTouchEvent of the View, and onTouch takes precedence over onTouchEvent. If the event is consumed by returning true in the onTouch method, the onTouchEvent will no longer be executed.

Also note that onTouch is able to get implementation requires two prerequisites, the first mOnTouchListener value can not be empty, and the second current click control must be enabled. So if you have a control that is non-enabled, then registering it onTouch events will never be executed. For this type of control, if we want to listen to its touch event, it must be done by overriding the onTouchEvent method in the control.

Why does the ListView introduce a sliding menu function, and the ListView can not scroll?

The use of the Button in the picture carousel is mainly because the Button is clickable and the ImageView is not clickable. If you want to use ImageView, you can have two ways to change. First, in the ImageView onTouch method to return true, this can ensure that ACTION_DOWN after the other actions can be implemented in order to achieve the effect of picture scroll. Second, in the layout file inside to add an imageView android: clickable = “true” attribute, so that ImageView into a click, even in the onTouch returned false, ACTION_DOWN after the other action can also be implemented.

Today’s explanation to here, I believe we are now on the Android event distribution mechanism has a further understanding, in the back of the article I will take you to explore the Android ViewGroup event distribution mechanism

Advertisements

One thought on “Android Event distribution(1)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s