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. Smooth processing, glide processing
  2. View sliding effect and logic
  3. Androidx sliding interface, nested sliding interface understanding

Now, you don’t feel like you’re just handling the sliding event in OnTouchEvent anymore, you can do that, but it’s all custom, right

2 Sliding constant

Before we introduce sliding, we need to know some sliding constants that will help us achieve a smoother slide

These constants are obtained through ViewConfiguration, as shown in the following example

ViewConfiguration configuration = ViewConfiguration.get(mContext)
Copy the code
  • Minimum sliding distance: getScaledTouchSlop()
  • Minimum glide speed: getScaledMinimumFlingVelocity (), pixels per second
  • Maximum glide speed: getScaledMaximumFlingVelocity (), pixels per second
  • Finger sliding across the maximum range: getScaledOverscrollDistance ()
  • Gliding across the largest distance: getScaledOverflingDistance ()

The speed of gliding here is to deal with the speed of inertia, which I have done deeply experience, always feel that the speed is not very comfortable; So when we generally glide, when we get the glide distance, we want to be between the maximum and the minimum;

3 Smooth sliding, gliding

Smooth sliding carries out gentle sliding according to time, while gliding requires tracking and analysis of movement events, and then carries out analysis according to time calculation state. According to the time for state processing, use Scroller or OverScroller to deal with, OverScroller can deal with the rebound effect; For event tracking analysis, use the VelocityTracker class

3.1 VelocityTracker class

This class has the following uses

Instance for

mVelocityTracker = VelocityTracker.obtain()
Copy the code

Track events

mVelocityTracker.addMovement(ev)
Copy the code

Get initial glide speed

mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int xVelocity = (int)mVelocityTracker.getXVelocity(mActivePointerId);
int yVelocity = (int)mVelocityTracker.getYVelocity(mActivePointerId);
Copy the code

The two methods should be followed to reduce errors; Additional calculation; ComputeCurrentVelocity parameter meaning

  1. How many milliseconds? Let’s say n
  2. The speed, measured in units of the value of parameter 1, is pixels per n milliseconds

Temporary data cleanup

mVelocityTracker.clear();
Copy the code

When switching fingers, the previous data is meaningless, so it needs to be cleaned up and recalculated

Object to recycle

mVelocityTracker.recycle();
Copy the code

3.2 OverScroller class

Scroller can handle it, just not rebound; This is just to explain the OverScroller class, which is just a state calculation class that doesn’t operate on views; Here are some of them

Initialize the

mScroller = new OverScroller(getContext());
Copy the code

sliding

public void startScroll(int x, int y, int dx, int dy, int duration)
Copy the code

X increases dx, y increases dy in unit time; The default time is 250ms

To calculate

public boolean computeScrollOffset()
Copy the code

Calculate the status of the current time, return true to indicate that the current status is still in progress, you can obtain the current status through the following

  • GetCurrX () : current x position
  • GetCurrY (): current y position
  • GetCurrVelocity () : current velocity

The springback

public boolean springBack(int startX, int startY, int minX, int maxX, int minY, int maxY)
Copy the code
  • The current value of x
  • The current value of y
  • X minima
  • Y the minimum
  • The maximum value of x
  • The maximum value of y

If used in sliding, it represents sliding distance, sliding minimum distance, sliding maximum distance;

gliding

fling(int startX, int startY, int velocityX, int velocityY,int minX, int maxX, int minY, int maxY, int overX, int overY)
Copy the code
  • Current x position
  • Current y position
  • Current X speed, pixels per second
  • The current y speed, pixels per second
  • Minimum value of x
  • Minimum y
  • Maximum value of x
  • Maximum value of y
  • X Maximum distance crossed
  • Y Indicates the maximum distance crossed

Bounces back only if it’s out of bounds

discarded

mScroller.abortAnimation();
Copy the code

Complete the judgment

mScroller.isFinished()
Copy the code

3.3 Smooth Movement

