1 introduction

For Android, sliding is essential; It’s not complicated, you know in onTouchEvent, you just let it slide and it’s done, it’s complicated, it’s nested; In this series, the final goal is to become familiar with the nested sliding mechanism; For sliding, it is divided into the following articles to complete the interpretation:

  1. Sliding base
  2. ScrollView sliding source code interpretation
  3. NestedScrollView nested slide source code interpretation
  4. CoordinatorLayout-AppBarLayout-CollapsingToolbarLayout complex slide logic source code interpretation

In this chapter, the implementation of some of the underlying framework logic

  1. Layout mechanism and processing
  2. Event mechanism
  3. ScrollView does not have nested sliding process (although there is nesting mechanism, but due to compatibility problems, we use NestedScrollView, introduced in the next chapter)

The layout mechanism and event distribution mechanism were not introduced in the foundation of the previous chapter, but will be introduced in detail here, because only by knowing these clearly, can the sliding processing problem be traced smoothly and accurately

2. Layout mechanism

View layout consists of three processes

  1. Measurement: Measure method, for the parent container to call the measurement process, internal through the onMeasure method for actual measurement
  2. Place: The layout method, which is called by the parent container and placed in the parent container, is used to place the descendant view inside onLayout
  3. Draw: draw method, for the parent container call, background color, its own drawing, child view drawing process, onDraw for its own drawing

2.1 measure process

The onMeasure method is mainly explained here; This method is a process of measuring its width and height, which is divided into two cases

  1. If it is not a container itself, then it is its own width and height, which can only be set by its own control limits
  2. Is the container, then need to consider the measurement of its child view, according to the child’s width and height measurement situation, and then set its own width and height

The setMeasuredDimension method is used to set the width and height, but this process requires additional dependencies. The results are determined by the following three criteria

  1. When distributing, the length of the delivery and the mode limits
  2. The layout properties of the child view
  3. Logical considerations for the view: for example, the container should consider how the child container should be placed, and the child view should restrict the width and height of the view

The above is also very straightforward, the author does not want to post too much source code, in order to explain, we need to tell about some of the following content

  1. Some member variables
  2. MeasureSpec class USES
  3. LayoutParams class
  4. Some common methods of use are helpful for our measurement

2.1.1 Member Variables

  • Measurement width/height: width and height values set during measurement; Relevant methods
protected final void setMeasuredDimension(int measuredWidth, Int measuredHeight public Final int getMeasuredWidth() public Final int getMeasuredHeight(Copy the code
  • Width/height: the placement process produces results based on the length relative to the top, bottom, left and right of the parent layout; Obtain as follows
public final int getWidth()
public final int getHeight()
Copy the code

In general, the measured width equals the width and the measured height equals the height

2.1.2 MeasureSpec class

This class is the auxiliary class for the composition and splitting of an integer; The higher two digits indicate the mode, and the lower 30 digits indicate the length; There are three models

  1. UNSPECIFIED: UNSPECIFIED mode, which means that the parent container receives the measurement results of the current view.
  2. EXACTLY: EXACTLY, where the developer defines the length
  3. AT_MOST: Maximum mode, the maximum length that a parent container can provide to a subclass

Model to obtain

MeasureSpec.getMode(widthMeasureSpec)
Copy the code

The length of the access

MeasureSpec.getSize(widthMeasureSpec)
Copy the code

synthetic

MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY)
Copy the code

2.1.3 LayoutParams class

This class is provided in the View class, which, broadly speaking, is some way for the parent container to restrict or interact with the immediate child View. But this kind of system is implemented, all is the relevant information that limits the measurement; Therefore, this class should be considered when customizing containers; There are two ways to limit a child’s view

  1. Add via addView; The following container method, corresponding to parameter extraction
protected LayoutParams generateDefaultLayoutParams()
protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p)
Copy the code
  1. Parsing through XML; The following container method is used to extract parameters
public LayoutParams generateLayoutParams(AttributeSet attrs)
Copy the code

A subclass of LayouParams should generally implement three constructors that correspond to the above three parsing methods

public LayoutParams(Context c, AttributeSet attrs)
public LayoutParams(int width, int height)
public LayoutParams(LayoutParams source)
Copy the code

The width here, the height here, is the value down here

  1. MATCH_PARENT, FILL_PARENT: negative values, indicating the same width or height as the parent container
  2. WRAP_CONTENT: The value is negative, indicating that it can accommodate the width and height measured by itself
  3. Positive: Indicates the exact value that the developer has set for it

