The article directories
- preface
-
- One touch event
- Event handling involves important methods
- Why is event distribution needed
- The Demonstrate code for this article
- Source code analysis
-
- Knowledge of bedding
- ACTION_DOWN processing in ViewGroup
-
- ACTION_DOWN
- The child view consumes ACTION_DOWN
- Child views do not consume ACTION_DOWN
- ACTION_MOVE processing of ViewGroup
-
- Suppose the child view consumes the previous ACTION_DOWN event
-
- Second ACTION_MOVE processing after viewGroup interception
- Assume that the subview did not consume the previous ACTION_DOWN event
- ACTION_UP processing under ViewGroup
-
- The child view consumes ACTION_DOWN
- The child view didn’t consume ACTION_DOWN
- The view of ACTION_DOWN
- The view of ACTION_MOVE
- The view of ACTION_UP
- conclusion
preface
This article is based on a further understanding of the Art of Android Development, partly excerpted from the book. Why write this article? The <
> content is very well summarized but many details are not posted for further reading.
One touch event
One touch event: refers to from aACTION_DOWN
, and multipleACTION_MOVE
, and individualACTION_UP/ACTION_CANCEL
Composition.
As for theACTION_UP
andACTION_CANCEL
The difference will be explained later in the source code analysis.
Event handling involves important methods
Activity: ————–dispatchTouchEvent ————–onTouchEvent
ViewGroup
————–dispatchTouchEvent
————–onTouchEvent
————–onInterceptTouchEvent
View
————–dispatchTouchEvent
————–onTouchEvent
In addition, only viewgroups can distribute events, and views can choose to consume or not consume events.
Why is event distribution needed
There is only one touch event, but it is possible that multiple views superimposed on the View need to be processed at the same time. In this case, according to the relevant business logic, which View should be processed.
The figure above has two superimposed listViews. Suppose we scroll down the child ListView. Should we slide the parent view or the child View?
The Demonstrate code for this article
//MainActivity.java
class MainActivity : Activity(a){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
log("dispatchTouchEvent ${MotionEvent.actionToString(ev.action)}");
return super.dispatchTouchEvent(ev)
}
override fun onTouchEvent(ev: MotionEvent): Boolean {
log("onTouchEvent ${MotionEvent.actionToString(ev.action)}");
return super.onTouchEvent(ev)
}
fun log(msg: String) {
Log.e("MainActivity"."${msg} ")}}Copy the code
//MyViewGroup.java
class MyViewGroup @JvmOverloads constructor(
context: Context.attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
log("dispatchTouchEvent ${MotionEvent.actionToString(ev.action)}")
return super.dispatchTouchEvent(ev)
}
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
log("onInterceptTouchEvent ${MotionEvent.actionToString(ev.action)}")
return super.onInterceptTouchEvent(ev)
}
override fun onTouchEvent(ev: MotionEvent): Boolean {
log("onTouchEvent ${MotionEvent.actionToString(ev.action)}")
return super.onTouchEvent(ev)
}
fun log(msg: String) {
Log.e("MyViewGroup"."${msg} ")}}Copy the code
//MyView.java
class MyView : View {
constructor(context: Context?) : super(context) {} constructor(context: Context? , attrs: AttributeSet?) :super(context, attrs) {} constructor(context: Context? , attrs: AttributeSet? , defStyleAttr: Int) :super(
context,
attrs,
defStyleAttr
) {
}
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
log("dispatchTouchEvent ${MotionEvent.actionToString(ev.action)}")
return super.dispatchTouchEvent(ev)
}
override fun onTouchEvent(ev: MotionEvent): Boolean {
log("onTouchEvent ${MotionEvent.actionToString(ev.action)}")
return super.onTouchEvent(ev)
}
fun log(msg: String) {
Log.e("MyView"."${msg} ")}}Copy the code
<! --activity_main.xml-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.example.example.MyViewGroup
android:layout_width="300dp"
android:layout_height="300dp"
android:background="#f0f"
android:text="Trigger network request"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.example.example.MyView
android:layout_gravity="center"
android:layout_width="150dp"
android:layout_height="150dp"
android:background="#f00" />
</com.example.example.MyViewGroup>
</androidx.constraintlayout.widget.ConstraintLayout>
Copy the code
Preview:
Source code analysis
Knowledge of bedding
This summary is intended only as a prelude to source code analysis.
All the click event will be from the Activity. The dispatchTouchEvent trigger. Click on the MyView output of the Demonstrate as shown above:
MainActivity: dispatchTouchEvent ACTION_DOWN --------- Starting point of ACTION_DOWN event distribution MyViewGroup: dispatchTouchEvent ACTION_DOWN MyViewGroup: onInterceptTouchEvent ACTION_DOWN MyView: dispatchTouchEvent ACTION_DOWN MyView: onTouchEvent ACTION_DOWN MyViewGroup: onTouchEvent ACTION_DOWN MainActivity: OnTouchEvent ACTION_DOWN MainActivity: dispatchTouchEvent ACTION_UP --------- Starting point of ACTION_UP event distribution MainActivity: onTouchEvent ACTION_UPCopy the code
We continue to read the source code:
class Activity{
public boolean dispatchTouchEvent(MotionEvent ev) {
ACTION_DOWN calls onUserInteraction
OnUserInteraction is null by default. You can implement such operations as burying points in this function
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
/ / return to PhoneWindow getWindow
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
// If PhoneWindow does not consume, call the Activity's onTouchEvent function
// This conclusion can be put down
return onTouchEvent(ev);
}
// An empty implementation
public void onUserInteraction(a) {}}Copy the code
The above we can get several conclusions: conclusion 1: events must be from the Activity. The dispatchTouchEvent began handing out. (here is not out of this conclusion, but for the sake of completeness.)
Conclusion 2: ACTION_DOWN event must call Activity. OnUserInteraction.
Conclusion 3: PhoneWindow will call back to activity. onTouchEvent(action_down. ACTION_MOVE,ACTION_UP) without consuming touch events.
Call PhoneWindow’s superDispatchTouchEvent function and determine whether to call activity.onTouchEvent or return true based on the return value
//PhoneWindow.java
class PhoneWindow{
private DecorView mDecor;
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
// The source code is traced to the DecorView
returnmDecor.superDispatchTouchEvent(event); }}Copy the code
//DecorView.java
class DecorView extends FrameLayout{
public boolean superDispatchTouchEvent(MotionEvent event) {
// FrameLayout inherits the ViewGroup dispatchTouchEvent function (FrameLayout inherits the ViewGroup and does not override this function)
return super.dispatchTouchEvent(event); }}Copy the code
And so the event schedule becomesActivity
Calls to theViewGroup.dispatchTouchEvent
The process of a function is not analyzed laterActivity
Pass the event toDecorView
“, but simple understandingActivity
Pass the event to us aboveMyViewGroup
(because ofDecorView
Is also aViewGroup
, our custom classMyViewGroup
The same type, which simplifies our analysis process).
simplified
ACTION_DOWN processing in ViewGroup
This summary only analyzes the ACTION_DOWN processing of ViewGroup, which is also difficult
ACTION_DOWN
At this point we assume that we click on the MyView of the previous example and an ACTION_DOWN event is generated.
class Activity{
public boolean dispatchTouchEvent(MotionEvent ev) {
// Generate ACTION_DOWN to call onUserInteraction
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
// Here we assume that MyViewGroup's dispatchTouchEvent function is called directly
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
// If MyViewGroup does not consume ACTION_DOWN, call the Activity's onTouchEvent function
return onTouchEvent(ev);
}
// An empty implementation
public void onUserInteraction(a) {}}Copy the code
MyViewGroup inherits ViewGroup, so let’s just look at dispatchTouchEvent for ViewGroup
//ViewGroup.java
class ViewGroup{
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
/ / some security checks, here we consider onFilterTouchEventForSecurity returns true
if (onFilterTouchEventForSecurity(ev)) {
// Currently returns ACTION_DOWN
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
ACTION_DOWN resets the flag bit and cancels the previous sequence of events
if (actionMasked == MotionEvent.ACTION_DOWN) {
// The ACTION_DOWN event cancels the previous event and sends an ACTION_CANCEL event to the previous view that was processing the event
cancelAndClearTouchTargets(ev);
// Reset some flag bits
resetTouchState();
}
/ /... slightly
}
returnhandled; }}Copy the code
We don’t need too care about cancelAndClearTouchTargets function, but we need to look at the resetTouchState
//ViewGroup.java
class ViewGroup{
protected int mGroupFlags;
private TouchTarget mFirstTouchTarget;
private void resetTouchState(a) {
// Set mFirstTouchTarget to null
clearTouchTargets();
// FLAG_DISALLOW_INTERCEPT is cancelled
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
private void clearTouchTargets(a) {
TouchTarget target = mFirstTouchTarget;
if(target ! =null) {
/ /.. slightly
mFirstTouchTarget = null; }}}Copy the code
The flag bit above and the mFirstTouchTarget are used and we’ll talk about that later, and we’ll move on to code analysis
class ViewGroup{
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
/ / some security checks, here we consider onFilterTouchEventForSecurity returns true
if (onFilterTouchEventForSecurity(ev)) {
// Currently returns ACTION_DOWN
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Reset mFirstTouchTarget to null
// Unflag mGroupFlags FLAG_DISALLOW_INTERCEPT
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
//
final boolean intercepted;
ActionMasked == motionEvent. ACTION_DOWN is true
//mFirstTouchTarget ! = null to false
if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget ! =null) {
// The resetTouchState function disables FLAG_DISALLOW_INTERCEPT so it must be false
final booleandisallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! =0;
OnInterceptTouchEvent = onInterceptTouchEvent = onInterceptTouchEvent
if(! disallowIntercept) {//onInterceptTouchEvent may change the ACTION of ev, but there is no need to pay too much attention to this alternative case
intercepted = onInterceptTouchEvent(ev);
// Prevent ev from being modified
ev.setAction(action);
} else {
intercepted = false; }}else {
intercepted = true;
}
/ / a little
}
returnhandled; }}Copy the code
ACTION_DOWN is not affected by FLAG_DISALLOW_INTERCEPT and will be called onInterceptTouchEvent. FLAG_DISALLOW_INTERCEPT may affect whether onInterceptTouchEvent is called if the mFirstTouchTarget is not null, whereas the mFirstTouchTarget is not null only in ACTION_MOVE/ACTION _UP.
MFirstTouchTarget is a view object that handles a sequence of touch events. Another question is how do YOU change the FLAG_DISALLOW_INTERCEPT bit? We can look at the following source code
//ViewParent.java
public interface ViewParent {
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept);
}
//ViewGroup.java
class ViewGroup implement ViewParent{
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
// Return if it has been set
if(disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! =0)) {
return;
}
// Set the flag bit
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// Pass through to the parent view of the ViewGroup
// This will be passed up one layer after another
if(mParent ! =null) { mParent.requestDisallowInterceptTouchEvent(disallowIntercept); }}}Copy the code
Note here that flag bits are passed up one layer at a time.
Moving on to the dispatchTouchEvent function,onInterceptTouchEvent is null by default and returns false.
//ViewGroup.java
class ViewGroup{
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
/ / some security checks, here we consider onFilterTouchEventForSecurity returns true
if (onFilterTouchEventForSecurity(ev)) {
// Currently returns ACTION_DOWN
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Reset mFirstTouchTarget to null
// Unflag mGroupFlags FLAG_DISALLOW_INTERCEPT
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
//
final boolean intercepted;
ActionMasked == motionEvent. ACTION_DOWN is true
//mFirstTouchTarget ! = null to false
if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget ! =null) {
// The resetTouchState function disables FLAG_DISALLOW_INTERCEPT so it must be false
final booleandisallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! =0;
if(! disallowIntercept) {// An empty implementation that returns false by default
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action);
} else {
intercepted = false; }}else {
//ACTION_DOWN will not be executed here
intercepted = true;
}
ActionMasked == MotionEvent.ACTION_CANCEL is false
//resetCancelNextUpFlag returns false, we don't need to care,
Canceled overall is false
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// Update list of touch targets for pointer down, if needed.
//true
final booleansplit = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) ! =0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
//intercepted returns false! Intercepted to true
Canceled is false
if(! canceled && ! intercepted) {ActionMasked is true because it is ACTION_DOWN
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
// The object ACTION_DOWN event is 0 in this case, and getActionIndex returns the index of the finger touched
final int actionIndex = ev.getActionIndex();
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
// The number of child views
final int childrenCount = mChildrenCount;
// If the number of sub-views is not zero, select a view to determine whether event distribution can be performed
if (newTouchTarget == null&& childrenCount ! =0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// Sort the view by the order in which it was drawn and the z size of the child view. The z priority of the child view is greater than the order in which it was drawn
// The larger the z value of the subview is, the higher it is after the preorderedList. If there is no z value, the subview is sorted according to the order in which it was drawn in the viewGroup
// The higher the priority of drawing after the ViewGroup, the higher the priority of drawing after the preorderedList
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
}
/ / a little}}/ / a little
}
returnhandled; }}Copy the code
We see buildTouchDispatchChildList correlation function implementation
//ViewGroup.java
class ViewGroup{
public ArrayList<View> buildTouchDispatchChildList(a) {
return buildOrderedChildList();
}
ArrayList<View> buildOrderedChildList(a) {
final int childrenCount = mChildrenCount;
// The children array of the ViewGroup is added to the array according to the order in which the children array is drawn
// There is only one child view, or all child views have no z-axis attributes, just return empty objects
// The children array will be used directly.
//childrenCount <= 1
/ /! HasChildWithZ () can be understood as: if all child views have no z-axis, it can be retrieved in reverse order of the children array priority
if (childrenCount <= 1| |! hasChildWithZ())return null;
if (mPreSortedChildren == null) {
mPreSortedChildren = new ArrayList<>(childrenCount);
} else {
mPreSortedChildren.clear();
mPreSortedChildren.ensureCapacity(childrenCount);
}
// Get a priority view set object based on the drawing order and the Z-axis. The algorithm has to be stable
final boolean customOrder = isChildrenDrawingOrderEnabled();
for (int i = 0; i < childrenCount; i++) {
// add next child (in child order) to end of list
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View nextChild = mChildren[childIndex];
final float currentZ = nextChild.getZ();
int insertIndex = i;
while (insertIndex > 0 && mPreSortedChildren.get(insertIndex - 1).getZ() > currentZ) {
insertIndex--;
}
mPreSortedChildren.add(insertIndex, nextChild);
}
returnmPreSortedChildren; }}Copy the code
One important thing we can learn from this is that the distribution order priority of the ViewGroup also needs to consider the z-axis of the child view.
Let’s dig deeper into the source code
//ViewGroup.java
class ViewGroup{
public boolean dispatchTouchEvent(MotionEvent ev) {
/ /... slightly
//intercepted returns false! Intercepted to true
Canceled is false
if(! canceled && ! intercepted) {ActionMasked is true because it is ACTION_DOWN
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
// The object ACTION_DOWN event is 0 in this case, and getActionIndex returns the index of the finger touched
final int actionIndex = ev.getActionIndex();
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
// The number of child views
final int childrenCount = mChildrenCount;
// If the number of sub-views is not zero, select a view to determine whether event distribution can be performed
if (newTouchTarget == null&& childrenCount ! =0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// Sort the view by the order in which it was drawn and the z size of the child view. The z priority of the child view is greater than the order in which it was drawn
// The larger the z value of the subview is, the higher it is after the preorderedList. If there is no z value, the subview is sorted according to the order in which it was drawn in the viewGroup
// The higher the priority of drawing after the ViewGroup, the higher the priority of drawing after the preorderedList
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
// Get the view in reverse order and determine which view can be processed
for (int i = childrenCount - 1; i >= 0; i--) {
// get the subscript
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
// Get the child view
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
/ / canReceivePointerEvents () returns true if the current view visible or executing animation
/ / isTransformedTouchPointInView judge whether the coordinates of the touch currently fall within the scope of the view
// The whole if determines whether the current view can handle the event. If the current view cannot handle the event, skip this loop and find the next view
if(! child.canReceivePointerEvents() || ! isTransformedTouchPointInView(x, y, child,null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
ACTION_DWON event getTouchTarget must return NULL. Analysis later
newTouchTarget = getTouchTarget(child);
// Because newTouchTarget==null so if we skip
if(newTouchTarget ! =null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
// Very important function, let's look at the concrete implementation of this function
// This function roughly returns a call to the View's dispatchTouchEvent
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
/ / a little
break; }}if(preorderedList ! =null) preorderedList.clear();
}
if (newTouchTarget == null&& mFirstTouchTarget ! =null) {
/ /... slightly}}}/ /... slightly}}Copy the code
We first see dispatchTransformedTouchEvent
//ViewGroup.java
class ViewGroup{
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// Canceling motions is a special case. We don't need to perform any transformations
// or filtering. The important part is the action, not the contents.
final int oldAction = event.getAction();
// Cancel is false and oldAction is ACTION_DWON, so skip if
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
// There are more than one touch points returned.
final int oldPointerIdBits = event.getPointerIdBits();
// Check the number of old and new touch points, if not equal must be 0
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
// If the status is inconsistent, the abnormal status is returned directly
if (newPointerIdBits == 0) {
return false;
}
// If the number of pointers is the same and we don't need to perform any fancy
// irreversible transformations, then we can reuse the motion event for this
// dispatch as long as we are careful to revert any changes we make.
// Otherwise we need to make a copy.
final MotionEvent transformedEvent;
NewPointerIdBits == oldPointerIdBitstrue
if (newPointerIdBits == oldPointerIdBits) {
// Do some offset calculations, the code here does not affect the analysis flow, so skip
//hasIdentityMatrix
// We call it fals
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}
// Child is not empty
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
// This calculation does not affect us because we have no scroll and offset
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
/ / distribution to MyView. DispatchTouchEvent
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
returnhandled; }}Copy the code
This is going to determine the future of the ViewGroup code based on whether the subview consumes it, and we’re going to do it in two different ways.
The child view consumes ACTION_DOWN
Assume that all MyViewGroup dispatchTransformedTouchEvent returns true.
class ViewGroup{
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
TouchTarget newTouchTarget = null;
// Get the view in reverse order and determine which view can be processed
for (int i = childrenCount - 1; i >= 0; i--) {
// get the subscript
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
// Get the child view
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
/ / canReceivePointerEvents () returns true if the current view visible or executing animation
/ / isTransformedTouchPointInView judge whether the coordinates of the touch currently fall within the scope of the view
// The whole if determines whether the current view can handle the event. If the current view cannot handle the event, skip this loop and find the next view
if(! child.canReceivePointerEvents() || ! isTransformedTouchPointInView(x, y, child,null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
ACTION_DWON event getTouchTarget must return NULL. Analysis later
newTouchTarget = getTouchTarget(child);
// Because newTouchTarget==null so if we skip
if(newTouchTarget ! =null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
// Assume that the function returns true
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// It's important to assign the value to newTouchTarget, which contains a reference to the child View
// The inner mFirstTouchTarget will be assigned to view
newTouchTarget = addTouchTarget(child, idBitsToAssign);
// Write this down
alreadyDispatchedToNewTouchTarget = true;
// End the loop
break; }}}/ / after the break... Because newTouchTarget is not null, the concrete method body is skipped
if (newTouchTarget == null&& mFirstTouchTarget ! =null) {
/ / a little
}
// mFirstTouchTarget is not null
if (mFirstTouchTarget == null) {
// There is no comparison here
} else {
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while(target ! =null) {
final TouchTarget next = target.next;
/ / alreadyDispatchedToNewTouchTarget to true
//target == newTouchTarget is true
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
/ / a little
}
// exit the loop after the assignmentpredecessor = target; target = next; }}//END dispatchTouchEvent
return handled;
}
// Note that mFirstTouchTarget is assigned
private TouchTarget addTouchTarget(View child, int pointerIdBits) {
TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
returntarget; }}Copy the code
So there’s a conclusion subview consumption event, and mFirstTouchTarget is going to be assigned to the corresponding subview.
Child views do not consume ACTION_DOWN
class ViewGroup{
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
/ / some security checks, here we consider onFilterTouchEventForSecurity returns true
if (onFilterTouchEventForSecurity(ev)) {
/ / a little
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
//intercepted returns false! Intercepted to true
Canceled is false
if(! canceled && ! intercepted) {ActionMasked is true because it is ACTION_DOWN
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
/ / a little
// The number of child views
final int childrenCount = mChildrenCount;
// If the number of sub-views is not zero, select a view to determine whether event distribution can be performed
if (newTouchTarget == null&& childrenCount ! =0) {
// Get the view in reverse order and determine which view can be processed
for (int i = childrenCount - 1; i >= 0; i--) {
There is no child view consumption event until the end of the loop}}// Since both mFirstTouchTarget and newTouchTarget are null, no analysis is required here
if (newTouchTarget == null&& mFirstTouchTarget ! =null) {
/ /...}}}// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
/ / because there is no child view consumption, dispatchTransformedTouchEvent internal dispatchTouchEvent function will call the superclass
// Notice that the third argument is passed null
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
/ / a little
}
// Update list of touch targets for pointer up or cancel, if needed.
/ / a little
}
returnhandled; }}Copy the code
We see dispatchTransformedTouchEvent function again.
//ViewGroup.java
class ViewGroup{
private boolean dispatchTransformedTouchEvent(MotionEvent event,
boolean cancel,
View child,
int desiredPointerIdBits) {
final boolean handled;
// Callback to view's dispatchTouchEvent function
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
}
returnhandled; }}Copy the code
ACTION_MOVE processing of ViewGroup
Suppose the child view consumes the previous ACTION_DOWN event
//ViewGroup.java
public class ViewGroup {
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
// Currently returns ACTION_MOVE
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
//
final boolean intercepted;
ActionMasked == motionEvent. ACTION_DOWN is false
//mFirstTouchTarget ! If null is true, mFirstTouchTarget will point to the child view after consuming the DWON event
if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget ! =null) {
//ACTION_MOVE does not reset the flag so the intercept event is FLAG_DISALLOW_INTERCEPT
final booleandisallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! =0;
if(! disallowIntercept) {// An empty implementation that returns false by default
intercepted = onInterceptTouchEvent(ev);
} else {
intercepted = false; }}else {
//ACTION_DOWN will not be executed here
intercepted = true;
}
ActionMasked == MotionEvent.ACTION_CANCEL is false
//resetCancelNextUpFlag returns false, we don't need to care,
Canceled overall is false
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
//true
final booleansplit = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) ! =0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
// mFirstTouchTarget is not null
if (mFirstTouchTarget == null) {
/ / a little
} else {
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
/ / target isn't empty
while(target ! =null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
// There are two ways to analyze the source code
// The ViewGroup onInterceptTouchEvent returns true,cancelChild is true
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
/ / if cancelChild to true. DispatchTransformedTouchEvent will be passed to the child view a cancel events
// If cancelChild is false, the current event is passed to target.child
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
// This is true for intercepting events
if (cancelChild) {
// Must be null
if (predecessor == null) {
// Reset mFirstTouchTarget object
mFirstTouchTarget = next;
} else {
/ / a little
}
target.recycle();
target = next;
continue; }}/ / a little}}Canceled true resets all states
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
/ / a little
}
returnhandled; }}Copy the code
/ / below for dispatchTransformedTouchEvent cancel to true source
//ViewGroup.java
class ViewGroup{
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
final int oldAction = event.getAction();
// Since cancel is true, the loop is entered
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
// Call ViewGroup's dispatchTouchEvent function to handle ACTION_CANCEL events
handled = super.dispatchTouchEvent(event);
} else {
// Pass an ACTION_CANCEL event to the child view
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
returnhandled; }}}Copy the code
/ / below for dispatchTransformedTouchEvent cancel to false of the source code
//ViewGroup.java
class ViewGroup{
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
if (child == null)
{
handled = super.dispatchTouchEvent(event);
} else {
// Events are distributed directly to the child view
handled = child.dispatchTouchEvent(event);
}
returnhandled; }}Copy the code
Conclusion 6: If the child view consumes a DOWN event and the parent viewGroup intercepts the event on MOVE, it passes a Cancel to the child view. Notice that the ViewGroup intercepts the MOVE event, but the MOVE event is not passed to the ViewGroup onTouchEvent, and subsequent events are passed to the ViewGroup. OnTouchEvent.
Second ACTION_MOVE processing after viewGroup interception
This section is based on the case where the child View consumes ACTION_DOWN and the first ACTION_MOVE is intercepted by the parent ViewGroup. Let’s look at how the parent ViewGroup handles subsequent action_moves in this case.
//ViewGroup.java
public class ViewGroup {
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
/ / some security checks, here we consider onFilterTouchEventForSecurity returns true
// Currently returns ACTION_MOVE
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Reset mFirstTouchTarget to null
// Unflag mGroupFlags FLAG_DISALLOW_INTERCEPT
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
//
final boolean intercepted;
ActionMasked == MotionEvent.ACTION_MOVE is false
//mFirstTouchTarget ! = null to false
// The overall judgment is false
// If onInterceptTouchEvent intercepts a move event, it will not be intercepted later
if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget ! =null) {
// Intercepting code does not walk
} else {
// Execute to this point
intercepted = true;
}
ActionMasked == MotionEvent.ACTION_CANCEL is false
//resetCancelNextUpFlag returns false, we don't need to care,
Canceled overall is false
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// Update list of touch targets for pointer down, if needed.
//true
final booleansplit = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) ! =0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// The view dispatchTouchEvent function of its parent class will be called due to the third null, and the view. DispatchTouchEvent will call onTouchEvent
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
/ /.. Since it will not be carried out here... slightly
}
returnhandled; }}Copy the code
Conclusion 7: After a ViewGroup intercepts the first move event, it does not call the interceptor again and calls its own descendant View’s dispatchTouchEvent. And view.dispatchTouchEvent is called back to ontouchEvent
Assume that the subview did not consume the previous ACTION_DOWN event
//ViewGroup.java
public class ViewGroup {
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
// Currently returns ACTION_MOVE
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
final boolean intercepted;
ActionMasked == motionEvent. ACTION_DOWN is false
/ / mFirstTouchTarget is empty
// So no interceptor is entered
if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget ! =null) {
/ / a little
} else {
// The direct flag is blocked
intercepted = true;
}
ActionMasked == MotionEvent.ACTION_CANCEL is false
//resetCancelNextUpFlag returns false, we don't need to care,
Canceled overall is false
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
//intercepted returns true! Intercepted to false
if(! canceled && ! intercepted) {/ / a little
}
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// // Because of the third null, the view dispatchTouchEvent function of its parent class is called, and the view.dispatchTouchEvent callback onTouchEvent
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
/ / a little
}
/ / a little
returnhandled; }}Copy the code
Conclusion 8: If all children of a ViewGroup do not consume ACTION_DWON events, then no distribution will be performed on the judgment children and the dispatchTouchEvent of its own descendant view will be called. And view.dispatchTouchEvent is called back to ontouchEvent
Note here: Our root view is a decorView that we can’t touch, so when our child view doesn’t consume DWON, the decorView doesn’t send any events, but the decorView can receive subsequent events.
Conclusion 9: With the presence of a decorView, if the DWON event does not have any view consumption, no event is issued below. But the decorView receives subsequent events.
ACTION_UP processing under ViewGroup
The child view consumes ACTION_DOWN
The context of this section is that the child view consumes ACTION_DOWN, and the parent ViewGroup does not intercept MOVE events
//ViewGroup.java
public class ViewGroup {
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
// Currently returns ACTION_UP
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
final boolean intercepted;
ActionMasked == motionEvent. ACTION_DOWN is false
//mFirstTouchTarget ! =null true
if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget ! =null) {
// Whether to call the intercept function according to FLAG_DISALLOW_INTERCEPT
final booleandisallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! =0;
if(! disallowIntercept) {// An empty implementation that returns false by default
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action);
} else {
intercepted = false; }}else {
// It will not be executed to this point
}
ActionMasked == MotionEvent.ACTION_CANCEL is false
//resetCancelNextUpFlag returns false, we don't need to care,
Canceled overall is false
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
//true
final booleansplit = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) ! =0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
// mFirstTouchTarget is not null
if (mFirstTouchTarget == null) {
/ / a little
} else {
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
/ / target isn't empty
while(target ! =null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
/ / resetCancelNextUpFlag returns false
/ / cancelChild intercepted decision
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
// If intercepted is true, a Cancel event is passed to the child view
// If intercepted is false then an up event is passed to the child view
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
// Break the loop
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue; } } predecessor = target; target = next; }}// resetTouchState is called because it is ACTION_UP
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
/ /... slightly
}
returnhandled; }}Copy the code
Conclusion 10: If the ViewGroup does not intercept the Up event, all variable identifiers will be reset after the event is passed to the child view. If the ViewGroup intercepts an Up event, the child view receives the Cancel event, and the event does not pass the ViewGroup’s onTouchEvent function, after which all variable identifiers are reset.
The child view didn’t consume ACTION_DOWN
The child view doesn’t consume ACTION_DOWN events, so it has to consume ACTION_DOWN events as a ViewGroup, otherwise the event won’t get delivered here. The specific consumption event judgment needs to be explained below in view
//ViewGroup.java
public class ViewGroup {
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
// Currently returns ACTION_UP
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
final boolean intercepted;
//mFirstTouchTarget ! = null to true
if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget ! =null) {
// Flag bit received affects whether to intercept
final booleandisallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! =0;
if(! disallowIntercept) {// An empty implementation that returns false by default
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action);
} else {
intercepted = false; }}else {
intercepted = true;
}
Canceled overall is false
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
//true
final booleansplit = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) ! =0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
// Because we assume the consumption DWON event here
if (mFirstTouchTarget == null) {
// The view dispatchTouchEvent function of its parent class will be called due to the third null, and the view. DispatchTouchEvent will call onTouchEvent
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
/ / a little
}
// Reset some state if it is an Up event
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
/ / a little
}
returnhandled; }}Copy the code
Here we analyze all the distribution paths and points for the three events in the ViewGroup. Keep in mind that ACTION_DOWN must call the onInterceptHoverEvent of the ViewGroup (regardless of the flag bit).
The view of ACTION_DOWN
We analyzed that the above View of dispatchTouchEvent ViewGroup will pass dispatchTransformedTouchEvent function call.
//view.java
class View{
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = false;
// Currently ACTION_DOWN
final int actionMasked = event.getActionMasked();
// If the event is DOWN, stop scrolling and notify the parent view's onStopNestedScroll function
if (actionMasked == MotionEvent.ACTION_DOWN) {
stopNestedScroll();
}
/ / onFilterTouchEventForSecurity as true
if (onFilterTouchEventForSecurity(event)) {
//ListenerInfo contains many listener callbacks
ListenerInfo li = mListenerInfo;
if(li ! =null //-------- is not empty ------&& li.mOnTouchListener ! =null // Whether the user sets the onTouch function callback --
&& (mViewFlags & ENABLED_MASK) == ENABLED ----------- If the current view is enable
&& li.mOnTouchListener.onTouch(this, event) //-------- calls the user-set onTouch function, and the return value of the function determines result
) {
result = true;
}
// The view. OnTouchEvent is called only if the user's custom onTouch returns false
if(! result && onTouchEvent(event)) { result =true; }} If the event is UP, stop scrolling and notify the parent view's onStopNestedScroll functionif(actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_CANCEL || (actionMasked == MotionEvent.ACTION_DOWN && ! result)) { stopNestedScroll(); }return result;
}
//
private ViewParent mNestedScrollingParent;
public void stopNestedScroll(a) {
if(mNestedScrollingParent ! =null) {
mNestedScrollingParent.onStopNestedScroll(this);
mNestedScrollingParent = null; }}}Copy the code
Conclusion 11: If the upper developer calls the view.setOnTouchListener function and returns true, view.onTouchEvent will not be called. Click events are handled in view.onTouchEvent, so click events cannot be called back.
We can see that the view. dispatchTouchEvent function is relatively simple, we only need to care about the view. onTouchEvent function, which returns the value to determine whether the event is consumed.
//View.java
public class View {
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
// As long as CLICKABLE or LONG_CLICKABLE is true, it will consume, even if view is DISABLED
// If CLICKABLE or LONG_CLICKABLE is true, it will be consumed.
// So it is easy to understand that CLICKABLE or LONG_CLICKABLE is true and will consume events regardless of whether the view is available
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
// Call the proxy class. If the proxy returns true, end the event
// Don't worry too much here
if(mTouchDelegate ! =null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true; }}// Take a close look at the last line of the if line return true.
// If CLICKABLE or LONG_CLICKABLE is true, it will be consumed
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
/ / a little
case MotionEvent.ACTION_DOWN:
/ /.. slightly
break;
case MotionEvent.ACTION_CANCEL:
/ /.. slightly
break;
case MotionEvent.ACTION_MOVE:
/ /.. slightly
break;
}
return true;
}
return false; }}Copy the code
Conclusion 12: The view will be consumed if its CLICKABLE or LONG_CLICKABLE is true.
The view of ACTION_MOVE
Seems to be relatively simple, there is not much explanation
The view of ACTION_UP
//View.java
public class View {
public boolean onTouchEvent(MotionEvent event) {
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
// There is a post operation that needs special attention. The PerformClick class has an interface function that also calls the view's PerformClick directly
// Delay the release of the click callback to give the view time to update its state before the click event is triggered
if(! post(mPerformClick)) { performClick(); }break;
}
return true;
}
return false; }}Copy the code
Let’s look at PerformClick and the PerformClick function
//View.java
private final class PerformClick implements Runnable {
@Override
public void run(a) { performClick(); }}// Here is a trick subview override performClick can be treated as a click to receive the click event
public boolean performClick(a) {
final boolean result;
final ListenerInfo li = mListenerInfo;
if(li ! =null&& li.mOnClickListener ! =null) {
// Callback click function
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
return result;
}
Copy the code
conclusion
Conclusion 1: events must be from the Activity. The dispatchTouchEvent began handing out. (here is not out of this conclusion, but for the sake of completeness.)
Conclusion 2: ACTION_DOWN event must call Activity. OnUserInteraction.
Conclusion 3: PhoneWindow will call back to activity. onTouchEvent(action_down. ACTION_MOVE,ACTION_UP) without consuming touch events.
Conclusion 4: The onInterceptTouchEvent is called when ACTION_DOWN is not affected by FLAG_DISALLOW_INTERCEPT
Conclusion 5: FLAG_DISALLOW_INTERCEPT may affect whether onInterceptTouchEvent is called if the mFirstTouchTarget is not null, whereas the mFirstTouchTarget is not null only in ACTION_MOVE/ACTION _UP.
Conclusion 6: If the child view consumes a DOWN event and the parent viewGroup intercepts the event on MOVE, it passes a Cancel to the child view. Notice that the ViewGroup intercepts the MOVE event, but the MOVE event is not passed to the ViewGroup onTouchEvent, and subsequent events are passed to the ViewGroup. OnTouchEvent.
Conclusion 7: After a ViewGroup intercepts the first move event, it does not call the interceptor again and calls its own descendant View’s dispatchTouchEvent. And view.dispatchTouchEvent is called back to ontouchEvent
Conclusion 8: If all children of a ViewGroup do not consume ACTION_DWON events, then no distribution will be performed on the judgment children and the dispatchTouchEvent of its own descendant view will be called. And view.dispatchTouchEvent is called back to ontouchEvent
Note here: Our root view is a decorView that we can’t touch, so when our child view doesn’t consume DWON, the decorView doesn’t send any events, but the decorView can receive subsequent events.
Conclusion 9: With the presence of a decorView, if the DWON event does not have any view consumption, no event is issued below. But the decorView receives subsequent events.
Conclusion 10: If the ViewGroup does not intercept the Up event, all variable identifiers will be reset after the event is passed to the child view. If the ViewGroup intercepts an Up event, the child view receives the Cancel event, and the event does not pass the ViewGroup’s onTouchEvent function, after which all variable identifiers are reset.
Conclusion 11: If the upper developer calls the view.setOnTouchListener function and returns true, view.onTouchEvent will not be called. Click events are handled in view.onTouchEvent, so click events cannot be called back.
Conclusion 12: View CLICKABLE or LONG_CLICKABLE will be consumed if it is true.
Viewgroups can only distribute events, whereas views can only choose to consume