1. 程式人生 > >自定義view系列(5)--99.99%實現QQ側滑刪除效果

自定義view系列(5)--99.99%實現QQ側滑刪除效果

首先宣告本文是基於GitHub上"baoyongzhang"的SwipeMenuListView修改而來,

可以說這個側滑刪除效果是我見過效果最好且比較靈活的專案,沒有之一!!!

但是在使用它之前需要給大家提兩點注意事項:

1,該專案支援Gradle dependence,但是目前作者提供的依賴地址對應的專案不是最新的專案,依賴過後的程式碼與demo中使用的不一致,會提示沒有BaseSwipeListAdapter這個類,因為這個類是其他的開發者後來提交上去的,所以如果想使用最新的程式碼,目前還是得把程式碼下載下來,然後把library檔案拷貝到自己專案中使用.

下圖是目前作者提供的依賴地址,不是最新的,所以想用最新程式碼的朋友還是直接下載程式碼到本地吧.


2,第二點注意事項應該算是一個bug吧,如果你測試過作者給的demo,你會發現如果某一項item已經被拉出來了,這個時候你再把ListView向上或向下滑動,讓這個被拉出來的item移出螢幕,然後再移回來,這個已經被拉出來的item會直接恢復到未拉出的狀態.這會讓使用者感覺很困惑,我明明已經拉出了這個選單,怎麼又不見了,然後可能就會產生這個軟體做的真垃圾的想法,進而可能把你的軟體解除安裝掉.如下圖:


對於上面兩個注意事項,第一個倒是沒什麼好說的,第二個問題怎麼辦呢?別急,這正是我們今天要說的內容.

首先我們可以先研究一下QQ的側滑刪除效果,說到這你可以開啟你的qq看看它的具體效果.

你會發現,如果一個item被拉出來了,當你的手指放到其他的item上時,它會直接先把被拉出的那個item關閉掉,並且當前動作的後續的事件也都不再響應,除非你再次把手指放到螢幕上,他才會響應相關事件,而如果你的手指放到當前被拉出的item上,他不會隱藏這個item,並且可以正常響應左右滑動事件.

ok,QQ的效果我們分析完畢,我們探討一下它的實現原理:

1,如果一個item已經被拉了出來,當你的手指放到其他的item上時,它會直接先把被拉出的那個item關閉掉, 怎麼實現呢?

首先我們需要判斷我們當前所按下的這個item是不是被拉出來的那個item,不是的話,我們才需要關閉,是的話,則不用管.程式碼如下:

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

2,並且當前動作的後續的事件也都不再響應, 怎麼實現呢?

這就很簡單了,根據view的事件分發原理,如果在某一個觸控事件中返回了false,那麼該事件後續的事件都不會再交給他處理,也就是說,如果我們在ACTION_DOWN的時候返回了false,那麼後續的ACTION_MOVE,ACTION_UP等事件都不會響應,所以要實現這個效果,我們只需要在關閉選單的後面,返回false就行了,完整的程式碼如下:

 /********新新增的內容,當按下的item不是當前已經開啟的item,則關閉已經開啟的item,並返回false.不再響應down以後的事件,仿qq效果********/
                    if (view instanceof SwipeMenuLayout) {
                        SwipeMenuLayout touchView = (SwipeMenuLayout) view;
                        if (!touchView.isOpen()) {
                            mTouchView.smoothCloseMenu();
                            return false;
                        }
                    }

這樣的幾行程式碼就實現了剛才分析的qq效果中的前半部分效果,即如果一個item被拉出來了,當你的手指放到其他的item上時,它會直接先把被拉出的那個item關閉掉,並且當前動作的後續的事件也都不再響應,除非你再次把手指放到螢幕上,他才會響應相關事件.

