Custom view Series (5) -- 99.99% to achieve QQ sideslip deletion effect

First of all, it is stated that this article is based on the modification of Swipe MenuListView of "baoyongzhang" on GitHub.

The project address: https://github.com/baoyongzhang/SwipeMenuListView

It can be said that this sideslip deletion effect is the best and more flexible project I have ever seen, none of them!!!

But before using it, we need to give you two points to note:

1. This project supports Gradle dependence, but the project corresponding to the dependency address provided by the author is not the latest project. Depending on the inconsistent code used in demo, it will be prompted that there is no BaseSwipeListAdapter class, because this class was submitted later by other developers, so if you want to use the latest code, you still have to download the code at present, and then Li. Copy the brary file to use in your own project.

The following figure is the current dependency address provided by the author, not the latest, so friends who want to use the latest code should download the code directly to the local bar.


2. The second thing to note should be a bug. If you have tested the demo given by the author, you will find that if an item has been pulled out, then you will slide ListView up or down to move the pulled item out of the screen, and then move it back. The pulled item will directly return to its unplugged state. This will make the user feel confused, me. It's obvious that the menu has been pulled out, but it's gone. Then it may generate the idea that the software is really garbage, and then it may uninstall your software. Here's the picture:



For the two precautions above, the first one is nothing to say. What about the second one? Don't worry, that's what we're going to talk about today.

First, we can study the sideslip deletion effect of QQ, and then you can open your QQ to see its specific effect.

You will find that if an item is pulled out, when your finger is placed on another item, it will close the pulled item directly first, and the subsequent events of the current action will no longer respond, unless you put your finger on the screen again, he will respond to the relevant events, and if you put your finger on the current pulled item, he will not hide the item, and Sliding events are normally responded to.

OK, the effect of QQ has been analyzed. We will discuss its implementation principle.

1. If an item has been pulled out, when you put your finger on another item, it will turn off the pulled item directly first. How to achieve this?

First of all, we need to determine whether the item we are currently pressing is the item pulled out. If not, we need to close it. If so, we don't need to care. The code is as follows:

 if (view instanceof SwipeMenuLayout) {
                        SwipeMenuLayout touchView = (SwipeMenuLayout) view;
                        if (!touchView.isOpen()) {
                            mTouchView.smoothCloseMenu();
                        }
                    }

2, and the subsequent events of the current action are no longer responding, how to achieve it?

This is very simple. According to the event distribution principle of view, if false is returned in a touch event, the subsequent events of the event will not be handled by him. That is to say, if false is returned in ACTION_DOWN, subsequent events such as ACTION_MOVE,ACTION_UP will not respond, so to achieve this effect, we only need to close the back of the menu. Just return to false. The complete code is as follows:

 /********When the item is not currently open, it closes the open item and returns false. It no longer responds to events after down load, and imitates the qq effect **********/
                    if (view instanceof SwipeMenuLayout) {
                        SwipeMenuLayout touchView = (SwipeMenuLayout) view;
                        if (!touchView.isOpen()) {
                            mTouchView.smoothCloseMenu();
                            return false;
                        }
                    }

Such a few lines of code achieve the first half of the qq effect just analyzed, that is, if an item is pulled out, when your finger is placed on other items, it will directly turn off the pulled item first, and the subsequent events of the current action will no longer respond, unless you put your finger on the screen again, he will respond to the relevant events.

The above lines of code are modified based on the SwipeMenuListView class. The complete modified SwipeMenuListView code is as follows:

package com.lanma.swipemenulistviewdemo.swipemenulistview;

import android.content.Context;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Interpolator;
import android.widget.ListAdapter;
import android.widget.ListView;

/**
 * @author baoyz
 * @date 2014-8-18
 * qiang_xi Modified in 2016-09-07 (effect of new qq)
 */
public class SwipeMenuListView extends ListView {

    private static final int TOUCH_STATE_NONE = 0;
    private static final int TOUCH_STATE_X = 1;
    private static final int TOUCH_STATE_Y = 2;

    public static final int DIRECTION_LEFT = 1;
    public static final int DIRECTION_RIGHT = -1;
    private int mDirection = 1;//swipe from right to left by default

