preface

The first few articles have been in the source code, speak process, reading difficulty coefficient slightly higher. Today we’re going to talk about something a little lighter and more basic: ways to make a View slide. Android and a lot of sliding View, such as ScrollView, NestedScrollView, ListView, RecyclerView, etc.. Sometimes when we want to customize some slideable views, we find no clue, which shows that we usually have a little bit less knowledge about slideable views. This article guides you through the basic postures for sliding a View. Finally, there is a simple draggable custom ScrollableView for you to check out

Basic knowledge preparation

Since scrolling is related to views and touch motionEvents, it’s worth reviewing the Android coordinate system and View coordinate system.

  1. The Android system

The Android coordinate system starts at the top left corner of the screen (0,0) and goes right on the x axis and down on the y axis.

  1. View coordinate system

    The View’s left, top, right, and bottom are all relative to the parent View.

    The values of getX() and getY() for MotionEvent are relative to the currently touched View, and getRawX() and getRawY() are relative to the left and top sides of the screen.

    The text description may not be clear, but if you want to see the pictures, you can check the related information on the Internet. It will be easier to look at the code for sliding once we have some understanding of the Android coordinate system.

View Indicates the sliding mode

  1. layout()
  2. OffsetLeftAndRight (), offsetTopAndBottom ()
  3. TranslationX, tanslationY, animation
  4. Your setX () and setY ()
  5. ScrollTo (), scrollBy ()

There are many ways to slide, but most of them are done by changing the position properties of the view and updating the position of the view by calculating dx and dy during action_move in the onTouchEvent method, and then it looks like the view is sliding. No more words, we go through one by one ~ ~

layout()

The layout method is familiar. It is used to position views:

public void layout(int l, int t, int r, int b) {... setFrame(l, t, r, b); . }protected boolean setFrame(int left, int top, int right, int bottom) {...// Calculate the previous size
      int oldWidth = mRight - mLeft;
      int oldHeight = mBottom - mTop;
      int newWidth = right - left;
      int newHeight = bottom - top;
      // Indicates whether the size has changed
      boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);

      ...

      // Left top right bottom
      mLeft = left;
      mTop = top;
      mRight = right;
      mBottom = bottom;
      //RenderNode records the view's left top right bottom when rendering the bottom layer
      mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);

      if (sizeChanged) {
        // Call the sizeChange method if the size changessizeChange(newWidth, newHeight, oldWidth, oldHeight); }... }Copy the code

The above code makes it clear that the Layout method is used to determine the top, bottom, left, and right values of the view, so we can slide the view by changing the top, bottom, left, and right values. First we define a custom View that records the coordinates of the finger’s ACTION_DOWN touch point in the onTouchEvent() method:

override fun onTouchEvent(event: MotionEvent): Boolean {
   when (event.action) {
        MotionEvent.ACTION_DOWN -> {
          // Get the x and y coordinates of motionEvent
          lastX = event.x
          lastY = event.y
        }
   }
   ...
}
Copy the code

Then we calculate how far the finger moves in ACTION_MOVE and change the value of the View’s left top right bottom:

. MotionEvent.ACTION_MOVE -> {// Slide dx and dy values
    val dx = (event.x - lastX).toInt()
    val dy = (event.y - lastY).toInt()
        // Update the view's left top right bottom
    layout(left + dx, top + dy, right + dx, bottom + dy)
}
...
Copy the code

After receiving ACTION_MOVE events in a row, we call layout to update the position of the view and make it move.

The code for all custom views is as follows:

class ScrollableView constructor(context: Context, attributeSet: AttributeSet?) :
    View(context, attributeSet) {

    private var lastX = 0f
    private var lastY = 0f

    override fun onTouchEvent(event: MotionEvent): Boolean {
    	 when (event.action) {
            MotionEvent.ACTION_DOWN -> {
              // Get the x and y coordinates of motionEvent
              lastX = event.x
              lastY = event.y
            }
            MotionEvent.ACTION_MOVE -> {
              // Slide dx and dy values
              val dx = (event.x - lastX).toInt()
              val dy = (event.y - lastY).toInt()
	      // Update the view's left top right bottom
  	      layout(left + dx, top + dy, right + dx, bottom + dy)				
           }
      	}    
        return true}}Copy the code

Reference to this custom View when run to achieve the following effect:

OffsetLeftAndRight (), offsetTopAndBottom ()

The names of these two methods indicate that they also change left, top, and bottom:

public void offsetLeftAndRight(int offset) {... mLeft += offset; mRight += offset; mRenderNode.offsetLeftAndRight(offset); . }public void offsetTopAndBottom(int offset) {... mTop += offset; mBottom += offset; mRenderNode.offsetTopAndBottom(offset); . }Copy the code

This way is very similar to the above layout, so we only need to put the above layout

layout(left + dx, top + dy, right + dx, bottom + dy)
Copy the code

Change to the following lines to achieve the same effect:

offsetLeftAndRight(dx)
offsetTopAndBottom(dy)
Copy the code

TranslationX, tanslationY

If you’ve animated properties, you know that changing the translationX and tanslationY values is a displacement animation, so you can do sliding by changing the translationX and tanslationY values of a view. Also, we replace the key sliding code of the custom view with:

translationX += dx
translationY += dy
Copy the code

To achieve the GIF renderings above.

Your setX () and setY ()

SetX () = setX();

public void setX(float x) {
    setTranslationX(x - mLeft);
}
Copy the code

SetX () calls setTranslationX() with the x-left parameter, so you can change the view’s translationX and tanslationY indirectly by setX() and setY(). Then the solution is basically the same as the translationX and tanslationY code:

x += dx
y += dy
Copy the code

The above two lines of code can also achieve the effect of the GIF in this article by replacing the key sliding code.

ScrollTo (), scrollBy ()

ScrollTo (x,y) slides to a specific value, and scrollBy(x,y) slides to increments x and y, which internally call scrollTo():

public void scrollBy(int x, int y) {
    scrollTo(mScrollX + x, mScrollY + y);
}

public void scrollTo(int x, int y) {
    if(mScrollX ! = x || mScrollY ! = y) {int oldX = mScrollX;
      intoldY = mScrollY; mScrollX = x; mScrollY = y; . }}Copy the code

Note: what exactly is scrollTo()? Online said sliding is the contents of the view/viewGroup, actually inside looking for mScrollX and mScrollY references from source, you can know the influence of the two values is the canvas canvas displacement, so we see is:

  • The view calls scrollTo to move its own content, such as textView moving text, imageView moving image
  • The viewGroup is moving its own children

Then we change the slider key code for the custom view above to:

(parent as View).scrollBy(-dx, -dy)
Copy the code

Can achieve the same effect. Note that if we want the view to follow our finger, we call the parent’s scrollBy and pass in -dx and -dy. I think the reason is that when the canvas moves in the positive direction, the actual content is not moved, so the content is moved backwards, and vice versa.

Development: Scroller

The above custom view only handles action_DOWN and action_move, so the effect is a bit stiff when the finger leaves the screen. The actual custom view is not so easy. In action_UP, the inertia sliding distance is calculated by obtaining the sliding speed of the finger when it leaves the screen to do a pan sliding, which involves a class Scroller. This article does not give an in-depth explanation of Scroller, but a brief overview of its functions: Scroller is like a sliding algorithm that uses the given:

  • StartScrollX Start sliding X value
  • StartScrollY Start sliding Y value
  • The final sliding x is (startScrollX+deltaX)
  • DeltaY y axis incremental slide value final slide y value (finalY) is (startScrollY+deltaY)
  • Duration Sliding duration

These values are used to calculate where you should slide to at each subsequent time point. Coupled with the View of computeScroll () method, each time in judging scroller.com puteScrollOffset () if it is true to get the scroller. CurrX, scroller. CurrY, and set up corresponding to the position of the View of property, To achieve smooth sliding. Two points are briefly popularized

  1. View computeScroll() method: This method calls back every time draw is called, like a hook method of Draw.
  2. Scroller.com puteScrollOffset () returns of is what?
public boolean computeScrollOffset(a) {
    // Return false if end
    if (mFinished) {
        return false;
    }

    int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);

    If the execution time is less than duration, calculate the current mCurrX, mCurrY
    if (timePassed < mDuration) {
      switch (mMode) {
      case SCROLL_MODE:
           //mInterpolator is basically the same as an interpolator for animation
           final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
           mCurrX = mStartX + Math.round(x * mDeltaX);
           mCurrY = mStartY + Math.round(x * mDeltaY);
           break; . }}else {
        // If the execution time is greater than or equal to duration, mark the end of the slide and set mCurrX and mCurrY as the final target values
        //mFinished is also true. The next time you call this method, it returns false
        mCurrX = mFinalX;
        mCurrY = mFinalY;
        mFinished = true;
    }
    return true;
}
Copy the code

Can know from the above source scroller.com puteScrollOffset () is used to identify whether the current slide over, the end of the returns false, not over return true.

After this, people familiar with Android animation will feel a bit familiar. Indeed, it is very similar to the design idea of animation, Interpolator, duration. Srcoller can be translated by scrollX or scrollY, translationX or translationY, or even more. Ok, that’s enough about Scroller for now. If you are interested, you can practice a wave by yourself. In the future, I will pick up the source code of Scroller and give relevant practice demo.

See Github for the above source code

Finally, I would like to recommend an open source library I maintain called the Excellent Android Highlight Boot Library. The Github address is github.com/hyy920109/H…

View series recommended reading:

  1. 【View series 】View event distribution source code exploration
  2. [View series] View measure process source code full analysis
  3. 【View Series 】 UNSPECIFIED, MeasureSpec is still available.
  4. 【View series 】 Handle your ViewPager2 slide conflicts