Introduction:

Slide is a common Android effect, slide operation has a good user experience.

The main content

  • How does the slide effect work
  • Seven common ways to implement sliding

The specific content

How does the slide effect work

The essence of sliding a View is to move a View and change its current position. Its principle is very similar to animation effect, which is to achieve this effect by constantly changing the coordinates of the View, dynamically and constantly changing the coordinates of the View, so as to realize the View sliding with the user’s touch.

But before we talk about sliders, we need to take a look at the window coordinate system in Android and the touch event on the screen — MotionEvent.

The Android system

In physics to describe the motion of an object, it is necessary to select a frame of reference, the so-called sliding, it is relative to the reference frame of the movement, the Android, the system will be the top left corner of the screen the vertices as Android coordinate system origin, from this point to the right is the X axis direction, down from this point is the Y axis is the direction, as shown in the figure below.

GetLocationOnScreen (intLocation []) is provided to get the position in the Android coordinates, the Android coordinates in the upper left corner of the view. Additionally, Use the getRawX(),getRawY() methods in the touch event to get the coordinates again in the Android coordinate system.

View coordinate system

Android in addition to the above in the coordinate system to have a view, he described the relationship between the child views in the position of the parent view, the two coordinate system is not complicated nor contradictions, their role is complementary to each other, similar to the Android system, view coordinate system similar to the origin for X to the right direction, the origin to Y way down, Except in the view coordinate system, the origin is no longer the top left corner of the screen in the Android coordinate system, but the top left corner of the parent view, as shown below.

The coordinates obtained by getX and getY in touch events are the coordinates in the view coordinates.

Touch event — MotionEvent

Touch event MotionEvent plays an important role in user interaction. Learning touch event well is the basis for mastering the following content. First, let’s take a look at some constants encapsulated in MotionEvent, which define different types of touch events.

// Single touch press action
public static final int ACTION_DOWN = 0;
// Single touch away action
public static final int ACTION_UP = 1;
// Single touch movement action
public static final int ACTION_MOVE = 2;
// Single touch cancels
public static final int ACTION_CANCEL = 3;
// Single touch out of bounds
public static final int ACTION_OUTSIDE = 4;
// Multi-touch press action
public static final int ACTION_POINTER_DOWN = 5;
// Multi-touch away action
public static final int ACTION_POINTER_UP = 6;
Copy the code

Normally, we get the type of touch event through event.getAction() in the onTouchEvent(MotionEvent event) method and use the switch to determine that the code module is fixed.

@Override
public boolean onTouchEvent(MotionEvent event) {
    // Get the X and Y coordinates of the current input point (view coordinates)
    int x = (int) event.getX();
    int y = (int) event.getY();
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            // Handle the input press action
            break;
        case MotionEvent.ACTION_MOVE:
            // Process the input movement action
            break;
        case MotionEvent.ACTION_UP:
            // Handle the exit action of input
            break;
    }
    return true;
}
Copy the code

You can usually use the above code to listen for touch events if multi-touch is not involved, but this is just a code module and we’ll talk about the logic later.

Android coordinates to obtain the method

In Android, the system provides a lot of methods to obtain coordinate values, relative distance, etc., the method is rich is good, but also to beginners brought a lot of trouble, DO not know in what case to use the following summary of some commonly used API.

These methods can be divided into two categories:

  • View provides a method to obtain coordinates:
    • GetTop (): Gets the distance from the top of the View itself to the top of its parent layout.
    • GetLeft (): Gets the distance from the left of the View itself to the left of its parent layout.
    • GetRight (): Retrieves the distance from the right of the View itself to the left of its parent layout.
    • GetBottom (): Retrieves the distance from the bottom of the View itself to the top of its parent layout.
  • Methods provided by MotionEvent:
    • GetX (): Gets the distance from the click event to the left of the control, the view coordinates.
    • GetY (): Gets the distance between the click event and the top of the control, the view coordinates.
    • GetRawX: Gets the distance to the left of the entire screen of the click event, the absolute coordinates.
    • GetRawY: Gets the absolute coordinates of the distance across the top of the screen of the click event.