    private int MAX_Y = 5;
    private int MAX_X = 3;
    private float mDownX;
    private float mDownY;
    private int mTouchState;
    private int mTouchPosition;
    private SwipeMenuLayout mTouchView;
    private OnSwipeListener mOnSwipeListener;

    private SwipeMenuCreator mMenuCreator;
    private OnMenuItemClickListener mOnMenuItemClickListener;
    private OnMenuStateChangeListener mOnMenuStateChangeListener;
    private Interpolator mCloseInterpolator;
    private Interpolator mOpenInterpolator;

    public SwipeMenuListView(Context context) {
        super(context);
        init();
    }

    public SwipeMenuListView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    public SwipeMenuListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        MAX_X = dp2px(MAX_X);
        MAX_Y = dp2px(MAX_Y);
        mTouchState = TOUCH_STATE_NONE;
    }

    @Override
    public void setAdapter(ListAdapter adapter) {
        super.setAdapter(new SwipeMenuAdapter(getContext(), adapter) {
            @Override
            public void createMenu(SwipeMenu menu) {
                if (mMenuCreator != null) {
                    mMenuCreator.create(menu);
                }
            }

            @Override
            public void onItemClick(SwipeMenuView view, SwipeMenu menu,
                                    int index) {
                boolean flag = false;
                if (mOnMenuItemClickListener != null) {
                    flag = mOnMenuItemClickListener.onMenuItemClick(
                            view.getPosition(), menu, index);
                }
                if (mTouchView != null && !flag) {
                    mTouchView.smoothCloseMenu();
                }
            }
        });
    }

    public void setCloseInterpolator(Interpolator interpolator) {
        mCloseInterpolator = interpolator;
    }

    public void setOpenInterpolator(Interpolator interpolator) {
        mOpenInterpolator = interpolator;
    }

    public Interpolator getOpenInterpolator() {
        return mOpenInterpolator;
    }

    public Interpolator getCloseInterpolator() {
        return mCloseInterpolator;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        //Processing at the interception, swip can also be used where click events are set in sliding, and click can not affect the original click events.
        int action = ev.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mDownX = ev.getX();
                mDownY = ev.getY();
                boolean handled = super.onInterceptTouchEvent(ev);
                mTouchState = TOUCH_STATE_NONE;
                mTouchPosition = pointToPosition((int) ev.getX(), (int) ev.getY());
                View view = getChildAt(mTouchPosition - getFirstVisiblePosition());

                //Assign values only when they are empty so that they are not assigned to each touch. There will be multiple open states.
                if (view instanceof SwipeMenuLayout) {
                    //If it's open, intercept it.
                    if (mTouchView != null && mTouchView.isOpen() && !inRangeOfView(mTouchView.getMenuView(), ev)) {
                        return true;
                    }
                    mTouchView = (SwipeMenuLayout) view;
                    mTouchView.setSwipeDirection(mDirection);
                }
                //If you touch it in another view
                if (mTouchView != null && mTouchView.isOpen() && view != mTouchView) {
                    handled = true;
                }

                if (mTouchView != null) {
                    mTouchView.onSwipe(ev);
                }
                return handled;
            case MotionEvent.ACTION_MOVE:
                float dy = Math.abs((ev.getY() - mDownY));
                float dx = Math.abs((ev.getX() - mDownX));
                if (Math.abs(dy) > MAX_Y || Math.abs(dx) > MAX_X) {
                    //Each intercepted down sets the touch state to TOUCH_STATE_NONE and only returns true does onTouchEvent go, so it's enough to write here.
                    if (mTouchState == TOUCH_STATE_NONE) {
                        if (Math.abs(dy) > MAX_Y) {
                            mTouchState = TOUCH_STATE_Y;
                        } else if (dx > MAX_X) {
                            mTouchState = TOUCH_STATE_X;
                            if (mOnSwipeListener != null) {
                                mOnSwipeListener.onSwipeStart(mTouchPosition);
                            }
                        }
                    }
                    return true;
                }
        }
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (ev.getAction() != MotionEvent.ACTION_DOWN && mTouchView == null)
            return super.onTouchEvent(ev);
        int action = ev.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                int oldPos = mTouchPosition;
                mDownX = ev.getX();
                mDownY = ev.getY();
                mTouchState = TOUCH_STATE_NONE;

                mTouchPosition = pointToPosition((int) ev.getX(), (int) ev.getY());
                /*******Put this code ahead of time ***********/
                View view = getChildAt(mTouchPosition - getFirstVisiblePosition());

                if (mTouchPosition == oldPos && mTouchView != null
                        && mTouchView.isOpen()) {
                    /********When the item is not currently open, it closes the open item and returns false. It no longer responds to events after down load, and imitates the qq effect **********/
                    if (view instanceof SwipeMenuLayout) {
                        SwipeMenuLayout touchView = (SwipeMenuLayout) view;
                        if (!touchView.isOpen()) {
                            mTouchView.smoothCloseMenu();
                            return false;
                        }
                    }
                    /***************************/
                    mTouchState = TOUCH_STATE_X;
                    mTouchView.onSwipe(ev);
                    return true;
                }
                if (mTouchView != null && mTouchView.isOpen()) {
                    mTouchView.smoothCloseMenu();
                    mTouchView = null;
                    // return super.onTouchEvent(ev);
                    // try to cancel the touch event
                    MotionEvent cancelEvent = MotionEvent.obtain(ev);
                    cancelEvent.setAction(MotionEvent.ACTION_CANCEL);
                    onTouchEvent(cancelEvent);
                    if (mOnMenuStateChangeListener != null) {
                        mOnMenuStateChangeListener.onMenuClose(oldPos);
                    }
                    return true;
                }
                if (view instanceof SwipeMenuLayout) {
                    mTouchView = (SwipeMenuLayout) view;
                    mTouchView.setSwipeDirection(mDirection);
                }
                if (mTouchView != null) {
                    mTouchView.onSwipe(ev);
                }
                break;
            case MotionEvent.ACTION_MOVE:
                //Some may have headers, so you need to subtract the header to make a judgment.
                mTouchPosition = pointToPosition((int) ev.getX(), (int) ev.getY()) - getHeaderViewsCount();
                //If the slider does not fully display, it will be retracted. At this time, mTouchView has been assigned, and then another view that can not be swip ped is slided.
                //Causes mTouchView swip. So it's a view to determine whether it's sliding or not by position.
                if (!mTouchView.getSwipEnable() || mTouchPosition != mTouchView.getPosition()) {
                    break;
                }
                float dy = Math.abs((ev.getY() - mDownY));
                float dx = Math.abs((ev.getX() - mDownX));
                if (mTouchState == TOUCH_STATE_X) {
                    if (mTouchView != null) {
                        mTouchView.onSwipe(ev);
                    }
                    getSelector().setState(new int[]{0});
                    ev.setAction(MotionEvent.ACTION_CANCEL);
                    super.onTouchEvent(ev);
                    return true;
                } else if (mTouchState == TOUCH_STATE_NONE) {
                    if (Math.abs(dy) > MAX_Y) {
                        mTouchState = TOUCH_STATE_Y;
                    } else if (dx > MAX_X) {
                        mTouchState = TOUCH_STATE_X;
                        if (mOnSwipeListener != null) {
                            mOnSwipeListener.onSwipeStart(mTouchPosition);
                        }
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                if (mTouchState == TOUCH_STATE_X) {
                    if (mTouchView != null) {
                        boolean isBeforeOpen = mTouchView.isOpen();
                        mTouchView.onSwipe(ev);
                        boolean isAfterOpen = mTouchView.isOpen();
                        if (isBeforeOpen != isAfterOpen && mOnMenuStateChangeListener != null) {
                            if (isAfterOpen) {
                                mOnMenuStateChangeListener.onMenuOpen(mTouchPosition);
                            } else {
                                mOnMenuStateChangeListener.onMenuClose(mTouchPosition);
                            }
                        }
                        if (!isAfterOpen) {
                            mTouchPosition = -1;
                            mTouchView = null;
                        }
                    }
                    if (mOnSwipeListener != null) {
                        mOnSwipeListener.onSwipeEnd(mTouchPosition);
                    }
                    ev.setAction(MotionEvent.ACTION_CANCEL);
                    super.onTouchEvent(ev);
                    return true;
                }
                break;
        }
        return super.onTouchEvent(ev);
    }

    public void smoothOpenMenu(int position) {
        if (position >= getFirstVisiblePosition()
                && position <= getLastVisiblePosition()) {
            View view = getChildAt(position - getFirstVisiblePosition());
            if (view instanceof SwipeMenuLayout) {
                mTouchPosition = position;
                if (mTouchView != null && mTouchView.isOpen()) {
                    mTouchView.smoothCloseMenu();
                }
                mTouchView = (SwipeMenuLayout) view;
                mTouchView.setSwipeDirection(mDirection);
                mTouchView.smoothOpenMenu();
            }
        }
    }

    public void smoothCloseMenu() {
        if (mTouchView != null && mTouchView.isOpen()) {
            mTouchView.smoothCloseMenu();
        }
    }

    private int dp2px(int dp) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
                getContext().getResources().getDisplayMetrics());
    }

    public void setMenuCreator(SwipeMenuCreator menuCreator) {
        this.mMenuCreator = menuCreator;
    }

    public void setOnMenuItemClickListener(
            OnMenuItemClickListener onMenuItemClickListener) {
        this.mOnMenuItemClickListener = onMenuItemClickListener;
    }

    public void setOnSwipeListener(OnSwipeListener onSwipeListener) {
        this.mOnSwipeListener = onSwipeListener;
    }

    public void setOnMenuStateChangeListener(OnMenuStateChangeListener onMenuStateChangeListener) {
        mOnMenuStateChangeListener = onMenuStateChangeListener;
    }

    public static interface OnMenuItemClickListener {
        boolean onMenuItemClick(int position, SwipeMenu menu, int index);
    }

    public static interface OnSwipeListener {
        void onSwipeStart(int position);

        void onSwipeEnd(int position);
    }

    public static interface OnMenuStateChangeListener {
        void onMenuOpen(int position);

        void onMenuClose(int position);
    }

    public void setSwipeDirection(int direction) {
        mDirection = direction;
    }

    /**
     * Determine whether the click event is in a view
     *
     * @param view
     * @param ev
     * @return
     */
    public static boolean inRangeOfView(View view, MotionEvent ev) {
        int[] location = new int[2];
        view.getLocationOnScreen(location);
        int x = location[0];
        int y = location[1];
        if (ev.getRawX() < x || ev.getRawX() > (x + view.getWidth()) || ev.getRawY() < y || ev.getRawY() > (y + view.getHeight())) {
            return false;
        }
        return true;
    }
}