This only needs to call the startScroll method of OverScroller to trigger, and call the scrollTo method in View computeScroll method to obtain the sliding state.

3.4 the glide

Gliding falls into two categories

  1. When the finger leaves and does not cross the boundary, glide. If it can bounce back, it will also bounce back and invoke the OverScroller fling method
  2. When the finger is out of bounds when it leaves, it will bounce back and call the OverScroller’s springBack method

It is also necessary to perform specific sliding in the computeScroll according to the state of the calculation

4 the View class

View class for sliding, provides sliding execution mechanism, sliding indicator, sliding fade layer, long press event processing and sliding some data judgment, these and sliding interface ScrollingView in AndroidX

4.1 Specific implementation of sliding

The specific execution is through the View variables mScrollX, mScrollY to complete, these two variables in the drawing, the canvas will be translated (see the View class draw method is called where), resulting in the content of the draw changes; This shift has no effect on the background of the current view, because the background is shifted in the opposite direction again (see the drawBackground method in the View class). And the operations on these two variables are

  • ScrollTo (int x, int y): moves to x, y
  • ScrollBy (int x, int y) : move range increases by x, y
  • OverScrollBy method, which will automatically handle the out-of-bounds processing and call onOverScrolled for the actual movement processing

I call these two methods performers; However, many sliding controls have smooth movement, and smooth movement is basically accomplished by using OverScroller or Scroller sliding method. Use the OverScroller for rebound, otherwise use the Scroller

protected boolean overScrollBy(int deltaX, int deltaY,
            int scrollX, int scrollY,
            int scrollRangeX, int scrollRangeY,
            int maxOverScrollX, int maxOverScrollY,
            boolean isTouchEvent)
Copy the code

The overScrollBy method returns the result. True indicates that the overbounds are crossed and rebound is required.

  1. Incremental value of x
  2. Incremental value of y
  3. X current movement value
  4. Y Current movement value
  5. X current maximum
  6. Y current maximum value
  7. X Maximum rebound value
  8. Y Maximum rebound value
  9. Finger movement or glide
protected void onOverScrolled(int scrollX, int scrollY,
            boolean clampedX, boolean clampedY)
Copy the code

OnOverScrolled method, parameter meanings are as follows:

  1. Current X slide
  2. Current y slide
  3. Is x out of bounds? True means it is out of bounds
  4. Y indicates whether the value is out of bounds. True indicates that the value is out of bounds

4.2 Holding Down Events

See the source code in the View class onTouchEvent method, isInScrollingContainer method

Long press events have certain rules:

  • The down event is used to trigger the sending delay callback and long press. The callback execution does not necessarily require finger lifting
  • Cancelled in cancel, move, or UP events

For the ordinary non-slip container view, long press event delay time is ViewConfiguration. GetLongPressTimeout (); And if they are sliding in the container, this time will trigger again send a trigger long according to the extension of the task, the time delay for ViewConfiguration. GetTapTimeout (); In my opinion, considering the particularity of sliding, a little more time can be added to determine whether it is a long-press event more accurately.

Whether sliding container judgment method, is by the ViewGroup shouldDelayChildPressedState method to handle; This method needs to return true in a sliding container

4.3 fade mask layer

Source code see view. draw method, drawing is divided into two cases, according to the mViewFlags flag to judge; That is, whether horizontal fade or vertical mask needs to be drawn;

This flag can be set in two ways, default is None

  • XML parameter configuration
android:requiresFadingEdge="horizontal|vertical|none"
Copy the code
  • Code sets
setHorizontalFadingEdgeEnabled(true);
setVerticalFadingEdgeEnabled(true);
Copy the code

It is not this setting, horizontal or vertical, these places will definitely appear in the mask, there are other restrictions, the mask is divided into 4, the four methods, logic is consistent, slightly different methods;

Layer has a height, also there are two ways to change, the default is ViewConfiguration getScaledFadingEdgeLength ()

  • XML setup