Seven ways to slide

After understanding the Android coordinate system and touch events, let’s take a look at how to use the API provided by the system to dynamically modify the coordinates of a View, namely the sliding effect. No matter which way is adopted, the implementation idea is basically the same. When touching the View, the system several times the coordinates of the View, To obtain the offset relative to the previous coordinates, and through the offset to modify the View’s coordinates, so that continuous repetition to achieve the sliding process.

Here’s an example of how to implement sliding in Android. Define a View and place it in a LinearLayout to implement a simple layout, as shown in the code below.


      
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.lgl.scrollviewdemo.DragView
        android:layout_width="100dp"
        android:layout_height="100dp" />
</RelativeLayout>
Copy the code

The default display looks like the following figure.

Layout method

As we all know, onLayout() is called to set the position of the View. You can also modify the View’s left, top, right, and bottom attributes to control the coordinates of the View. Each time onTouchEvent() is called, we get the coordinates of the points. The logic here is clear. The code looks like this.

 // Touch events
private int lastX = 0;
private int lastY = 0;

@Override
public boolean onTouchEvent(MotionEvent event) {
    int rawX = (int) event.getRawX();
    int rawY = (int) event.getRawY();

    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            // Record the coordinates of the touch points
            lastX = rawX;
            lastY = rawY;
            break;
        case MotionEvent.ACTION_MOVE:
            // Calculate the offset
            int officeX = rawX - lastX;
            int officeY = rawY - lastY;
            // Add the offset to the current left,top,right,bottom
            layout(getLeft() + officeX, getTop() + officeY, getRight() + officeX, getBottom() + officeY);
            // Reset the initial value
            lastX = rawX;
            lastY = rawY;
            break;
        case MotionEvent.ACTION_UP:
            // Handle the exit action of input
            break;
    }
    return true;
}
Copy the code

So here we can move this View.

With offsetTopAndBottom offsetLeftAndRight () ()

This method is equivalent to the system provides a left, right, up and down encapsulation, when calculating the offset, just use the following code to complete the View Layout, the effect is the same as using the Layout() method.

// Offset the left and right simultaneously
offsetLeftAndRight(officeX);
// Offset both up and down
offsetTopAndBottom(officeY);
Copy the code
LayoutParams

LayoutParams keeps the layout parameters of a View, so you can dynamically change the position parameters of a layout in the program by changing LayoutParams, thus changing the effect of the View position. We can easily use getLayoutParams() to obtain a View’s LayoutParams. Of course, the offset calculation method is the same as the Layout method. When we get the offset, we can use getLayoutParams to obtain a View’s LayoutParams. LayoutParams can be changed by setLayoutParams, as shown below.

LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams();
                layoutParams.leftMargin = getLeft()+officeX;
                layoutParams.topMargin = getTop()+officeY;
                setLayoutParams(layoutParams);
Copy the code

It is important to note that when you getLayoutParams from getLayoutParams(), you need to set the layoutParams to a different type depending on the View and the layout type. Such as the View in the LinearLayout is the LinearLayout. LayoutParams, for example in the RelativeLayout is RelativeLayout. LayoutParams, Otherwise the system won’t be able to get layoutParams.

Through a layoutParams to change the position of a View, change is the View of Margin usually attribute, so in addition to using the layout of the layoutParams properties, also need to ViewGroup. MarginLayoutParams to implement this function.

ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
                layoutParams.leftMargin = getLeft()+officeX;
                layoutParams.topMargin = getTop()+officeY;
                setLayoutParams(layoutParams);
Copy the code

We can see that it’s better to use a ViewGroup, and we don’t care what the parent layout is.

ScrollTo and scrollBy