So it seems that the effect of implementing qq is very simple, but thanks to the original author's good writing, it is easier for me to modify it.

In other words, when the item pressed is not the item that has been pulled out, the corresponding effect has been achieved. If the item pressed is the item that has been pulled out, how can the effect be achieved?

In fact, the original author has actually achieved this effect, but the implementation is not good enough, there are some problems, I have fixed this problem here.

The problem is that if you slide left and right on the currently pulled item, there is a high probability that the pulled item will be closed when you lift your finger and the sliding direction is toward the pulled direction. Lift a chestnut, for example, if you set the pulled direction by sliding left, when you slide left and right on the pulled item, and in the front of the lifted finger. You're sliding to the left, and reasonably the item should be pulled apart, but there's a good chance that when you raise your finger, the item will be turned off, so it's a very user experience problem.

An item is actually a SwipeMenuLayout. In the SwipeMenuLayout class, there is an onSwipe method. The parameter MotionEvent of this method is passed from the onTouchEvent method of SwipeMenuListView. As mentioned before, when the finger is raised, the problem will arise. So we can see the implementation code in ACTION_UP directly.

The following code is problematic:

if ((isFling || Math.abs(mDownX - event.getX()) > (mMenuView.getWidth() / 2)) &&
					Math.signum(mDownX - event.getX()) == mSwipeDirection) {
				// open
				smoothOpenMenu();
			} else {
				// close
				smoothCloseMenu();
				return false;
			}