android:fadingEdgeLength="16dp"
Copy the code
  • Set by method
setFadingEdgeLength(int length)
Copy the code

The height to be drawn is basically this height, unless the height is greater than the control itself, which is half the control height

The mask also has a ratio of parameters to each edge, which is between 0 and 1; Values returned outside the range are ignored; The default implementation is as follows:

protected float getTopFadingEdgeStrength() { return computeVerticalScrollOffset() > 0 ? 1.0 f: 0.0 f; } protected float getBottomFadingEdgeStrength() { return computeVerticalScrollOffset() + computeVerticalScrollExtent() <  computeVerticalScrollRange() ? 1.0 f: 0.0 f; } protected float getLeftFadingEdgeStrength() { return computeHorizontalScrollOffset() > 0 ? 1.0 f: 0.0 f; } protected float getRightFadingEdgeStrength() { return computeHorizontalScrollOffset() + computeHorizontalScrollExtent() < computeHorizontalScrollRange() ? 1.0 f: 0.0 f; }Copy the code

The second condition is: the ratio of the mask * the height of the mask > 1.0f then the edge of this position will be drawn

The mask is a rectangular linear gradient mask, handled by linear shade; The gradient is from a completely opaque color to a completely transparent color

shader = new LinearGradient(0, 0, 0, 1, color | 0xFF000000, color & 0x00FFFFFF, Shader.TileMode.CLAMP)
Copy the code

This color can be changed by overriding the following method, which defaults to black

    public int getSolidColor() {
        return 0;
    }
Copy the code

In fact, this layer in android all standard controls, only the time control directly used, other retained features; And from the default implementation of the system, this is implemented for sliding

4.4 the scroll bar

It consists of two parts: a Track and a Thumb. The slide can be considered as a sliding whole, fixed, and the slide block is only a part of it, the position can be changed;

There are show and hide control, source see awakenScrollBars(), onDrawScrollBars method;

4.4.1 display

The display is controlled by parameters, that is, the display position, and the direction of the display position can be slid to display; There are two ways

  1. Set in the XML
android:scrollbars="vertical|horizontal"
Copy the code
  1. Code sets
public void setHorizontalScrollBarEnabled(boolean horizontalScrollBarEnabled)
public void setVerticalScrollBarEnabled(boolean verticalScrollBarEnabled)
Copy the code

4.4.2 hidden

Parameter controlled, can be configured or set in the XML layout; The default is true, as follows

android:fadeScrollbars="true"

public void setScrollbarFadingEnabled(boolean fadeScrollbars) 
Copy the code

Fade out effect, after the display operation is presented delay, delay time, the default is ViewConfiguration. GetScrollDefaultDelay (), which can be changed by two ways

  • The configuration in XML can change when the onDrawScrollBars method call does not pass the time
android:scrollbarDefaultDelayBeforeFade="10"
Copy the code
  • OnDrawScrollBars pass time control in the code

Fade out effect as alpha transformation, was the default ViewConfiguration. GetScrollBarFadeDuration (); It can also be changed in two ways

  1. The XML configuration
android:scrollbarFadeDuration="1000"
Copy the code
  1. Methods set up
public void setScrollBarFadeDuration(int scrollBarFadeDuration)
Copy the code

4.4.3 Style control

Styles also have two forms of control

  1. Circular screen devices: Mainly for Android watches and other devices, this I can’t see the effect, not to mention the display control
  2. Other devices: Drawa ScrollBarDrawable image type

ScrollBarDrawable is a class that is not publicly available to developers, so we’ll just look at its properties here

  • Android :scrollbarSize: vertical width, horizontal height
  • ScrollbarThumbHorizontal/scrollbarThumbVertical: slider color
  • ScrollbarTrackVertical/scrollbarTrackHorizonta: slide color
  • ScrollbarStyle: slider style, default value insideOverlay, and three other values insideInset, outsideOverlay, outsideInset; InsideXXX doesn’t take into account the padding, so it covers the padding, and outside doesn’t take into account the margin, it covers the margin