In a View, the system provides scrollTo and scrollBy to move the position of a View. The difference between the two methods is also easy to understand. To and by, scrollTo(x,y) represents the move to a specific point, and scrollBy(dx,dy) represents the increment of the move.

int officeX = rawX - lastX;
int officeY = rawY - lastY;
scrollBy(officeX,officeY);
Copy the code

But, when we drag the View, you see that the View doesn’t move, are we doing something wrong? The method is correct, and the View is moving, but it’s not moving what we want, it’s just moving the content of the View, so the content of the View is moving, and if you use to and by in a ViewGroup, then all the child views will move, if you use it in a View, So what’s moving is the content of the View, so let’s take an example, TextView, content is its text, ImageView, drawable is its object.

The scrollBy method should be used to move the View in the ViewGroup of the View.

((View)getParent()).scrollBy(officeX,officeY);
Copy the code

But when dragging the View again, you will find that the View while moving, but the touch, it’s not that we want to move with touch point moves, here need to understand the knowledge View moving some of the first, everyone in understanding the problem of time, imagine a mobile phone is a hollow plate, plate, here is a huge canvas Or what we want to display the view, when the plate cover somewhere in the canvas, through the middle of an empty rectangle, we saw the view of mobile phone screen, and other views on the canvas, were flat covered can’t see, our views and the similar example items, we didn’t see the view, but that doesn’t mean it doesn’t exist, Maybe it’s just outside of the screen, but when YOU call scrollBy, you can imagine that the outer cover plate is moving, so it’s kind of abstract, so let’s look at a concrete example.

As you can see, only the middle part of the view is visible, while the other parts are invisible. Set a button in the visible area, and its coordinate is (20.10). Next we use the scrollBy method to move the post-figure.

We can see that although scrollBy(20.10) is set in the positive direction of XY, the visible area of the screen, the Button, is moved in the opposite direction. This is the difference between the reference frame selection and the effect.

Through the above analysis, it can be found that if we set the parameters dx and dy of scrollBy to a positive number, the content will move in the negative direction of the coordinate axis; otherwise, it will move in the positive direction.

int officeX = rawX - lastX;
int officeY = rawY - lastY;
scrollBy(-officeX,-officeY);
Copy the code

Try again, you can find that the effect is the same as the previous methods, similar to the scrollTo we can also achieve.

Scroller

ScrollTo and scrollBy are done in a flash, which is very abrupt, while Scroller can achieve smooth effect. The principle of Scroller is the same as the previous method of using scrollTo and scrollBy. The code is shown below.

  • Initialize the scroller:

First, create a Scroller object using his constructor.

// Initialize the mScroller
mScroller = new Scroller(context);
Copy the code
  • Rewrite computeScroll to implement simulated sliding:

Next, we need to rewrite the computeScroll method, which is the core of using Scroller. When drawing a View, the system will call this method in the onDraw() method, which actually uses the ScrollTo() method combined with the Scroller object. To help get the current scroll value, we can continuously and instantly move a small distance to achieve overall smooth movement. Normally, the computeScroll code can be written using standard methods.

/** * simulates sliding */
@Override
public void computeScroll(a) {
    super.computeScroll();

    // Check whether the Scroller is complete
    if (mScroller.computeScrollOffset()) {
        ((View) getParent()).scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
    }
    // Constantly call computeScroll by redrawing
    invalidate();
}
Copy the code

The Scroller class computeScrollOffset() provides computeScrollOffset(), getCurrX() and getCurrY() to obtain the current slide coordinates. The only thing to note is invalidate(). Because scrollX and scrollY coordinates in the simulation process can only be obtained in computeScroll, but the computeScroll method will not be called automatically and can only be briefly called through invalidate→OnDraw→computeScroll(). This invalidate is required, and when the simulation ends, the computeScrollOffset returns false, thus ending the loop.

  • StartScroll starts the simulation process:

Finally, we need to use the smooth movement event to start the smooth process using the Scroller class’s startScroll() method, which has two overloaded methods.

public void startScroll(int startX, int startY, int dx, int dy, int duration) {}
public void startScroll(int startX, int startY, int dx, int dy) {}
Copy the code

As you can see, the difference between them is that each of them has a specified duration, while each of them has the same duration as in the animation, while the other four coordinates, which are the start and offset, can complete a translation effect through the above steps.

Let’s go back to the example, initialize the Scroller object at the construction minute, and then rewrite the computeScroll method. Finally, we need to listen for the event that the finger leaves the screen, and call startScroll() after the event to complete the translation, so we are in ACTION_UP.

case MotionEvent.ACTION_UP:
    // Handle the exit action of input
    View view = ((View)getParent());
    mScroller.startScroll(view.getScrollX(),view.getScrollY(),view.getScrollX(),view.getScrollY());
    invalidate();
    break;
Copy the code
Attribute animation

This content is more, put later detailed record.

ViewDragHelper

In its support library, Google provides two layouts, DrawerLayout and SlidingPaneLayout, to help developers achieve the side-swipe effect. These two layouts are greatly convenient for us to create our own sliding layouts. However, behind these two powerful layouts, But hidden behind a little-known, but powerful class – ViewDragHelper, ViewDragHelper, can basically achieve a variety of different needs, drag and drop, so this method is the ultimate sliding solution.

Although ViewDragHelper is very powerful, but the use of this chapter is the most complex, we need to understand the basic use of ViewDragHelper, through continuous practice to master it, we here to achieve a QQ slide sidebar layout, we will see how to achieve the specific.

  • Initialize ViewDragHelper:

First, you initialize the ViewDragHelper, which is usually defined in a ViewGroup and initialized by its static methods.

mViewDragHelper = ViewDragHelper.create(this,callback);
Copy the code

His first argument is the View to listen on, and his second argument is a Callback that is the heart of the business.

  • Intercept events:

Next, you override the interception event and pass it to ViewDragHelper for processing.

// Event interception
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    return mViewDragHelper.shouldInterceptTouchEvent(ev);
}
// Touch events
@Override
public boolean onTouchEvent(MotionEvent event) {
    // Pass the touch event to ViewDragHelper
    mViewDragHelper.processTouchEvent(event);
    return true;
}
Copy the code
  • Processing computeScroll () :

Yes, using ViewDragHelper also requires a computeScroll rewrite, because ViewDragHelper is also translated internally by Scroller, so we can use it this way.

@Override
public void computeScroll(a) {
    if(mViewDragHelper.continueSettling(true)) {
        ViewCompat.postInvalidateOnAnimation(this); }}Copy the code
  • Handle callback Cakkback:

We can just come out new.

// side slip callback
private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {
    // When to start touching
    @Override
    public boolean tryCaptureView(View child, int pointerId) {
        // Check if the currently touched child is mMainView
        return mMainView == child;
    }
    // Handle horizontal sliding
    @Override
    public int clampViewPositionHorizontal(View child, int left, int dx) {
        return left;
    }
    // Handle vertical sliding
    @Override
    public int clampViewPositionVertical(View child, int top, int dy) {
        return 0;
    }
    // call after dragging
    @Override
    public void onViewReleased(View releasedChild, float xvel, float yvel) {
        super.onViewReleased(releasedChild, xvel, yvel);
        // Lift the finger and move slowly to the specified position
        if (mMainView.getLeft() < 500) {
            // Close the menu
            mViewDragHelper.smoothSlideViewTo(mMainView, 0.0);
            ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
        } else {
            // Open the menu
            mViewDragHelper.smoothSlideViewTo(mMainView, 300.0);
            ViewCompat.postInvalidateOnAnimation(DragViewGroup.this); }}};Copy the code
  • Define the onFinishInflate() method of the ViewGroup

