Recently, I have been busy looking for a job. It took three months, but the results were not perfect. Want to go to a big factory, want to go to a good company, want to meet more powerful people still not come true. Maybe they are not strong enough, maybe they are not hard enough, maybe they need some luck. Life takes a few twists and turns. No one has a smooth ride all the time. The article will continue to be updated in the coming period. I hope you’ll stay tuned. Thanks~
preface
For Lollipop(Android 5.0), Google introduced NestedScrolling, which is NestedScrolling. This article will take you through Google’s design of this mechanism. By reading this article, you can know the following:
- Implementation and limitations of nested sliding in traditional event distribution mechanisms.
- The principle of Google NestedScrolling mechanism.
- Call relationship between NestedScrollingChild and NestedScrollingParent interfaces.
- NestedScrollingChild2 and NestedScrollingParent2 interfaces appear.
The examples covered in this blog are implemented in the NestedScrollingDemo project, and you can help yourself.
Traditional event mechanisms deal with the limitations of nested sliding
In the traditional event distribution mechanism, when an event is generated, its transmission process follows the following order: parent control -> child control, the event is always transmitted to the parent control, when the parent control does not intercept the event, then the current event will be transmitted to its child control. Once the parent control needs to intercept an event, the child control has no chance of receiving the event.
So in a traditional event distribution mechanism, if there are nested sliding scenarios, we need to manually resolve event conflicts. The specific nesting slide example is shown in the figure below:
The effect of implementation, please see NestedTraditionLayout. Java
To achieve the above effect, in a traditional sliding mechanism, we need to do the following steps:
- We need to call the onInterceptTouchEvent method in the parent control to intercept the upward swipe.
- When the parent control intercepts the event, it needs to control its own onTouchEvent to handle the sliding event so that it slides to the HeaderView to hide.
- When the HeaderView slides to hide, the parent controls do not intercept events, but internal child controls (RecyclerView or ListView) to handle the sliding events.
One problem with dealing with nested slides using traditional event blocking mechanisms is that the entire nested slide is incoherent. That is, when the parent control slides to the hidden HeaderView, this time if you want to internal (RecyclerView or ListView) to handle the sliding event. Just lift your finger and slide it up again.
Those familiar with event distribution should know that the reason for the incoherence is that the parent control blocks events, so events in the same sequence of events are still passed to the parent control and its onTouchEvent method is called. Instead of calling the onTouchEvent method of the child control.
Overview of NestedScrolling
In order to achieve consistent NestedScrolling, Google introduced NestedScrolling for Lollipop(Android 5.0). This mechanism does not break away from the traditional event distribution mechanism, but adds gesture sliding and fling methods to the system’s own viewgroups and Views. In order to be compatible with lower versions (below 5.0, there is no corresponding API for View and ViewGroup), Google also provides the following classes and interfaces in support V4 package:
The parent control needs to implement the interface and use the class:
- NestedScrollingParent (interface)
- NestedScrollingParent2 (also interface and inherits NestedScrollingParent)
- NestedScrollingParentHelper (class)
Interfaces and classes used by child controls:
- NestedScrollingChild (interface)
- NestedScrollingChild2 (also interface and inherits NestedScrollingChild)
- NestedScrollingChildHelper (class)
Note that if you are running Android 5.0 or higher, you can use the ViewGoup and View methods directly. However, for backward compatibility, it is recommended to use the corresponding interface provided by the Support V4 package for nested sliding. The usage and method of these interfaces will be explained in detail below.
This section describes interfaces between NestedScrollingParent and NestedScrollingChild
Before we understand how nested sliding is used, we need to understand the description of the methods in the interfaces corresponding to the parent and child controls. The NestedScrollingParent2 and NestedScrollingChild2 interfaces can be ignored because they are designed to solve a problem with nested sliding processes. So for this stage we only need to understand the basic nested sliding rules. NestedScrollingParent2 and NestedScrollingChild2 are described below. Now let’s look at the basic interface method introduction.
NestedScrollingParent
To implement nested sliding as an interface, we need the parent control to implement the NestedScrollingParent interface. The specific interface methods are as follows:
/** * A nested slide is coming. Check if the parent controls accept nested slides@paramChild Child of the parent class of the nested slide@paramTarget Specifies the subclass of the nested slide *@paramNestedScrollAxes supports nestedScrollAxes. Horizontal, vertical, or do not specify *@returnWhether or not the parent control accepts nested slides, and only then will the remaining nested slides */ be executed
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {}
/** * this method will only be called when onStartNestedScroll returns true, i.e. the parent control accepts nested slides
public void onNestedScrollAccepted(View child, View target, int axes) {}
/** * Determine whether the parent control is processed first with the child control before the nested sliding child control slides. **@paramTarget Specifies the subclass of the nested slide *@paramDx horizontal nested sliding child controls want to change the distance dx<0 to the right dx>0 to the left *@paramThe distance by which the child control of dy vertical nested sliding wants to change dy<0 slide down dy>0 slide up *@paramThe consumed parameter tells us when we implement this function to go back and tell the child control how far the current parent consumes * consumed[0] horizontally and consumed[1] vertically so that the child control can adjust accordingly */
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {}
/** * Inline sliding child control after sliding, determine whether the parent control continues processing (i.e., after the parent consumes a certain distance, the child consumes again, finally determine whether the parent consumes) **@paramTarget Specifies the subclass of the nested slide *@paramDxConsumed Distance that a child control that slides horizontally nested slides *@paramDyConsumed how far the child control that slides is nested vertically *@paramDxUnconsumed distance (unconsumed distance) * for a child control that is nested sliding horizontally@paramDyUnconsumed the unrolled distance (unconsumed distance) of the sliding child control nested vertically */
public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {}
/** * nesting slides end */
public void onStopNestedScroll(View child) {}
/** * If the parent controls fling, the child controls cannot hold the fling. * *@paramTarget Specifies the subclass of the nested slide *@paramVelocityX horizontal speed velocityX > 0 slide left, vice versa *@paramVelocityY in the vertical direction velocityY > 0 sliding up, otherwise sliding down *@returnWhether the parent control blocks the fling */
public boolean onNestedPreFling(View target, float velocityX, float velocityY) {}
/** * If the parent control does not intercept the fling, the child control passes the fling to the parent control **@paramTarget Specifies the subclass of the nested slide *@paramVelocityX horizontal speed velocityX > 0 slide left, vice versa *@paramVelocityY in the vertical direction velocityY > 0 sliding up, otherwise sliding down *@paramWhether the Consumed control can consume the fling or whether it consumed the fling *@returnWhether the parent control consumes the fling */
public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {}
/** * returns the current parent control nesting slide direction, can be horizontal or vertical, or unchanged */
public int getNestedScrollAxes(a) {}
Copy the code
NestedScrollingChild Describes the interface
If nested sliding is implemented as an interface, the child controls need to implement the NestedScrollingChild interface. The specific interface methods are as follows:
/** * opens a nested slide **@paramNested sliding method supported by AXES, divided into horizontal, vertical, or not specified *@returnIf true, the current child control has found a nested sliding view */
public boolean startNestedScroll(int axes) {}
/** * Before the child control slides, the event is distributed to the parent control, which determines how much ** is consumed@paramDx horizontal nested sliding child controls want to change the distance dx<0 to the right dx>0 to the left *@paramThe distance by which the child control of dy vertical nested sliding wants to change dy<0 slide down dy>0 slide up *@paramThe Consumed child control passes to the parent control array, which is used to store the horizontal and vertical consumed distance of the parent control, consumed horizontal distance [0], consumed vertical distance [1] *@paramOffsetInWindow Specifies the offset * of the current window child control@returnIf true, the parent control has consumed */
public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed, @Nullable int[] offsetInWindow) {}
/** * When the parent control consumes the event, the child control processes the event, and then distributes the event to the parent control. The parent control determines whether to consume the remaining distance. * *@paramDxConsumed Distance that a child control that slides horizontally nested slides *@paramDyConsumed how far the child control that slides is nested vertically *@paramDxUnconsumed distance (unconsumed distance) * for a child control that is nested sliding horizontally@paramDyUnconsumed distance (unconsumed distance) * of the child control that is nested sliding vertically@paramOffsetInWindow Specifies the offset * of the current window child control@returnIf true is returned, the parent control continues to consume */
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow) {}
/** * child control stops nested sliding */
public void stopNestedScroll(a) {}
/** * If the parent controls fling, the child controls cannot hold the fling. * *@paramVelocityX horizontal speed velocityX > 0 slide left, vice versa *@paramVelocityY in the vertical direction velocityY > 0 sliding up, otherwise sliding down *@returnIf true, the parent control intercepts fling */
public boolean dispatchNestedPreFling(float velocityX, float velocityY) {}
/** * If the parent control does not hold its child fling, the child control calls the fling method and passes it to the parent control@paramVelocityX horizontal speed velocityX > 0 slide left, vice versa *@paramVelocityY in the vertical direction velocityY > 0 sliding up, otherwise sliding down *@paramWhether the Consumed control can consume the fling or whether it consumed the fling *@returnWhether the parent control consumes the fling */
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {}
/** * Sets whether the current child control supports nested sliding. If not, the parent control is unable to respond to nested sliding@paramEnabled True */ is supported
public void setNestedScrollingEnabled(boolean enabled) {}
/** * Whether the current child control supports nested sliding */
public boolean isNestedScrollingEnabled(a) {}
/** * Determines whether the current child control has nested sliding parent control */
public boolean hasNestedScrollingParent(a) {}
Copy the code
Google nested sliding method call design
From the above, I believe that we have a basic understanding of the functions of NestedScrollingParent and NestedScrollingChild interface methods, but we do not know the corresponding relationship between these methods and the call timing. So now let’s analyze Google’s implementation and design of the whole nesting sliding process. To handle nested sliding, Google breaks down the process into the following steps:
- 1. If the parent control does not block the event, the child control will first ask the parent control whether it supports nested sliding after receiving the sliding event.
- 2. If the parent control supports nested sliding, the parent control pre-slides. The remaining distance is then handed over to the child control for processing.
- 3. After the child control receives the remaining sliding distance of the parent control and slides, if the sliding distance is still left, it will ask the parent control whether it needs to continue to consume the remaining distance.
- 4. If a child control has a fling, the parent control is asked whether the child control has a fling
Prior to intercept
Fling. If the parent control preblocks. Is handed to the parent control processing.Child controls do not handle fling
. - 5. If the parent control does not intercept the fling beforehand, the fling is passed to the parent control. The child controls also process fling.
- 6. When the entire nesting slide ends, the child control notifies the parent control that the nesting slide ends.
For those unfamiliar with fling effects, RecyclerView Scroll and Fling can be used
Combine the methods in NestedScrollingParent and NestedScrollingChild. We can get the call relationships between the corresponding methods. The details are shown in the figure below:
Child control method call timing
Once we understand the interface’s calling relationships, we need to know when the child control calls the corresponding nested sliding method. Because under the low version, the child controls to the parent control relay events need to cooperate with NestedScrollingChildHelper class used with NestedScrollingChild interface. Due to space constraints. I won’t show you how to construct a child control that supports nested sliding. The rest of the knowledge points will be based on NestedScrollingChildView. I hope you can combine the code with the blog to understand.
In the following chapters, we will first explain Google’s API design of nested sliding under NestedScrollingParent and NestedScrollingChild interfaces. The NestedScrollingParent2 and NestedScrollingChild2 interfaces are explained separately.
The time when the startNestedScroll method is called
According to the nesting sliding mechanism, if a child control wants to pass an event to a parent control, the parent control cannot intercept the event. When a child control wants to preprocess an event to a parent control, it must pass the event to the parent control in its onTouchEvent method. It should be noted that when the child control calls the startNestedScroll method, it will only determine whether there is a parent control that supports nested sliding and inform the parent control that nested sliding starts. There is no actual event passing at this time. Therefore, this method can only be called when the event in the onTouchEvent method of the child control is motionEvent.action_down. The pseudocode is shown below:
public boolean onTouchEvent(MotionEvent event) {
int action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_DOWN: {
mLastX = x;
mLastY = y;
// Find the parent of the nested slide and inform the parent that the nested slide has started. The default is vertical
startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
break; }}return super.onTouchEvent(event);
}
Copy the code
How does a child view find the parent control and tell the parent control to start a nested slide just by using the startNestedScroll method? Let’s take a look at startNestedScroll method implementation, startNestedScroll within a method will be called NestedScrollingChildHelper startNestedScroll method. The specific code is as follows:
public boolean startNestedScroll(@ScrollAxis int axes, @NestedScrollType int type) {
if (hasNestedScrollingParent(type)) {
// Already in progress
return true;
}
if (isNestedScrollingEnabled()) {// Determine whether the child controls support nested sliding
// Get the parent control of the current view
ViewParent p = mView.getParent();
View child = mView;
while(p ! =null) {
// Determine whether the current parent control supports nested sliding
if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes, type)) {
setNestedScrollingParentForType(type, p);
ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes, type);
return true;
}
if (p instanceof View) {
child = (View) p;
}
// Keep looking upp = p.getParent(); }}return false;
}
Copy the code
We can see from the code, when the sliding control supports nested, child controls will obtain the current parent controls, and call the ViewParentCompat. OnStartNestedScroll method. Let’s continue with the method:
public static boolean onStartNestedScroll(ViewParent parent, View child, View target,
int nestedScrollAxes, int type) {
if (parent instanceof NestedScrollingParent2) {// Check whether the parent controls implement NestedScrollingParent2
// First try the NestedScrollingParent2 API
return ((NestedScrollingParent2) parent).onStartNestedScroll(child, target,
nestedScrollAxes, type);
} else if (type == ViewCompat.TYPE_TOUCH) {// If the parent controls NestedScrollingParent
// Else if the type is the default (touch), try the NestedScrollingParent API
return IMPL.onStartNestedScroll(parent, child, target, nestedScrollAxes);
}
return false;
}
Copy the code
If we look at the code, we can see that when the parent controls implement the NestedScrollingParent interface, the IMPL. OnStartNestedScroll method will be used, so we continue:
public boolean onStartNestedScroll(ViewParent parent, View child, View target,
int nestedScrollAxes) {
if (parent instanceof NestedScrollingParent) {
return ((NestedScrollingParent) parent).onStartNestedScroll(child, target,
nestedScrollAxes);
}
return false;
}
Copy the code
The onStartNestedScroll method in ViewParetCompat will be called, which will eventually call the onStartNestedScroll method of the parent control. The parent control’s onStartNestedScroll will be called to determine whether nested sliding is supported.
So let’s go back to the startNestedScroll method of the child control. We can see that if the current parent control does not support nested sliding, it will look up until it finds one. If it is still not found, the nested sliding methods of the subsequent child and parent controls are not called. If the child control finds a parent control that supports nested slides, the onNestedScrollAccepted method of the parent control is then called to indicate that the parent control accepts nested slides.
Child control dispatchNestedPreScroll method call timing
When the parent accepts nested slides, the child controls need to pass gesture slides to the parent. Since slides have already occurred, events in MotionEvent.ACTION_MOVE are filtered in onTouchEvent. Then call the dispatchNestedPreScroll method and these pass the slide event to the parent control. The pseudocode is shown below:
private final int[] mScrollConsumed = new int[2];// Records the distance consumed by the parent control
public boolean onTouchEvent(MotionEvent event) {
int action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_MOVE: {
int dy = mLastY - y;
int dx = mLastX - x;
// Pass the event to the parent control and record the distance consumed by the parent control.
if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset)) {
dx -= mScrollConsumed[0];
dy -= mScrollConsumed[1]; }}}return super.onTouchEvent(event);
}
Copy the code
In the above code, dy and dx are the vertical and horizontal distances of the child control respectively, and int[] mScrollConsumed is used vertically to record the distance consumed by the parent control. So when we call dispatchNestedPreScroll to pass the event to the parent control for consumption, then the actual distance that the child control can process is:
- Horizontal: dx -= mScrollConsumed[0];
- Vertical direction: dy -= mScrollConsumed[1];
Next, we continue to look at the dispatchNestedPreScroll method.
Inside the dispatchNestedPreScroll method will be called NestedScrollingChildHelper dispatchNestedPreScroll method specific code is as follows:
public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed,
@Nullable int[] offsetInWindow, @NestedScrollType int type) {
if (isNestedScrollingEnabled()) {
// Get the parent control of the current nested slide, if null, return directly
final ViewParent parent = getNestedScrollingParentForType(type);
if (parent == null) {
return false;
}
if(dx ! =0|| dy ! =0) {
int startX = 0;
int startY = 0;
if(offsetInWindow ! =null) {
mView.getLocationInWindow(offsetInWindow);
startX = offsetInWindow[0];
startY = offsetInWindow[1];
}
if (consumed == null) {
if (mTempNestedScrollConsumed == null) {
mTempNestedScrollConsumed = new int[2];
}
consumed = mTempNestedScrollConsumed;
}
consumed[0] = 0;
consumed[1] = 0;
// Call onNestedPreScroll of the parent control to handle the event
ViewParentCompat.onNestedPreScroll(parent, mView, dx, dy, consumed, type);
if(offsetInWindow ! =null) {
mView.getLocationInWindow(offsetInWindow);
offsetInWindow[0] -= startX;
offsetInWindow[1] -= startY;
}
return consumed[0] != 0 || consumed[1] != 0;
} else if(offsetInWindow ! =null) {
offsetInWindow[0] = 0;
offsetInWindow[1] = 0; }}return false;
}
Copy the code
In this method, the parent control that gets the current nested slide is determined first. If the parent is not null and support nested sliding, then invokes the ViewParentCompat. OnNestedPreScroll () method. The code looks like this:
public void onNestedPreScroll(ViewParent parent, View target, int dx, int dy,
int[] consumed) {
if (parent instanceofNestedScrollingParent) { ((NestedScrollingParent) parent).onNestedPreScroll(target, dx, dy, consumed); }}Copy the code
The observation code eventually calls the parent control’s onNestedPreScroll method. It is important to note that the parent control may consume all sliding events passed by the child control. There are no further events for the child control to handle.
The onNestedPreScroll method is especially important in determining the sliding distance of the parent control during nested slides.
Child control dispatchNestedScroll method call timing
After the parent control preprocesses the slide event, that is, after calling the onNestedPreScroll method and passing the consumed distance to the child control, the child control retrieves the remaining events and consumes them. If the child control is still not consumed, dispatchNestedScroll is called to pass the remaining events to the parent control. If the parent control does not process. This is then passed to child controls for processing. The pseudocode is shown below:
private final int[] mScrollConsumed = new int[2];// Records the distance consumed by the parent control
public boolean onTouchEvent(MotionEvent event) {
int action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_MOVE: {
int dy = mLastY - y;
int dx = mLastX - x;
// Pass the event to the parent control and record the distance consumed by the parent control.
if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset)) {
dx -= mScrollConsumed[0];
dy -= mScrollConsumed[1];
scrollNested(dx,dy);// Handle nested sliding}}}return super.onTouchEvent(event);
}
// Handle nested sliding
private void scrollNested(int x, int y) {
int unConsumedX = 0, unConsumedY = 0;
int consumedX = 0, consumedY = 0;
// How many events are consumed by child controls is up to you
if(x ! =0) {
consumedX = childConsumeX(x);
unConsumedX = x - consumedX;
}
if(y ! =0) {
consumedY = childConsumeY(y);
unConsumedY = y - consumedY;
}
// Child controls handle events
childScroll(consumedX, consumedY);
// After the child control processes, it passes the remaining events to the parent control
if (dispatchNestedScroll(consumedX, consumedY, unConsumedX, unConsumedY, mScrollOffset)) {
// After passing to the parent control, the rest of the logic is implemented by itself
}
// Pass to the parent control, the parent control does not process, then the child control continues processing.
childScroll(unConsumedX, unConsumedY);
}
/** * child control slide logic */
private void childScroll(int x, int y) {
// How does the child control slide to implement itself
}
/** * how far does the child control consume horizontally */
private int childConsumeX(int x) {
// The specific logic is implemented by itself
return 0;
}
/** * child control vertical consumption distance */
private int childConsumeY(int y) {
// The specific logic is implemented by itself
return 0;
}
Copy the code
In the code above, these methods are abstracted because the distance consumed by the child control is determined by the child control. Inside the child controls dispatchNestedScroll method will be called NestedScrollingChildHelper dispatchNestedScroll method, the specific code is as follows:
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow,
@NestedScrollType int type) {
if (isNestedScrollingEnabled()) {
final ViewParent parent = getNestedScrollingParentForType(type);
if (parent == null) {
return false;
}
if(dxConsumed ! =0|| dyConsumed ! =0|| dxUnconsumed ! =0|| dyUnconsumed ! =0) {
int startX = 0;
int startY = 0;
if(offsetInWindow ! =null) {
mView.getLocationInWindow(offsetInWindow);
startX = offsetInWindow[0];
startY = offsetInWindow[1];
}
// Call the onNestedScroll method of the parent control.
ViewParentCompat.onNestedScroll(parent, mView, dxConsumed,
dyConsumed, dxUnconsumed, dyUnconsumed, type);
if(offsetInWindow ! =null) {
mView.getLocationInWindow(offsetInWindow);
offsetInWindow[0] -= startX;
offsetInWindow[1] -= startY;
}
return true;
} else if(offsetInWindow ! =null) {
// No motion, no dispatch. Keep offsetInWindow up to date.
offsetInWindow[0] = 0;
offsetInWindow[1] = 0; }}return false;
}
Copy the code
Within this method will be called ViewParentCompat onNestedScroll method. Continuing tracing will eventually call the ViewParentCompat nonstatic onNestedScroll method as follows:
public void onNestedScroll(ViewParent parent, View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed) {
if (parent instanceofNestedScrollingParent) { ((NestedScrollingParent) parent).onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed); }}Copy the code
In this method, the onNestedScroll method of the parent control is finally called to process the remaining distance of the child control.
Child control stopNestedScroll method call timing
When the entire sequence of events ends (when the finger lifts or unslides), the parent control needs to be notified that the nested slide has ended. Therefore, we need to filter events in MotionEvent.ACTION_UP and MotionEvent.ACTION_CANCEL in OnTouchEvent, and notify the parent control by stopNestedScroll () method. The pseudocode is shown below:
public boolean onTouchEvent(MotionEvent event) {
int action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_UP: { // End the event pass when the finger is raised
stopNestedScroll();
break;
}
case MotionEvent.ACTION_CANCEL: { // End the event pass when the finger is raised
stopNestedScroll();
break; }}return super.onTouchEvent(event);
}
Copy the code
In the stopNestedScroll () method, the parent control’s onStopNestedScroll () method will eventually be called, so I won’t do any more analysis here.
The child control is fling
Now we are left with one last nested sliding method!! Right! Is fling. Before we can understand how a child control handles fling, we need to know what fling represents. In Android, you slide a finger across the screen and release it. The control slides inertia in the same direction as the finger and stops. That is, we need to filter the motionEvent.action_up event in the onTouchEvent method and get the desired sliding speed. The pseudocode is as follows:
To fling is to fling.
public boolean onTouchEvent(MotionEvent event) {
// Add a speed detector for processing fling
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
int action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_UP: {
mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity);
int xvel = (int) mVelocityTracker.getXVelocity();
int yvel = (int) mVelocityTracker.getYVelocity();
if(! dispatchNestedPreFling(velocityX, velocityY)) {boolean consumed = canScroll();
// Pass the fling effect to the parent control
dispatchNestedFling(velocityX, velocityY, consumed);
// The child controls process the fling
childFling();// How does a child control implement its own fling
stopNestedScroll();// The child control notifies the parent control that scrolling has ended
}
stopNestedScroll();// Tell the parent control to stop sliding
break; }}return super.onTouchEvent(event);
}
Copy the code
There is no explanation of how the fling effect is distributed to the parent control. Be sure to understand this in conjunction with the NestedScrollingChildView class. So assuming everyone has looked at the source code, we can get the following points:
- The child control dispatchNestedPreFling eventually invokes the parent control’s onNestedPreFling method.
- The dispatchNestedFling of the child control eventually invokes the onNestedFling method.
- If the parent control’s intercept fling(that is, the onNestedPreFling method) returns as
true
). Child controls have no chance to process fling. - If the parent control
Don't
Intercept Fling (that is, the onNestedPreFling method returns asfalse
), the parent controls call the onNestedFling method and the child controls process the fling simultaneously. - When both the parent control and the child control process a fling, the child control immediately calls the stopNestedScroll method to notify the parent that the nesting slide is over.
Introduction to NestedScrollingChild2 and NestedScrollingParent2
The last knowledge point, everybody refuels!!!!!!
In the first half of this article, we explained NestedScrollingChild and NestedScrollingParent. NestedScrollingChild2 and NestedScrollingParent2 interfaces are not mentioned. So what do these two interfaces do? This goes back to the process that NestedScrollingChild uses to handle a fling. The API design of NestedScrollingChild and NestedScrollingChild used to create a fling. The following issues are not considered:
- It is impossible for the parent control to know whether the child control ends. Child controls are only available in
ACTION_UP
The stopNestedScroll method is called in. Although the parent control is notified to end the nested slide, the child control may still be in its fling. - There is no way for a child control to pass part of its fling to the parent control. The parent control must process the entire fling.
Using NestedScrollingChild2 and NestedScrollingParent2, a child can transfer its fling to its parent, and the parent can transfer the rest of the fling to its children. When the child control stops fling, the parent control is notified that the fling is over. Is this very similar to the nested sliding we analyzed earlier? Let’s look at the following example:
The effect of implementation, please see NestedScrollingParentLayout. Java
In the above example, nesting sliding of NestedScrollingChild(NestedScrollView or RecyclerView, etc.) and NestedScrollingParent interface is implemented. We can obviously see that when our fingers quickly slide down and lift, The child controls are distributed to the parent controls because the distance between them is different. When the parent controls slide and fling, the RecyclerView or NestedScrollView continues to scroll. This creates a discontinuous feel, as if each control is sliding alone.
Under the same sliding condition, the nested sliding of NestedScrollingChild2(NestedScrollView or RecyclerView, etc.) and NestedScrollingParent2 interface is realized. Look at the following example:
The effect of implementation, please see NestedScrollingParent2Layout. Java
Looking at the figure above, we can see that the parent control and child control (RecyclerView or NestedScrollView) slide more smoothly and reasonably. Let’s take a look at Google’s design.
NestedScrollingChild2 and NestedScrollingParent2 inherit NestedScrollingChild and NestedScrollingParent respectively, and add type parameter to inherited interface part method. The value of type is TYPE_TOUCH(0) and TYPE_NON_TOUCH(1). Used to distinguish between gestures that slide and fling. The specific differences are shown in the figure below:
The picture is large and may not be clear. It is recommended to zoom in.
The logic of the onTouchEvent method is different from that of NestedScrollingChild and NestedScrollingParent. The pseudo-code for the onTouchEvent method is as follows:
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getActionMasked();
int y = (int) event.getY();
int x = (int) event.getX();
// Add a speed detector to handle the fling effect
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
switch (action) {
case MotionEvent.ACTION_UP: {// When the finger is lifted, end the nested slide and determine whether the fling effect has occurred
mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity);
int xvel = (int) mVelocityTracker.getXVelocity();
int yvel = (int) mVelocityTracker.getYVelocity();
fling(xvel, yvel);// How to handle the fling
mVelocityTracker.clear();
stopNestedScroll(ViewCompat.TYPE_TOUCH));// Stop with a parameter
break; }}return super.onTouchEvent(event);
}
Copy the code
When the child is lifted, stopNestedScroll(viewCompat.type_touch) is called to notify the parent that the scroll has ended and to continue looking at the fling method. The pseudocode is shown below:
private boolean fling(int velocityX, int velocityY) {
// Determine if the speed is sufficient. Execute the fling if you are large enough
if (Math.abs(velocityX) < mMinFlingVelocity) {
velocityX = 0;
}
if (Math.abs(velocityY) < mMinFlingVelocity) {
velocityY = 0;
}
if (velocityX == 0 && velocityY == 0) {
return false;
}
if (dispatchNestedPreFling(velocityX, velocityY)) {
boolean canScroll = canScroll();
// Pass the fling effect to the parent control
dispatchNestedFling(velocityX, velocityY, canScroll);
// The child controls are processing the fling effect
if (canScroll) {
// Tell the parent control to start the fling event,
startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_NON_TOUCH);
velocityX = Math.max(-mMaxFlingVelocity, Math.min(velocityX, mMaxFlingVelocity));
velocityY = Math.max(-mMaxFlingVelocity, Math.min(velocityY, mMaxFlingVelocity));
doFling(velocityX, velocityY);
return true; }}return false;
}
Copy the code
The dispatchNestedPreFling and dispatchNestedFling methods are still called in the new interface’s processing logic. The fling has not been replaced, but that does not mean there is no change. The child controls call the startNestedScroll method and set TYPE_NON_TOUCH (Fling) to TYPE_NON_TOUCH. The onStartNestedScroll method lets you know whether the scroll type is fling or scroll. Let’s move on to the doFling method. The pseudocode is as follows:
/** * The actual fling effect */
private void doFling(int velocityX, int velocityY) {
mScroller.fling(0.0, velocityX, velocityY, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
postInvalidate();
}
Copy the code
The doFling method is as simple as calling The OverScroller fing method and calling the postInvalidate method (not postOnAnimation(), for your sake). OverScroller Fing method mainly calculates the actual movement distance under the condition of uniform deceleration according to the current incoming speed. This explains why the child controls can pass the fling to the parent if only the speed is present, because the speed ends up being the actual distance traveled.
The algorithm that converts speed to distance in Scroller’s fling method is not explained here. Unfamiliar friends can Google or Baidu by themselves.
If you’re familiar with Scroller, you need to call either the postInvalidate() or Invalidate() method to get the distance to the fling. At the same time, the actual movement distance is obtained in the computeScroll() method of the child control. So the final distribution of fing in the child control is actually in the computeScroll() method. Continue to look at the pseudocode for this method:
public void computeScroll(a) {
if (mScroller.computeScrollOffset()) {
int x = mScroller.getCurrX();
int y = mScroller.getCurrY();
int dx = x - mLastFlingX;
int dy = y - mLastFlingY;
mLastFlingX = x;
mLastFlingY = y;
// Determine whether the parent control is consumed before the child controls process the fling
if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, null, TYPE_NON_TOUCH)) {
// Calculate the remaining distance after the parent control is consumed
dx -= mScrollConsumed[0];
dy -= mScrollConsumed[1];
// Because the parent control was passed vertical by default, the child control consumes the remaining vertical
int hResult = 0;
int vResult = 0;
int leaveDx = 0;// The distance that the child controls consume horizontally
int leaveDy = 0;// The distance the parent controls consume vertically
if(dx ! =0) {
leaveDx = childFlingX(dx);
hResult = dx - leaveDx;// Get the horizontal distance left after the child control is consumed
}
if(dy ! =0) {
leaveDy = childFlingY(dy);// Get the remaining vertical distance after the child control is consumed
vResult = dy - leaveDy;
}
dispatchNestedScroll(leaveDx, leaveDy, hResult, vResult, null, TYPE_NON_TOUCH); }}else {
// Notify the parent control when the fling endsstopNestedScroll(TYPE_NON_TOUCH); }}Copy the code
Looking at the code, we can see that the way the child controls distribute the fling is very similar to the logic used to distribute the gesture scroll.
- When fing is generated, the band is called
type(TYPE_NON_TOUCH)
The dispatchNestedPreScroll method of the parameter determines whether the parent control handles the Fling event. - If the parent control processes, the child control consumes the remaining distance after the parent control consumes it
- After the child control is consumed, if there is any distance left, the band is called
type(TYPE_NON_TOUCH)
The dispatchNestedScroll method of the parameter passes the remaining distance to the parent control. - When the child control fling ends, the stopNestedScroll(TYPE_NON_TOUCH) method is called to notify the parent control that the fling has ended.
NestedScrollingChild2 and NestedScrollingParent2 add TYPE_NON_TOUCH to the existing methods to let the parent control distinguish between sliding and fling. Have to admire the design of Google. Not only compatible but also solves practical problems.
conclusion
Through the above analysis, we can draw the following conclusions:
- The NestedScrolling mechanism is based on the original event mechanism. In order to achieve NestedScrolling, the parent control cannot intercept events.
- NestedScrolling uses pairs of interfaces. For example, NestedScrollingChild2 is paired with NestedScrollingParent2. NestedScrollingChild is paired with NestedScrollingParent.
- NestedScrollingChild2 and NestedScrollingParent2 are used to distribute the child control to the parent control. And in the corresponding method through type (
TYPE_TOUCH(0)
,TYPE_NON_TOUCH(1)
) to determine whether to swipe or fling.
The last
Now the whole NestedScrolling mechanism is explained. In the following articles, we will explain the corresponding NestedScrolling examples, CoordinatorLayout and Behavior, custom Behavior and other related knowledge points. If you are interested, you can continue to follow ~. Thank you for taking the time to read this article. Thanks