Article 4.5 instructions

See the onDrawScrollIndicators method for the source code

Whether it is visible or not is controlled by three aspects

  1. The display position of the indicator bar is not None; There are two ways to set it up, XML and code
android:scrollIndicators="none"

public void setScrollIndicators(@ScrollIndicators int indicators[, @ScrollIndicators int mask])
Copy the code
  1. The indicator bar can slide in the corresponding direction of the display position; Top: Swipe up, bottom-down, left to left, right to right

Left and right sliding judgment; If the parameter is negative, it means left and positive is right

public boolean canScrollHorizontally(int direction) { final int offset = computeHorizontalScrollOffset(); final int range = computeHorizontalScrollRange() - computeHorizontalScrollExtent(); if (range == 0) return false; if (direction < 0) { return offset > 0; } else { return offset < range - 1; }}Copy the code

Sliding up and down judgment; The parent parameter indicates up, and the positive parameter indicates down

public boolean canScrollVertically(int direction) { final int offset = computeVerticalScrollOffset(); final int range = computeVerticalScrollRange() - computeVerticalScrollExtent(); if (range == 0) return false; if (direction < 0) { return offset > 0; } else { return offset < range - 1; }}Copy the code

The indicator bar icon is R.dawable. Scroll_indicator_material and cannot be changed. Here’s what I found:

<shape xmlns:android="http://schemas.android.com/apk/res/android"
       android:tint="?attr/colorForeground">
    <solid android:color="#1f000000" />
    <size
        android:height="1dp"
        android:width="1dp" />
</shape>
Copy the code

Article indicates the location: location as its one side, on the opposite side of the vertical, and position [- | +] on the other side for the picture

Only purpose, indicating which direction you can slide in at this point; I don’t think it’s practical

4.6 the springback

The default is that it can rebound, but the rebound effect is not processed; There are two ways to turn rebound on and off

  1. In the XML processing
android:overScrollMode="always"
Copy the code
  1. Code sets
public void setOverScrollMode(int overScrollMode)
Copy the code

You can obtain the value by using the following method. If the value is OVER_SCROLL_NEVER, it cannot be bounced back

public int getOverScrollMode()
Copy the code

The springback length can be set according to the corresponding constant obtained in Section 2

Rebound effect, can appear in the finger movement, glide two processes; Need to judge through the above method, for processing; The system provides the default rebound effect class EdgeEffect. Here are some examples of such applications

EdgeEffect class

EdgeEffect mEdgeGlowTop = new EdgeEffect(getContext()); // instantiate medgeglowtop.setcolor (color); Medgeglowtop. onPull(deltaDistance, Displacement) Medgeglowtop.isfinished () // State judge medgeGlowTop.onRelease () // Release MedgeGlowTop.onabsorb (Velocity) // Surrender Medgeglowtop.setsize (width, height) // Sets the range of rectangles to draw. The result indicates whether further processing is requiredCopy the code

The default drawing direction of this class is the top left corner of the current view. Therefore, when passing parameters and drawing onPull, coordinate and rotation should be considered to achieve the correct effect

4.7 Nested sliding enable disable configuration

This can be configured in XML or set in code

android:nestedScrollingEnabled="true"

public void setNestedScrollingEnabled(boolean enabled)
public boolean isNestedScrollingEnabled()
Copy the code

4.8 measurement

So here’s overriding the onMeasure method, and there are two cases

  1. Inherit the ViewGroup; You need to completely rewrite the logic yourself
  2. Inherit ViewGroup subclass; We can rely on the measurement logic of the parent class to rewrite the key method of measurement, or we can carry out the parent class measurement first

Both cases require UNSPECIFIED schema MeasureSpec.UNSPECIFIED for the child layout measurement to have the possibility of sliding distance

The more specific logic needs to be done by itself; LayoutParams is the layout parameter of the container. This class specifies some functions of the container. It is also an important way for the child view to inform the parent container of its properties

