View Event Passing Source Parsing
View is the smallest basic UI control in Android, which is basically the last control to handle events when touching events are passed to it; it is different from ViewGroup, it has no other subclasses; and the source code of event transmission of View is the best to master, so here carefully analyze the source code, learn to record the source code; Code using Android 2.2 source code, because this version of View event source code is the simplest and most direct; it is conducive to learning, and 5.0 to see half a day dizzy, Android 2.2 to 5.0 source code logic can not change too much, can only be a lot of refactoring optimization; so starting from 2.2.~~
dispatchTouchEvent
When a touch event is passed to a UI control, it is the dispatchTouchEvent method that is first passed to the UI control and enters the source code of the method.
/** * Pass the touch screen motion event down to the target view, or this * view if it is the target. * * @param event The motion event to be dispatched. * @return True if the event was handled by the view, false otherwise. */ public boolean dispatchTouchEvent(MotionEvent event) { if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)) { return true; } return onTouchEvent(event); }
The source comment above says: Pass down the touchscreen event to the target View, or pass it to the View if the View is the ultimate goal;
The parameter means the event behavior to be distributed (that is, the touch event behavior)
Return true if this event is processed by View, otherwise false
Line 9 is an if condition judgment which has three conditions and is a union. If all the conditions are established, it will return true in parentheses; it will not execute code further, otherwise it will continue to execute.
Line 13 executes the onTouchEvent method:
First look at the three conditions of if:
1: The first condition is mOnTouchListener!= null
What object is mOnTouchListener? It is an object that touches and monitors an implementation class of OnTouchListener (OnTouchListener is an abstract interface), which is injected by the developer himself, and can also be found from the source code.
From here, we can see that mOnTouchListener is assigned. If we do not set setOnTouchListener, mOnTouchListener is null, and if condition is not valid./** * Register a callback to be invoked when a touch event is sent to this view. * @param l the touch listener to attach to this view */ public void setOnTouchListener(OnTouchListener l) { mOnTouchListener = l; }
After we set up setOnTouchListener, we will make the second condition judgement.
2: (mViewFlags & ENABLED_MASK) == ENABLED
This condition means that the View is currently available and can be used.
Looking at each bit operator individually, I am not sure whether it is true or not. In fact, it can be confirmed from the source code.
This method enables View to determine whether View is available or not. The condition is (mViewFlags & ENABLED_MASK) === ENABLED, which can return true, but not false./** * Returns the enabled status for this view. The interpretation of the * enabled state varies by subclass. * * @return True if this view is enabled, false otherwise. */ @ViewDebug.ExportedProperty public boolean isEnabled() { return (mViewFlags & ENABLED_MASK) == ENABLED; }
3:mOnTouchListener.onTouch(this, event)
This condition is that the subclass implements the interface method that the class calls the subclass implements, although the reference is still the parent class OnTouchListener.
Let's look at the OnTouchListener interface, which has only one onTouch method.
Now think about it, if the subclass implements this interface, don't we have to implement the method in the interface, which just returns a Boolean variable value?/** * Interface definition for a callback to be invoked when a touch event is * dispatched to this view. The callback will be invoked before the touch * event is given to the view. */ public interface OnTouchListener { /** * Called when a touch event is dispatched to a view. This allows listeners to * get a chance to respond before the target view. * * @param v The view the touch event has been dispatched to. * @param event The MotionEvent object containing full information about * the event. * @return True if the listener has consumed the event, false otherwise. */ boolean onTouch(View v, MotionEvent event); }
If we implement the OnTouchListener interface and the method return value is true, then this condition holds.
To sum up a small condition of if, there are three conditions:
First: setOnTouchListener is set, then mOnTouchListener is not null
Second: The View itself is usable
Thirdly, the onTouch method in the mOnTouchListener object returns true.
Otherwise, execute the following onTouchEvent method
onTouchEvent
Following is a step-by-step analysis of the internal execution of the onTouchEvent method:
It's a long way to go, and that's fine. We don't have to look for ^ - ^ and paste the code.
/** * Implement this method to handle touch screen motion events. * * @param event The motion event. * @return True if the event was handled, false otherwise. */ 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; }
(viewFlags & ENABLED_MASK) == DISABLED)
Let's analyze this code in the following steps
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)); }
For this code, we mainly look at the condition in if (viewFlags & ENABLED_MASK) === DISABLED, which means to determine whether the View is available, first of all, in the source code.
/** * This view is disabled. Intrepretation varies by subclass. Use with * ENABLED_MASK when calling setFlags. {@hide} */ static final int DISABLED = 0x00000020; /** * Mask for use with setFlags indicating bits used for indicating whether * this view is enabled {@hide} */ static final int ENABLED_MASK = 0x00000020;
Both DISABLED and ENABLED_MASK are immutable final int values; bitwise operations are used here, & 1 is the only time when the same binary position is 1.
DISAB LED is converted from 16 to 2 digits, which is 0000 0000 0000 0000 0000 0000 00010 0000.
The conversion from ENABLED_MASK to binary is 0000 0000 0000 0000 0000 0000 00010 0000
In fact, the conversion from hexadecimal system to binary system is very simple. Every hexadecimal system can be converted to binary system by itself. If you are still very ignorant, you will understand this introduction. Binary Bit Operation Accumulation Memorandum
After the bit operation of viewFlags & ENABLED_MASK, there are only two results, either 0x00000020 or DISABLED, or 0x0000000 or ENABLED.
/** * This view is enabled. Intrepretation varies by subclass. Use with * ENABLED_MASK when calling setFlags. {@hide} */ static final int ENABLED = 0x00000000;
If not, the if condition returns ((viewFlags & CLICKABLE) == CLICKABLE)||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
This means that a click or a long click can return true; as for binary bit operations and the above analysis can not be the same; the other two conditions are used to determine whether the View is clickable and whether a long click can be found in the source code, and then paste the source code, so to see. More clearly:
/** * Indicates whether this view reacts to click events or not. * * @return true if the view is clickable, false otherwise * * @see #setClickable(boolean) * @attr ref android.R.styleable#View_clickable */ @ViewDebug.ExportedProperty public boolean isClickable() { return (mViewFlags & CLICKABLE) == CLICKABLE; } /** * Indicates whether this view reacts to long click events or not. * * @return true if the view is long clickable, false otherwise * * @see #setLongClickable(boolean) * @attr ref android.R.styleable#View_longClickable */ public boolean isLongClickable() { return (mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE; }
Here's a summary: If a View is not available, but as long as it can be clicked or long clicked, it returns true, which consumes the event;
mTouchDelegate != null
Next, look at the next source code
if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } }
This source code means that the View has no touch proxy event set, and if mTouchDelegate is not null, it will enter the condition.
How did mTouchDelegate come from, or from the source code
/** * Sets the TouchDelegate for this View. */ public void setTouchDelegate(TouchDelegate delegate) { mTouchDelegate = delegate; }
The mTouchDeleagte object is null if the user injects it here without injecting it. The above condition is not valid.
public class TouchDelegate {
We can override the onTouchEvent method in TouchDelegate or not, because the class itself has the onTouchEvent method.
The unique structure of this TouchDelegate is as follows:
/** * Constructor * * @param bounds Bounds in local coordinates of the containing view that should be mapped to * the delegate view * @param delegateView The view that should receive motion events */ public TouchDelegate(Rect bounds, View delegateView) { mBounds = bounds; mSlop = ViewConfiguration.get(delegateView.getContext()).getScaledTouchSlop(); mSlopBounds = new Rect(bounds); mSlopBounds.inset(-mSlop, -mSlop); mDelegateView = delegateView; }
Looking at the commentary means that the boundary (size) of the current coordinate containing the view should be mapped to the delegateView, which should be touched by the event. To put it plainly, the touching event of the current view should be handled by another view, which we can see here; no further study, no more. It seems that this agent is seldom set up in the subclasses of View. We can get an agent to see the effect in private.
((viewFlags & CLICKABLE) == CLICKABLE ||(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
Looking down at the next source code, it is a long if conditional statement, which contains a switch statement, that is to say, if this is clickable or long click, the result returns true, the event must be consumed by the View processing, in order to facilitate browsing, and then paste the source code.
You can see the return true; no matter which action you take, as long as you enter the if, all return true; the event is consumed by the View;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; }
Now it's the end of the onTouchEvent method. For various cases in the switch case, we will analyze them step by step, according to the following.
Press (MotionEvent.ACTION_DOWN)
MotionEvent. ACTION_MOVE,
MotionEvent. ACTION_CANCEL was cancelled.
Logic of MotionEvent. ACTION_UP
MotionEvent.ACTION_DOWN
First look at the following:
case MotionEvent.ACTION_DOWN: if (mPendingCheckForTap == null) { mPendingCheckForTap = new CheckForTap(); } mPrivateFlags |= PREPRESSED; mHasPerformedLongPress = false; postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); break;
Line 2 mPengdingCheckForTap is a Runnable object, if null
Line 3 creates an object
Line 4 of the code is a private flag tag variable mPrivateFlags, mPrivateFlags |= PREPRESSED; the meaning of this sentence is marked as click-down.
Line 5 of the code is mHasPerformed LongPress, a Boolean variable with a value of false, indicating that there is no CEO press;
Line 6 of the code adds the task mPengingCheckForTap to the message queue and delays execution. As for how long the delay is, the source code is 115 milliseconds. Here is the click time. After exceeding the 115 milliseconds click time limit, the Runnable task is executed.
For the fourth line of code, there may be some doubts about what to do. Here I explain that there are many states in View, such as whether it can be clicked or double-clicked, whether it can be long-clicked or not, whether it can be focused; in fact, each of these states corresponds to two kinds: either it can or it can not be reflected in the code. One of the binary systems is 1 and 0, and one is true 0, which means No. To put it plainly, it is controlled by binary bit operation, because every bit of the binary system is 0 or 1. It may still be a little unclear. Here is an example to illustrate:
For example, mPrivateFlags |= PREPRESSED;
Assuming that mPrivateFlags is any value converted to binary, it is as follows: aaaaaa aaaaaa aaaaaa aaaaaa aaaaaa
From the source code, we can know that PREPRESSED is a constant value of 0x02000000:00000010 0000 0000000 0000000 0000 0000 0000.
mPrivateFlags |= PREPRESSED; the result is: aa aaaa aa1a aaaa aa aaaaaa aaaaaa aaaaaa
It can be seen that the position of mPrivateFlags remains unchanged except for the position of 1. Here it becomes 1, which can be interpreted as the meaning of clicking and pressing.
~PREPRESSED :1111 1101 1111 1111 1111 1111 1111 1111
mPrivateFlags &= ~PREPRESSED; :aaaa aa0a aaaa aaaa aaaa aaaa aaaa aaaa
You can see that all the positions in the mPrivateFlags result remain unchanged except for the position of 0. Here it becomes 10, which can be interpreted as canceling the click-down.
The value of mPrivateFlags is set to 1 after | PREPRESSED. The other positions are invariable. In fact, we don't need to care about it, because as long as this position is 1, we know that it is valid under the press condition.
The value of mPrivateFlags is set to zero when the value of mPrivateFlags is set to zero after -~PRESSED, and other positions are invariable. In fact, we don't need to care about it, because as long as this position is zero, we know that its pressing condition is not valid.
Next, let's look at the run method implemented in mPending CheckForTap:
private final class CheckForTap implements Runnable { public void run() { mPrivateFlags &= ~PREPRESSED; mPrivateFlags |= PRESSED; refreshDrawableState(); if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) { postCheckForLongClick(ViewConfiguration.getTapTimeout()); } } }
Line 3 cancels the click-down behavior tag (explained above), because the click event exceeds 115 milliseconds and can no longer be treated as a click-down behavior;
Line 4 is marked as a long-press behavior; PRESSED is also a final variable whose existence is meant to determine whether a long-press is a long-press, and the basic principle is based on whether a bit of the binary system is zero or one.
Line 5 Update Background
Line 6, (mViewFlags & LONG_CLICKABLE) === LONG_CLICKABLE to determine whether a long press is acceptable. This behavior is acceptable if we set a long press listener or a long press in the code. No.
This condition does not hold; the source code for postCheckForLongClick is as follows
The main thing here is actually mPending Check ForLongPress, and the value source of ViewConfiguration. getLongPress Timeout () is 500 milliseconds. If it exceeds 500 milliseconds, it will check if it is long pressed.private void postCheckForLongClick(int delayOffset) { mHasPerformedLongPress = false; if (mPendingCheckForLongPress == null) { mPendingCheckForLongPress = new CheckForLongPress(); } mPendingCheckForLongPress.rememberWindowAttachCount(); postDelayed(mPendingCheckForLongPress, ViewConfiguration.getLongPressTimeout() - delayOffset); }
Starting at line 06, the isPressed() source code is as follows:class CheckForLongPress implements Runnable { private int mOriginalWindowAttachCount; public void run() { if (isPressed() && (mParent != null) && mOriginalWindowAttachCount == mWindowAttachCount) { if (performLongClick()) { mHasPerformedLongPress = true; } } } public void rememberWindowAttachCount() { mOriginalWindowAttachCount = mWindowAttachCount; } }
Have you seen it? This condition must be true at this time.public boolean isPressed() { return (mPrivateFlags & PRESSED) == PRESSED; }
mParent!=null must also be valid. When any View is added to Activity through the setContentView method, there must be a parent View.
The last condition is mOriginal Windows AttachCount == mWindows AttachCount. Look at the rememberWindows AttachCount method of the previous source snippet and you know that it must also be true.
If it all works, then execute the performLongClick method
Look at the onLongClick method in the long click event implemented by ourselves in line 6. If it returns true, the last method returns true, and if it returns false, the last method returns false.public boolean performLongClick() { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED); boolean handled = false; if (mOnLongClickListener != null) { handled = mOnLongClickListener.onLongClick(View.this); } if (!handled) { handled = showContextMenu(); } if (handled) { performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); } return handled; }
For returning true, it's easy to understand by looking at the source code above. If it returns false, here's why this method returns false in the end.
It's mainly this line of code.handled = showContextMenu();
getParent() must be a ViewGruop, right? So go to ViewGruop and see this methodpublic boolean showContextMenu() { return getParent().showContextMenuForChild(this); }
All subclasses of the source ViewGroup have no Overdide method, so mParent!=null will end up false, because the top-level DecoerView has no parent View; isn't it?public boolean showContextMenuForChild(View originalView) { return mParent != null && mParent.showContextMenuForChild(originalView); }
What impact does Chang'an's method of monitoring return to treu or false have on the future? Keep it here and look at the source code later.
MotionEvent.ACTION_MOVE
Source code when moving
Firstly, line 6 mTouchSlop is the smallest sliding distance that the system can recognize. Where did it come from when it was initialized?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;
public View(Context context) { mContext = context; mResources = context != null ? context.getResources() : null; mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED; // Used for debug only //++sInstanceCount; mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); }
After we assume that the user is sliding the pixel, the distance that a touch needs to move is the smallest distance that is considered to be sliding./** * @return Distance a touch can wander before we think the user is scrolling in pixels */ public int getScaledTouchSlop() { return mTouchSlop; }
if the condition means that the touch exceeds the minimum sliding distance recognized by the View Width and Height + system, that is, it is considered to have slid outside the View, then the cancel click inside will be executed and the long press action will be cancelled:
/** * Remove the tap detection timer. */ private void removeTapCallback() { if (mPendingCheckForTap != null) { mPrivateFlags &= ~PREPRESSED; removeCallbacks(mPendingCheckForTap); } }
Above is the cancellation of click behavior and tasks, after the code analysis, it is not difficult to understand.
private void removeLongPressCallback() { if (mPendingCheckForLongPress != null) { removeCallbacks(mPendingCheckForLongPress); } }
Cancel the Long Press Task, which is followed by the Cancel of the Long Press Task
MotionEvent.ACTION_CANCEL
Canceled source code
case MotionEvent.ACTION_CANCEL: mPrivateFlags &= ~PRESSED; refreshDrawableState(); removeTapCallback(); break;
First, we need to know what will receive the event MotionEvent.ACTION_CANCEL. When the previous action was intercepted by the parent container, the child View will receive such an event, such as the user has been Moving, when a mobile event is intercepted by the parent View, the child View will receive the cancelled event.
Analysis code
The second line of code is to cancel the long press tag.
Line 3 is to refresh the background
Line 4 is to cancel the click task and tag
It's very simple, isn't it?
MotionEvent.ACTION_UP
Finally, let's look at a lifting incident.
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;
Firstly, if the event is not cancelled or moved to an area outside the View when the user slides, the condition of line 3 will certainly hold.
Lines 7 to 9 of code if condition means whether the focus is accessible, whether the focus is accessible by touch, and whether the focus is accessible by touch.
These three conditions are not valid for what the View is supposed to be. This can be seen in the subclass of View by the attr obtained when the View is initialized.
For EditText, these three conditions will hold. When the finger clicks EditText to leave, the third condition will hold when the focus is achieved.
If true, the return value through requstFouse will be true, otherwise the focus Taken will always be false.
The value of mHasPerformed LongPress in 11 lines of code is true only if it returns true by pressing the onLongClick method in the listener for a long time; otherwise, the value is false.
If true, the condition is not executed, and the user's onLongClick method executes and returns false, then the following click listening events will be executed later.~~~
16 rows will enter this if condition unless it is EditText.
20-24 lines of code to execute click-listening events
Look at line 23. If this task is not executed in the message queue, it will return the value false. If it is executed locally again, then 24 lines of code will not execute.private final class PerformClick implements Runnable { public void run() { performClick(); } }
After 29 lines, the main content of the code is to refresh the status and background, which are analyzed and explained in the front and easy to understand.
Well, Veiw's event code is analyzed once, hoping to be helpful to everyone who sees the source code.