上面的那幾行程式碼是基於SwipeMenuListView類修改而來,完整的修改之後的SwipeMenuListView程式碼如下所示:

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修改於2016-09-07(新增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) {
        //在攔截處處理,在滑動設定了點選事件的地方也能swip,點選時又不能影響原來的點選事件
        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());

                //只在空的時候賦值 以免每次觸控都賦值,會有多個open狀態
                if (view instanceof SwipeMenuLayout) {
                    //如果有打開了 就攔截.
                    if (mTouchView != null && mTouchView.isOpen() && !inRangeOfView(mTouchView.getMenuView(), ev)) {
                        return true;
                    }
                    mTouchView = (SwipeMenuLayout) view;
                    mTouchView.setSwipeDirection(mDirection);
                }
                //如果摸在另外個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) {
                    //每次攔截的down都把觸控狀態設定成了TOUCH_STATE_NONE 只有返回true才會走onTouchEvent 所以寫在這裡就夠了
                    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());
                /*******把這句程式碼提前*********/
                View view = getChildAt(mTouchPosition - getFirstVisiblePosition());

                if (mTouchPosition == oldPos && mTouchView != null
                        && mTouchView.isOpen()) {
                    /********新新增的內容,當按下的item不是當前已經開啟的item,則關閉已經開啟的item,並返回false.不再響應down以後的事件,仿qq效果********/
                    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:
                //有些可能有header,要減去header再判斷
                mTouchPosition = pointToPosition((int) ev.getX(), (int) ev.getY()) - getHeaderViewsCount();
                //如果滑動了一下沒完全展現,就收回去,這時候mTouchView已經賦值,再滑動另外一個不可以swip的view
                //會導致mTouchView swip 。 所以要用位置判斷是否滑動的是一個view
                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;
    }

    /**
     * 判斷點選事件是否在某個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;
    }
}

這樣看來實現qq的那個效果是不是很簡單,不過也多虧了原作者寫的好,我修改起來才更容易.

話說回來,當按下的item不是已經被拉出來的那個item時,相應的效果我們已經實現,如果按下的item正好是那個已經被拉出來的item,效果怎麼實現呢?

其實對於這個效果原作者其實已經實現了,只不過實現的不夠好,存在一些問題,我這裡把這個問題修復了.

存在的問題就是:如果在當前被拉出的item上左右滑動時,當你在擡起手指的那一刻並且滑動方向是向著拉開的方向滑動,有很大機率,這個被拉開的item會被關閉,舉個栗子,比如你設定的是向左滑動是拉開的方向,當你在已經被拉開的item上左右滑動,並且在擡起手指的前一刻你是向左滑動的,講道理的話這個item應該是處於被拉開的狀態,但是實際上有很大機率當你擡起手指時,這個item卻被關閉了,所以這是一個相當影響使用者體驗問題,

一個item的其實就是一個SwipeMenuLayout,在SwipeMenuLayout類中,有一個onSwipe方法,該方法的引數MotionEvent就是從SwipeMenuListView的onTouchEvent方法中傳遞過來的,之前說過,當手指擡起時才會出問題,那麼我們直接就看ACTION_UP當中的實現程式碼,

以下程式碼是有問題的:

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

我們來分析一下,isFliing不用看,問題不是出在這裡,我們看後面的判斷邏輯:X軸方向上滑動距離的絕對值大於選單寬度的一半並且滑動方向是向著拉開的方向滑動時,一個item才會被拉開,不然其他所有情況都會直接進else語句關閉item,問題就是出現在這裡,而上面說的問題也是由這個導致的,試想一種情況,由於當前的item是已經被拉出來過的,如果我們的滑動距離絕對值沒有超過選單寬度的一半但是我們滑動的方向是向著item被拉出來的方向,講道理的話我們的item是應該繼續以被拉出的狀態顯示才對,但是根據程式碼中的判斷邏輯,這種情況是直接進else語句的,也就是直接關閉這個item的,所以這裡的邏輯判斷有大問題,我們要的效果是:既然你已經被拉出來了只要你是繼續往拉出的方向滑動,我就不會進else,而是直接再在if中進行判斷滑動的距離,這樣的邏輯才是正確的,而為了在第一次滑動時,不出現問題,我們還得為第一次滑動做相應操作.

所以修改之後的程式碼邏輯如下所示:

  if ((isFling || Math.signum(mDownX - event.getX()) == mSwipeDirection)) {
                    // open
                    /**************新新增內容****防止在已被拉開的item上向拉開的方向滑動然後擡起手指時,item有很大機率關閉的問題******************/
                    if (Math.abs(mDownX - event.getX()) > (mMenuView.getWidth() / 2)) {
                        smoothOpenMenu();
                    } else {
                        //沒有item開啟時,且滑動距離不滿足開啟的條件才進行關閉
                        if (!isOpen()) {
                            smoothCloseMenu();
                        }
                    }
                    /*******************************************/
                } else {
                    // close
                    smoothCloseMenu();
                    return false;
                }

這樣,我們就完美實現了qq的第二個效果,即如果你的手指放到當前被拉出的item上,他不會隱藏這個item,並且可以正常響應左右滑動事件.

完整的修改之後的SwipeMenuLayout類的程式碼如下所示:

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修改於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
                    /**************新新增內容****防止在已被拉開的item上向拉開的方向滑動然後擡起手指時,item有很大機率關閉的問題******************/
                    if (Math.abs(mDownX - event.getX()) > (mMenuView.getWidth() / 2)) {
                        smoothOpenMenu();
                    } else {
                        //沒有item開啟時,且滑動距離不滿足開啟的條件才進行關閉
                        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;
    }
}

那麼.由於我們的標題名字起的是99.99%實現側滑刪除效果,那麼我們就來看下效果圖,看看到底是不是99.99%的一樣:

QQ效果圖:                                                                                                 修改後的效果圖:

                                             

上面的兩張效果圖可能看不出什麼,特別是我們上面說的那幾個效果,主要是視屏轉成gif之後又卡播放速度又快,所以想比較qq的效果和我們自己的效果,還是下載demo自己比較吧.我只能說改過之後的效果和QQ的效果基本就是一樣的.嘎嘎~~