5 ScrollingView interface

This interface method is easier to understand if you understand the above

  • Slide/computeVerticalScrollRange computeHorizontalScrollRange () () : content length;
  • / computeVerticalScrollOffset computeHorizontalScrollOffset () () : the corresponding direction has been sliding distance
  • / computeVerticalScrollExtent computeHorizontalScrollExtent () () : the length of the display content

These methods are the core methods used for sliding judgment, fade mask, indicator bar and slider bar; If you do not implement the View, you will not have the effect that the View has implemented, and the corresponding method will definitely not be available. For example:

  • CanScrollHorizontally, canScrollVertically
  • Hidden scroll bar: awakenScrollBars

6 Nested interfaces

Interfaces are also divided into child view methods and parent container methods. The child view method is used to inform the parent container of the processing, and the parent container method is the high-speed child sliding view whether to process and the processing result state;

6.1 NestedScrollingParent3 interface

It inherits NestedScrollingParent2, which inherits NestedScrollingParent; Methods the following

  1. OnStartNestedScroll method: whether the parent container needs to process the sliding event of the child view. True indicates that it accepts the sliding event
  2. OnNestedScrollAccepted method: accepts a sliding event query from the child view
  3. OnStopNestedScroll method: gets a notification when a subview stops sliding
  4. OnNestedScroll method: After the child view has been sliding, the parent container will be sliding
  5. OnNestedPreScroll method: before the child view slides, the parent container slides
  6. OnNestedFling: When the child view needs to glide, the child view handles it and the parent view handles it
  7. OnNestedPreFling: The parent view glides when the child view needs to glide. Returns a result indicating whether to process
  8. GetNestedScrollAxes: Handles sliding dimensions while the child view is sliding

Note that when nested, finger gliding is relatable, whereas gliding must be mutually exclusive

Parameters involved are described as follows:

  1. Type: slide or glide, ViewCompat.TYPE_TOUCH slide, ViewCompat.TYPE_NONE_TOUCH slide
  2. Consumed: an array containing both x and y directions; It is usually an output variable that indicates how much is consumed during the current processing
  3. DxConsumed /dyConsumed: Indicates how much sliding distance the child view has consumed when passed to the parent
  4. DxUnconsumed/dyUnconsumed: that is passed to the parent container, how many sliding distance for consumption
  5. Target: indicates that it is passed from that child view
  6. Dx /dy: the distance by which the event slides
  7. Child: the immediate child of the current container that contains target
  8. The direction of the axes: sliding, ViewCompat SCROLL_AXIS_HORIZONTAL, ViewCompat. SCROLL_AXIS_VERTICAL two values
  9. VelocityX /velocityY: Initial speed when gliding

6.2 NestedScrollingChild3

Inheriting NestedScrollingChild2, which inherits NestedScrollingChild; The method is as follows:

  1. Whether setNestedScrollingEnabled/isNestedScrollingEnabled: nested sliding support
  2. StartNestedScroll: notifying nesting to start
  3. StopNestedScroll: indicates that nesting is over
  4. HasNestedScrollingParent: Whether there are inline parent containers for nested processing
  5. DispatchNestedScroll: continues to notify sliding events after processing
  6. DispatchNestedPreScroll: Notifying the slide event when the slide is not processed
  7. DispatchNestedFling: Notify the glide event after your own processing
  8. DispatchNestedPreFling: Notifying the glide event first

Parameters are not explained, similar to 6.1

6.3 the auxiliary class

In chapter two of this method are used in the View and ViewGroup, androidx also provides a default implementation of auxiliary class, these two classes is NestedScrollingParentHelper, NestedScrollingChildHelper; These two classes are primarily intended to address version-compatibility issues

summary

In the foundation, mainly introduced the sliding foundation aspects, more boring; Part of the content is not introduced in detail, in the subsequent source interpretation, will slowly come

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!

ScrollView sliding source code interpretation