MarginLayoutParams has a generic subclass MarginLayoutParams, which adds margin attributes in four directions; Width and height in LayoutParams and mode and length in MeasureSpec are the core logical embodiment of the invariable part in measurement.

2.1.4 Standard measurement rules

The standard rules for subviews are as follows

/** * Note rules are as follows: The value of the schema and length is returned as (Length, mode) * 1. The value of childDimension is positive, and (EXACTLY) * 2. The value of the spec is UNSPECIFIED. UNSPECIFIED) * 3. As the mode of the spec is AT_MOST and the childDimension is WRAP_CONTENT, the spec length is UNSPECIFIED. ChildDimension = WRAP_CONTENT (spec length, AT_MOST) * 5) EXACTLY) */ public static int getChildMeasureSpec(int spec, int padding, int childDimension)Copy the code
  • Spec: MeasureSpec Specifies the synthesized integer value; Note that the schema with UNSPECIFIED length less than version 23 returns 0, which is not a valid length value
  • Padding: The length already occupied, including margin; Notice that margin is handled in the parent container
  • ChildDimension: The width and height of LayoutParams

Self view standard measurement is shown in the following method

/** * As currently UNSPECIFIED, measureSpace mode is UNSPECIFIED. Currently, the size of the child mode is UNSPECIFIED. MeasureSpace = AT_MOST; Public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState)Copy the code
  • Size: the minimum length obtained by its own rule
  • MeasureSpec: Limits when measuring itself, length and width composite values
  • ChildMeasuredState: Indicates the measurement status. If the length is smaller than the minimum required length, MEASURED_STATE_TOO_SMALL is MEASURED_STATE_MASK

2.2 the layout process

public void layout(int l, int t, int r, int b)
Copy the code

This method represents the placement process, where the parameters are recorded; Parameter meanings are as follows:

  • L: The distance between the left edge of view and the left edge of parent layout; Corresponding to the member variable mLeft
  • T: The distance between the view boundary and the parent layout boundary; Corresponding to the member variable mTop
  • R: View right boundary distance from the parent layout left boundary distance; Corresponding member variable mRight;
  • B: The distance between the lower boundary of view and the upper boundary of parent layout; Corresponding to the member variable mBottom

These four parameters are used to find the correct position relative to the screen when drawing

If the measurement view is not a container, then do any processing in onLayout, then because the parameters are recorded, it can ensure that the current view is drawn to the correct position; If it is a container, you need to tell the child view where it is relative to itself

2.3 the draw process

Drawing process:

  1. Draw background color: Private method that cannot be overridden
private void drawBackground(Canvas canvas)
Copy the code
  1. Draw itself: Normally this method does nothing for the container
protected void onDraw(Canvas canvas)
Copy the code
  1. Subview drawing: Tells the subview to draw
protected void dispatchDraw(Canvas canvas)
Copy the code
  1. Foreground color: The top layer to draw; The system provides the indicator bar, the scroll bar, is handled here, so I think the edge effect is better handled here
public void onDrawForeground(Canvas canvas)
Copy the code

2.4 summary

In the layout process, the measurement in the slippable direction should be unrestricted mode, so that there can be slippable distance;

In layout, measurement is more complicated because it involves more aspects and depends on specific requirements. The placing process is relatively simple, using the results of measurement and then placed; The drawing process relies only on user-implemented effects, and its complexity depends on the requirements

3. Event distribution mechanism

The event distribution here is touch event distribution; I have seen a lot of explanations about this before, and only told them about the general process, which is probably the so-called writing reference, the author should understand; Although the three methods have played a role, I think the event is divided into two processes, rather than the flow chart of the three methods, which lead people astray

  • View Event Handling
  • ViewGroup dispatches View events

The following three processes are introduced; But a little bit about the basics

3.1 Event Basics

A touch event is a series of events that start with ACTION_DOWN and end with ACTION_UP or ACTION_CANCEL. The situation is basically as follows

  • ACTION_DOWN: Event that the first finger is pressed
  • ACTION_UP: Event of last finger lifting
  • ACTION_CANCEL: Cancel the event, that is, you cannot get the subsequent event
  • ACTION_MOVE: Move event
  • ACTION_POINTER_DOWN: Press event when multiple fingers are pressed
  • ACTION_POINTER_UP: Lifting event when multiple fingers are present
ACTION_DOWN ----> Other events -----> ACTION_UP/ACTION_CANCELCopy the code

The three related methods are also the ones we are considering rewriting

  • Distribution source method
DispatchTouchEvent (MotionEvent EV) // Event processing start methodCopy the code
  • The parent intercepts events and does not distribute methods to descendants; Method execution depends on the current state of the container. Interception is allowed, by default
OnInterceptTouchEvent (MotionEvent EV) // dispatchTouchEvent is dispatched within the dispatchTouchEvent method. ViewGroup methods are not intercepted by defaultCopy the code
  • View itself processing method
OnTouchEvent (MotionEvent Event) // dispatchTouchEvent within the dispatchTouchEvent method; This includes click and hold events by defaultCopy the code

There is a method that allows the parent to intercept the distribution, and there is a descendant view that tells the parent, please don’t intercept, as follows:

RequestDisallowInterceptTouchEvent (Boolean) disallowIntercept true / / parameter said are not allowed to interceptCopy the code

There are many methods of multi-finger sliding processing, generally divided into cases; In nested slides, relay is used, and we’ll talk about some of the handling methods of this in the section on nested slides;

3.2 View Event Processing

I won’t post the dispatchTouchEvent code in the View; Read a few key points, which are also sequential in execution

  1. For event handling, if enabled, the slider handles sliders (the slider mentioned in the previous article), which remember the result of the processing
  2. In the enable state and the touch listener exists, the process is called and the result is remembered
  3. If the previous slide result is false, touch back and remember the result

After the execution of these three processes, the processing result is the View event processing result; Note that the default state cannot be clicked, so events are not processed by default

Touch the callback

This is:

Public Boolean onTouchEvent(MotionEvent Event) // true Indicates that the current view processes this eventCopy the code

Also, in order of execution, name the key points that affect custom sliding

  1. Disable returns the click state
  2. If a touch agent exists, the result of the touch agent processing is returned
  3. Return true if you are in the click state or if the default long press event is enabled.
  4. Default return false

The View processing process is still very simple, just check whether there is a processing itself

3.3 ViewGroup Distributes View events

DispatchTouchEvent is overwritten in ViewGroup; In my opinion, it’s about who’s going to deal with it; That is

  1. Whether the descendant view is processed
  2. Do you want to deal with it

The descendant view handles the situation

  • When descendants are not allowed to intercept, the descendants of the event handler can be found
  • The descendant allows interception when the current container does not, and the view for event handling can be found

When the descendant does not process the event, the container simply issues a super.DispatchTouchEvent to handle the event itself, as if it were a View

There are some misunderstandings about the onInterceptTouchEvent method, but it should be explained here. Otherwise, when reading about nested sliders, you may ignore some of them or get confused about some of them

  • Intercepting is not only the result of the onInterceptTouchEvent method, but also if no descendants are found to process
  • OnInterceptTouchEvent, which meets the following criteria
    • Currently, the interception mode is allowed
    • Down events or descendants of events that can be handled were found
  • Before intercepting, it is possible to determine the result based on onInterceptTouchEvent. If it is not currently intercepted, it looks for the descendants of the processable event, which is within the view according to the point in time
  • The current event, if intercepted, issues Cancel to the processable descendant and resets the current processable descendant object to NULL; After this event, the onInterceptTouchEvent method is not returned, but its own onTouchEvent method is directly executed as a result

4. ScrollView does not have nested sliding mechanism

As you can see from the previous slide basics, there are a few rules we should follow

  1. Inheriting some of the functions of the View architecture,
  2. Sliding logic: here can be divided into layout process, event interception processing, sliding processing

4.1 Rewriting Method

ScrollView inherits FrameLayout without implementing any interface; Among the 6 methods that need to be handled for sliding: four of them are not basically usable by default; Methods that do not normally need to be overridden:

Public int computeVerticalScrollOffset sliding distance () / / already public int computeVerticalScrollExtent length () / / visible public int ComputeHorizontalScrollOffset sliding distance () / / already public int computeHorizontalScrollExtent () / / visible lengthCopy the code

The method that needs to be overridden is the total length of the content that needs to be displayed

public int computeHorizontalScrollRange()
public int computeVerticalScrollRange()
Copy the code

This should follow the logic

0 <= offset <= range - extent
Copy the code

The default implementation of extent is either width or height, which is the actual visible area without considering the padding; If it is possible to cross the boundary for a short distance, the range is added to the distance across the boundary

Current control, rewrite the vertical direction of the logic, also this direction can slide; But there are some caveats

  1. Margin values of subviews are not considered

After rewriting the methods, there are two methods that greatly reduce development

  • OverScrollBy: automatically calculates the current effective sliding value and obtains the overbound state, which is rolled back using the onOverScrolled method
  • CanScrollVertically/canScrollHorizontally: whether can be horizontal or vertical continue to slide

4.2 Layout Process

Measurement process

Use FrameLayout to measure logic and make changes in its key logic methods; The change is to measure the child container method

  • MeasureChild:
  • measureChildWithMargins

As the current container is swiped up and down, the width requirements remain the standard logic, and the height measurement mode is modified as MeasureSpec.UNSPECIFIED

The drawing process rewrites the Draw method, but adds edge effects on the top layer of the canvas based on the original View drawing

4.3 Event Interception

Event interception needs to be handled as follows:

  1. OnInterceptTouchEvent is not intercepted in a Down event
  2. When a slip is detected, it needs to be intercepted in onInterceptTouchEvent or onTouchEvent, and the parent layout is not allowed to intercept.

The reason for intercepting in both methods is that onInterceptTouchEvent may only be called once in a Down event; The call is as follows:

  1. The down event is called once and returns true, directly intercepting. In this case, onTouchEvent directly handles the Down event and subsequent events if the result is true
  2. OnTouchEvent is called once for a Down event, but no child view was found to handle the event. In this case, onTouchEvent also directly handles the Down event and subsequent events if the result is true
  3. The move event dispatches the subview Cancel event after zero or more times of interception; The next event is handled by onTouchEvent. If true is returned, subsequent events continue to be processed; otherwise, the event ends

Sliding: The sliding distance is greater than the minimum sliding length

4.4 Multi-finger processing

In a multi-finger event, the position inside contains the event information of multiple fingers. Some processes with multiple fingers must use the following method

  • The event type is obtained through the getActionMasked method
  • The event comes with a coordinate, and the finger index must be specified: getY(index)

Each finger has two identity variables, index and ID; This is due to compatibility due to historical version handling issues;

  • FindPointerIndex (id) = index, id = getPointerId(index)
  • Index starts from 0. After 0 is removed, the index of the next event still starts from 0
  • Get the index using the getActionIndex method in down or Point-down events

4.5 Movement Processing

Movement can be divided into finger movement on the screen and away from the screen movement

Note: OverScroller is just a computing tool, and the data needs to be refreshed and processed in the computeScroll method to apply it

4.5.1 computeScroll method

Smooth movement and glide both use this method for sliding updates

  • MScroller.com puteScrollOffset () is calculated, and returns whether update calculation
  • By comparing the calculated X and Y as well as the current sliding variables, overScrollBy is used for the actual sliding of the changes

4.5.2 Follow hand movement

This process is handled in a move event; The overScrollBy method and onOverScrolled method handle the movement together;

The onOverScrolled method actually moves in the following two ways

  1. Move through the scrollTo method
  2. Move by directly modifying the Scroll variable and refreshing the canvas

But what we can use is the scrollTo method; That’s the difference between Google Home and Google Home;

If the slide is over the boundary and the current OverScroller calculation is not finished, the rebound processing will be carried out

4.5.3 gliding

Handle in the UP event; But there are two cases

  1. Glide at a speed greater than the minimum
  2. If gliding is not possible and the boundary is crossed at this time, rebound dynamic effect is carried out

The flingWithNestedDispatch method calls the Fling method by determining whether a glide is possible. Judgment logic

  1. Slide between 0 and slide range, excluding 0 and maximum
  2. The current sliding value is greater than 0 and the speed is less than 0
  3. The current sliding value is less than the maximum sliding distance and the speed is greater than 0

5, summary

This article is from the point of view of the self to interpret, there is no posted source code, there is no meaning of each sentence of source code, is the hope that readers read the relevant chapters, their own source code, find their own understanding, so as not to cause problems due to different abstract understanding of everyone.

If you gain something from this article, please give the author an encouragement and a thumbs-up. Thank you for your support

Technology changes quickly, but the basic technology, the theoretical knowledge is always the same; The author hopes to share the basic knowledge of common technical points in the rest of his life. If you think the article is well written, please pay attention and like it. If there are mistakes in the article, please give me more advice!

Slide foundation

NestedScrollView nested slide source code interpretation