Let's analyse it. isFliing doesn't need to look. The problem is not here. Let's look at the logic behind it: When the absolute value of sliding distance in the X-axis direction is greater than half the width of the menu and sliding direction is sliding in the direction of pulling, an item will be pulled apart. Otherwise, all other cases will go directly into the else statement to close the item. The problem appears here. It's also caused by this. Imagine a situation where the current item has been pulled out. If our absolute sliding distance is not more than half the width of the menu, but our sliding direction is toward the pulled out direction of the item, reasonably, our item should continue to be displayed in the pulled out state, but according to the judgment logic in the code, this is the case. It goes directly into the else statement, that is to say, closes the item directly, so there is a big problem in the logic judgment here. The effect we want is: since you have been pulled out, as long as you continue to slide in the pull-out direction, I will not go into the else, but directly judge the distance of the slide in if. This logic is correct, and in order to avoid the problem in the first slide, I will. They have to do the same for the first slide.

So the modified code logic is as follows:

  if ((isFling || Math.signum(mDownX - event.getX()) == mSwipeDirection)) {
                    // open
                    /**************Adding new content **** prevents item from sliding in the pulled direction and lifting its finger, which has a high probability of closing the problem ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ****/
                    if (Math.abs(mDownX - event.getX()) > (mMenuView.getWidth() / 2)) {
                        smoothOpenMenu();
                    } else {
                        //When item is not opened, and the sliding distance does not satisfy the condition of opening, it is closed.
                        if (!isOpen()) {
                            smoothCloseMenu();
                        }
                    }
                    /*******************************************/
                } else {
                    // close
                    smoothCloseMenu();
                    return false;
                }