In the onFinishInflate() method, the child views are defined as MenuView and MainView in sequence, and the width of the View is obtained in the onSizeChanged() method. You can use this value if you slide the View based on its width.

// the XML is loaded after the callback
@Override
protected void onFinishInflate(a) {
    super.onFinishInflate();
    mMenuView = getChildAt(0);
    mMainView = getChildAt(1);
}
// Callback when component size changes
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    mWidth = mMenuView.getMeasuredWidth();
}
Copy the code
  • Finally, the code for sideslipping through the entire ViewDragHelper is as follows:
import android.content.Context;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;

/** * Created by LGL on 16/3/22. */
public class DragViewGroup extends FrameLayout {

    / / slide
    private ViewDragHelper mViewDragHelper;
    private View mMenuView, mMainView;
    private int mWidth;

    public DragViewGroup(Context context) {
        super(context);
        initView();

    }

    public DragViewGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    public DragViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
    }

    // Initialize the data
    private void initView(a) {
        mViewDragHelper = ViewDragHelper.create(this, callback);
    }

    // the XML is loaded after the callback
    @Override
    protected void onFinishInflate(a) {
        super.onFinishInflate();
        mMenuView = getChildAt(0);
        mMainView = getChildAt(1);
    }


    // Callback when component size changes
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = mMenuView.getMeasuredWidth();
    }

    // Event interception
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {

        return mViewDragHelper.shouldInterceptTouchEvent(ev);
    }

    // Touch events
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // Pass the touch event to ViewDragHelper

        mViewDragHelper.processTouchEvent(event);

        return true;
    }

    // side slip callback
    private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {
        // When to start touching
        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            // Check if the currently touched child is mMainView
            return mMainView == child;
        }

        // Handle horizontal sliding
        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            return left;
        }

        // Handle vertical sliding
        @Override
        public int clampViewPositionVertical(View child, int top, int dy) {
            return 0;
        }

        // call after dragging
        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            super.onViewReleased(releasedChild, xvel, yvel);
            // Lift the finger and move slowly to the specified position
            if (mMainView.getLeft() < 500) {
                // Close the menu
                mViewDragHelper.smoothSlideViewTo(mMainView, 0.0);
                ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
            } else {
                // Open the menu
// mViewDragHelper.smoothSlideViewTo(mMainView, 300, 0); // The sidebar width is 300
                mViewDragHelper.smoothSlideViewTo(mMainView, mWidth, 0);  // The sidebar width defines the width for the layout
                ViewCompat.postInvalidateOnAnimation(DragViewGroup.this); }}};@Override
    public void computeScroll(a) {
        if (mViewDragHelper.continueSettling(true)) {
            ViewCompat.postInvalidateOnAnimation(this); }}}Copy the code
  • Finally used in XML layout

      
<com.example.cc.myapplication.DragViewGroup xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:windowSoftInputMode="adjustPan|stateHidden">


    <LinearLayout
        android:layout_width="300dp"
        android:layout_height="match_parent"
        android:background="@android:color/black"
        android:orientation="horizontal">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:textSize="24sp"
            android:textColor="@android:color/white"
            android:text="Sidebar"/>

    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/darker_gray"
        android:orientation="vertical">

        <View
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:textSize="24sp"
            android:textColor="@android:color/white"
            android:text="Main interface"/>

    </LinearLayout>

</com.example.cc.myapplication.DragViewGroup>
Copy the code

This is just a simple simulation of the QQ side slide menu this function. ViewDragHelper also has a lot of power.

  • Other listening events for viewDragHelper. Callback

The system defines a large number of listener events to help us deal with various events. Here are a few.

The event role
onViewCaptured() Callback after the user touches the View.
onViewDragStateChanged() Callback when the drag state changes (idle, draggin, etc.).
onViewPositionChanged() Callbacks when position changes (often used when sliding to change scale for scaling, etc.).