In this way, we have perfectly achieved the second effect of qq, that is, if your finger is placed on the currently pulled item, it will not hide the item, and can respond to the sliding events.

The code for the SwipeMenuLayout class after the complete modification is as follows:

package com.lanma.swipemenulistviewdemo.swipemenulistview;

import android.content.Context;
import android.support.v4.view.GestureDetectorCompat;
import android.support.v4.widget.ScrollerCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.GestureDetector.OnGestureListener;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Interpolator;
import android.widget.AbsListView;
import android.widget.FrameLayout;

/**
 * @author baoyz
 * @date 2014-8-23
 * qiang_xi Modified in 2016-09-07
 */
public class SwipeMenuLayout extends FrameLayout {

    private static final int CONTENT_VIEW_ID = 1;
    private static final int MENU_VIEW_ID = 2;

    private static final int STATE_CLOSE = 0;
    private static final int STATE_OPEN = 1;

    private int mSwipeDirection;

    private View mContentView;
    private SwipeMenuView mMenuView;
    private int mDownX;
    private int state = STATE_CLOSE;
    private GestureDetectorCompat mGestureDetector;
    private OnGestureListener mGestureListener;
    private boolean isFling;
    private int MIN_FLING = dp2px(15);
    private int MAX_VELOCITYX = -dp2px(500);
    private ScrollerCompat mOpenScroller;
    private ScrollerCompat mCloseScroller;
    private int mBaseX;
    private int position;
    private Interpolator mCloseInterpolator;
    private Interpolator mOpenInterpolator;

    private boolean mSwipEnable = true;

    public SwipeMenuLayout(View contentView, SwipeMenuView menuView) {
        this(contentView, menuView, null, null);
    }

    public SwipeMenuLayout(View contentView, SwipeMenuView menuView,
                           Interpolator closeInterpolator, Interpolator openInterpolator) {
        super(contentView.getContext());
        mCloseInterpolator = closeInterpolator;
        mOpenInterpolator = openInterpolator;
        mContentView = contentView;
        mMenuView = menuView;
        mMenuView.setLayout(this);
        init();
    }

    // private SwipeMenuLayout(Context context, AttributeSet attrs, int
    // defStyle) {
    // super(context, attrs, defStyle);
    // }

    private SwipeMenuLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    private SwipeMenuLayout(Context context) {
        super(context);
    }

    public int getPosition() {
        return position;
    }

    public void setPosition(int position) {
        this.position = position;
        mMenuView.setPosition(position);
    }

    public void setSwipeDirection(int swipeDirection) {
        mSwipeDirection = swipeDirection;
    }

    private void init() {
        setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT,
                LayoutParams.WRAP_CONTENT));
        mGestureListener = new SimpleOnGestureListener() {
            @Override
            public boolean onDown(MotionEvent e) {
                isFling = false;
                return true;
            }

            @Override
            public boolean onFling(MotionEvent e1, MotionEvent e2,
                                   float velocityX, float velocityY) {
                // TODO
                if (Math.abs(e1.getX() - e2.getX()) > MIN_FLING
                        && velocityX < MAX_VELOCITYX) {
                    isFling = true;
                }
                // Log.i("byz", MAX_VELOCITYX + ", velocityX = " + velocityX);
                return super.onFling(e1, e2, velocityX, velocityY);
            }
        };
        mGestureDetector = new GestureDetectorCompat(getContext(),
                mGestureListener);

        // mScroller = ScrollerCompat.create(getContext(), new
        // BounceInterpolator());
        if (mCloseInterpolator != null) {
            mCloseScroller = ScrollerCompat.create(getContext(),
                    mCloseInterpolator);
        } else {
            mCloseScroller = ScrollerCompat.create(getContext());
        }
        if (mOpenInterpolator != null) {
            mOpenScroller = ScrollerCompat.create(getContext(),
                    mOpenInterpolator);
        } else {
            mOpenScroller = ScrollerCompat.create(getContext());
        }

        LayoutParams contentParams = new LayoutParams(
                LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
        mContentView.setLayoutParams(contentParams);
        if (mContentView.getId() < 1) {
            mContentView.setId(CONTENT_VIEW_ID);
        }

        mMenuView.setId(MENU_VIEW_ID);
        mMenuView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
                LayoutParams.WRAP_CONTENT));

        addView(mContentView);
        addView(mMenuView);

        // if (mContentView.getBackground() == null) {
        // mContentView.setBackgroundColor(Color.WHITE);
        // }

        // in android 2.x, MenuView height is MATCH_PARENT is not work.
        // getViewTreeObserver().addOnGlobalLayoutListener(
        // new OnGlobalLayoutListener() {
        // @Override
        // public void onGlobalLayout() {
        // setMenuHeight(mContentView.getHeight());
        // // getViewTreeObserver()
        // // .removeGlobalOnLayoutListener(this);
        // }
        // });

    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
    }

    public boolean onSwipe(MotionEvent event) {
        mGestureDetector.onTouchEvent(event);
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mDownX = (int) event.getX();
                isFling = false;
                break;
            case MotionEvent.ACTION_MOVE:
                // Log.i("byz", "downX = " + mDownX + ", moveX = " + event.getX());
                int dis = (int) (mDownX - event.getX());
                if (state == STATE_OPEN) {
                    dis += mMenuView.getWidth() * mSwipeDirection;
                }
                swipe(dis);
                break;
            case MotionEvent.ACTION_UP:
                if ((isFling || Math.signum(mDownX - event.getX()) == mSwipeDirection)) {
                    // open
                    /**************Adding new content **** prevents item from sliding in the pulled direction and lifting its finger, which has a high probability of closing the problem ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ****/
                    if (Math.abs(mDownX - event.getX()) > (mMenuView.getWidth() / 2)) {
                        smoothOpenMenu();
                    } else {
                        //When item is not opened, and the sliding distance does not satisfy the condition of opening, it is closed.
                        if (!isOpen()) {
                            smoothCloseMenu();
                        }
                    }
                    /*******************************************/
                } else {
                    // close
                    smoothCloseMenu();
                    return false;
                }
                break;
        }
        return true;
    }

    public boolean isOpen() {
        return state == STATE_OPEN;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return super.onTouchEvent(event);
    }

    private void swipe(int dis) {
        if (!mSwipEnable) {
            return;
        }
        if (Math.signum(dis) != mSwipeDirection) {
            dis = 0;
        } else if (Math.abs(dis) > mMenuView.getWidth()) {
            dis = mMenuView.getWidth() * mSwipeDirection;
        }

        mContentView.layout(-dis, mContentView.getTop(),
                mContentView.getWidth() - dis, getMeasuredHeight());

        if (mSwipeDirection == SwipeMenuListView.DIRECTION_LEFT) {

            mMenuView.layout(mContentView.getWidth() - dis, mMenuView.getTop(),
                    mContentView.getWidth() + mMenuView.getWidth() - dis,
                    mMenuView.getBottom());
        } else {
            mMenuView.layout(-mMenuView.getWidth() - dis, mMenuView.getTop(),
                    -dis, mMenuView.getBottom());
        }
    }

    @Override
    public void computeScroll() {
        if (state == STATE_OPEN) {
            if (mOpenScroller.computeScrollOffset()) {
                swipe(mOpenScroller.getCurrX() * mSwipeDirection);
                postInvalidate();
            }
        } else {
            if (mCloseScroller.computeScrollOffset()) {
                swipe((mBaseX - mCloseScroller.getCurrX()) * mSwipeDirection);
                postInvalidate();
            }
        }
    }

    public void smoothCloseMenu() {
        state = STATE_CLOSE;
        if (mSwipeDirection == SwipeMenuListView.DIRECTION_LEFT) {
            mBaseX = -mContentView.getLeft();
            mCloseScroller.startScroll(0, 0, mMenuView.getWidth(), 0, 350);
        } else {
            mBaseX = mMenuView.getRight();
            mCloseScroller.startScroll(0, 0, mMenuView.getWidth(), 0, 350);
        }
        postInvalidate();
    }

    public void smoothOpenMenu() {
        if (!mSwipEnable) {
            return;
        }
        state = STATE_OPEN;
        if (mSwipeDirection == SwipeMenuListView.DIRECTION_LEFT) {
            mOpenScroller.startScroll(-mContentView.getLeft(), 0, mMenuView.getWidth(), 0, 350);
        } else {
            mOpenScroller.startScroll(mContentView.getLeft(), 0, mMenuView.getWidth(), 0, 350);
        }
        postInvalidate();
    }

    public void closeMenu() {
        if (mCloseScroller.computeScrollOffset()) {
            mCloseScroller.abortAnimation();
        }
        if (state == STATE_OPEN) {
            state = STATE_CLOSE;
            swipe(0);
        }
    }

    public void openMenu() {
        if (!mSwipEnable) {
            return;
        }
        if (state == STATE_CLOSE) {
            state = STATE_OPEN;
            swipe(mMenuView.getWidth() * mSwipeDirection);
        }
    }

    public View getContentView() {
        return mContentView;
    }

    public SwipeMenuView getMenuView() {
        return mMenuView;
    }

    private int dp2px(int dp) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
                getContext().getResources().getDisplayMetrics());
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mMenuView.measure(MeasureSpec.makeMeasureSpec(0,
                MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(
                getMeasuredHeight(), MeasureSpec.EXACTLY));
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        mContentView.layout(0, 0, getMeasuredWidth(),
                mContentView.getMeasuredHeight());
        if (mSwipeDirection == SwipeMenuListView.DIRECTION_LEFT) {
            mMenuView.layout(getMeasuredWidth(), 0,
                    getMeasuredWidth() + mMenuView.getMeasuredWidth(),
                    mContentView.getMeasuredHeight());
        } else {
            mMenuView.layout(-mMenuView.getMeasuredWidth(), 0,
                    0, mContentView.getMeasuredHeight());
        }
    }

    public void setMenuHeight(int measuredHeight) {
        Log.i("byz", "pos = " + position + ", height = " + measuredHeight);
        LayoutParams params = (LayoutParams) mMenuView.getLayoutParams();
        if (params.height != measuredHeight) {
            params.height = measuredHeight;
            mMenuView.setLayoutParams(mMenuView.getLayoutParams());
        }
    }

    public void setSwipEnable(boolean swipEnable) {
        mSwipEnable = swipEnable;
    }

    public boolean getSwipEnable() {
        return mSwipEnable;
    }
}

So, since our title name is 99.99% to achieve the sideslip deletion effect, let's take a look at the effect map and see if it's 99.99% the same:

QQ rendering: Modified rendering:

                                             


The above two effect maps may not see anything, especially the effects we mentioned above. The main reason is that when the video screen is converted to gif, the card plays faster. So to compare the effect of QQ with that of our own, we should download demo ourselves. I can only say that the effect after the modification is basically the same as that of QQ.~~

Download address (which already contains the modified library file), you need to copy and paste directly. http://download.csdn.net/detail/qiang_xi/9624250  Resource download requires 1 point

Keywords: Android github Gradle

Added by msarefin on Wed, 29 May 2019 22:57:31 +0300