Android自定義view系列之99.99%實(shí)現(xiàn)QQ側(cè)滑刪除效果實(shí)例代碼詳解
首先聲明本文是基于GitHub上"baoyongzhang"的SwipeMenuListView修改而來,該項(xiàng)目地址:
https://github.com/baoyongzhang/SwipeMenuListView
可以說這個(gè)側(cè)滑刪除效果是我見過效果最好且比較靈活的項(xiàng)目,沒有之一!!!
但是在使用它之前需要給大家提兩點(diǎn)注意事項(xiàng):
1,該項(xiàng)目支持Gradle dependence,但是目前作者提供的依賴地址對(duì)應(yīng)的項(xiàng)目不是最新的項(xiàng)目,依賴過后的代碼與demo中使用的不一致,會(huì)提示沒有BaseSwipeListAdapter這個(gè)類,因?yàn)檫@個(gè)類是其他的開發(fā)者后來提交上去的,所以如果想使用最新的代碼,目前還是得把代碼下載下來,然后把library文件拷貝到自己項(xiàng)目中使用.
下圖是目前作者提供的依賴地址,不是最新的,所以想用最新代碼的朋友還是直接下載代碼到本地吧.

2,第二點(diǎn)注意事項(xiàng)應(yīng)該算是一個(gè)bug吧,如果你測(cè)試過作者給的demo,你會(huì)發(fā)現(xiàn)如果某一項(xiàng)item已經(jīng)被拉出來了,這個(gè)時(shí)候你再把ListView向上或向下滑動(dòng),讓這個(gè)被拉出來的item移出屏幕,然后再移回來,這個(gè)已經(jīng)被拉出來的item會(huì)直接恢復(fù)到未拉出的狀態(tài).這會(huì)讓用戶感覺很困惑,我明明已經(jīng)拉出了這個(gè)菜單,怎么又不見了,然后可能就會(huì)產(chǎn)生這個(gè)軟件做的真垃圾的想法,進(jìn)而可能把你的軟件卸載掉.如下圖:

對(duì)于上面兩個(gè)注意事項(xiàng),第一個(gè)倒是沒什么好說的,第二個(gè)問題怎么辦呢?別急,這正是我們今天要說的內(nèi)容.
首先我們可以先研究一下QQ的側(cè)滑刪除效果,說到這你可以打開你的qq看看它的具體效果.
你會(huì)發(fā)現(xiàn),如果一個(gè)item被拉出來了,當(dāng)你的手指放到其他的item上時(shí),它會(huì)直接先把被拉出的那個(gè)item關(guān)閉掉,并且當(dāng)前動(dòng)作的后續(xù)的事件也都不再響應(yīng),除非你再次把手指放到屏幕上,他才會(huì)響應(yīng)相關(guān)事件,而如果你的手指放到當(dāng)前被拉出的item上,他不會(huì)隱藏這個(gè)item,并且可以正常響應(yīng)左右滑動(dòng)事件.
ok,QQ的效果我們分析完畢,我們探討一下它的實(shí)現(xiàn)原理:
1,如果一個(gè)item已經(jīng)被拉了出來,當(dāng)你的手指放到其他的item上時(shí),它會(huì)直接先把被拉出的那個(gè)item關(guān)閉掉, 怎么實(shí)現(xiàn)呢?
首先我們需要判斷我們當(dāng)前所按下的這個(gè)item是不是被拉出來的那個(gè)item,不是的話,我們才需要關(guān)閉,是的話,則不用管.代碼如下:
if (view instanceof SwipeMenuLayout) {
SwipeMenuLayout touchView = (SwipeMenuLayout) view;
if (!touchView.isOpen()) {
mTouchView.smoothCloseMenu();
}
}
2,并且當(dāng)前動(dòng)作的后續(xù)的事件也都不再響應(yīng), 怎么實(shí)現(xiàn)呢?
這就很簡(jiǎn)單了,根據(jù)view的事件分發(fā)原理,如果在某一個(gè)觸摸事件中返回了false,那么該事件后續(xù)的事件都不會(huì)再交給他處理,也就是說,如果我們?cè)贏CTION_DOWN的時(shí)候返回了false,那么后續(xù)的ACTION_MOVE,ACTION_UP等事件都不會(huì)響應(yīng),所以要實(shí)現(xiàn)這個(gè)效果,我們只需要在關(guān)閉菜單的后面,返回false就行了,完整的代碼如下:
/********新添加的內(nèi)容,當(dāng)按下的item不是當(dāng)前已經(jīng)打開的item,則關(guān)閉已經(jīng)打開的item,并返回false.不再響應(yīng)down以后的事件,仿qq效果********/
if (view instanceof SwipeMenuLayout) {
SwipeMenuLayout touchView = (SwipeMenuLayout) view;
if (!touchView.isOpen()) {
mTouchView.smoothCloseMenu();
return false;
}
}
這樣的幾行代碼就實(shí)現(xiàn)了剛才分析的qq效果中的前半部分效果,即如果一個(gè)item被拉出來了,當(dāng)你的手指放到其他的item上時(shí),它會(huì)直接先把被拉出的那個(gè)item關(guān)閉掉,并且當(dāng)前動(dòng)作的后續(xù)的事件也都不再響應(yīng),除非你再次把手指放到屏幕上,他才會(huì)響應(yīng)相關(guān)事件.
上面的那幾行代碼是基于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) {
//在攔截處處理,在滑動(dòng)設(shè)置了點(diǎn)擊事件的地方也能swip,點(diǎn)擊時(shí)又不能影響原來的點(diǎn)擊事件
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());
//只在空的時(shí)候賦值 以免每次觸摸都賦值,會(huì)有多個(gè)open狀態(tài)
if (view instanceof SwipeMenuLayout) {
//如果有打開了 就攔截.
if (mTouchView != null && mTouchView.isOpen() && !inRangeOfView(mTouchView.getMenuView(), ev)) {
return true;
}
mTouchView = (SwipeMenuLayout) view;
mTouchView.setSwipeDirection(mDirection);
}
//如果摸在另外個(gè)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都把觸摸狀態(tài)設(shè)置成了TOUCH_STATE_NONE 只有返回true才會(huì)走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()) {
/********新添加的內(nèi)容,當(dāng)按下的item不是當(dāng)前已經(jīng)打開的item,則關(guān)閉已經(jīng)打開的item,并返回false.不再響應(yīng)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();
//如果滑動(dòng)了一下沒完全展現(xiàn),就收回去,這時(shí)候mTouchView已經(jīng)賦值,再滑動(dòng)另外一個(gè)不可以swip的view
//會(huì)導(dǎo)致mTouchView swip 。 所以要用位置判斷是否滑動(dòng)的是一個(gè)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;
}
/**
* 判斷點(diǎn)擊事件是否在某個(gè)view內(nèi)
*
* @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;
}
}
這樣看來實(shí)現(xiàn)qq的那個(gè)效果是不是很簡(jiǎn)單,不過也多虧了原作者寫的好,我修改起來才更容易.
話說回來,當(dāng)按下的item不是已經(jīng)被拉出來的那個(gè)item時(shí),相應(yīng)的效果我們已經(jīng)實(shí)現(xiàn),如果按下的item正好是那個(gè)已經(jīng)被拉出來的item,效果怎么實(shí)現(xiàn)呢?
其實(shí)對(duì)于這個(gè)效果原作者其實(shí)已經(jīng)實(shí)現(xiàn)了,只不過實(shí)現(xiàn)的不夠好,存在一些問題,我這里把這個(gè)問題修復(fù)了.
存在的問題就是:如果在當(dāng)前被拉出的item上左右滑動(dòng)時(shí),當(dāng)你在抬起手指的那一刻并且滑動(dòng)方向是向著拉開的方向滑動(dòng),有很大幾率,這個(gè)被拉開的item會(huì)被關(guān)閉,舉個(gè)栗子,比如你設(shè)定的是向左滑動(dòng)是拉開的方向,當(dāng)你在已經(jīng)被拉開的item上左右滑動(dòng),并且在抬起手指的前一刻你是向左滑動(dòng)的,講道理的話這個(gè)item應(yīng)該是處于被拉開的狀態(tài),但是實(shí)際上有很大幾率當(dāng)你抬起手指時(shí),這個(gè)item卻被關(guān)閉了,所以這是一個(gè)相當(dāng)影響用戶體驗(yàn)問題,一個(gè)item的其實(shí)就是一個(gè)SwipeMenuLayout,在SwipeMenuLayout類中,有一個(gè)onSwipe方法,該方法的參數(shù)MotionEvent就是從SwipeMenuListView的onTouchEvent方法中傳遞過來的,之前說過,當(dāng)手指抬起時(shí)才會(huì)出問題,那么我們直接就看ACTION_UP當(dāng)中的實(shí)現(xiàn)代碼,
以下代碼是有問題的:
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軸方向上滑動(dòng)距離的絕對(duì)值大于菜單寬度的一半并且滑動(dòng)方向是向著拉開的方向滑動(dòng)時(shí),一個(gè)item才會(huì)被拉開,不然其他所有情況都會(huì)直接進(jìn)else語句關(guān)閉item,問題就是出現(xiàn)在這里,而上面說的問題也是由這個(gè)導(dǎo)致的,試想一種情況,由于當(dāng)前的item是已經(jīng)被拉出來過的,如果我們的滑動(dòng)距離絕對(duì)值沒有超過菜單寬度的一半但是我們滑動(dòng)的方向是向著item被拉出來的方向,講道理的話我們的item是應(yīng)該繼續(xù)以被拉出的狀態(tài)顯示才對(duì),但是根據(jù)代碼中的判斷邏輯,這種情況是直接進(jìn)else語句的,也就是直接關(guān)閉這個(gè)item的,所以這里的邏輯判斷有大問題,我們要的效果是:既然你已經(jīng)被拉出來了只要你是繼續(xù)往拉出的方向滑動(dòng),我就不會(huì)進(jìn)else,而是直接再在if中進(jìn)行判斷滑動(dòng)的距離,這樣的邏輯才是正確的,而為了在第一次滑動(dòng)時(shí),不出現(xiàn)問題,我們還得為第一次滑動(dòng)做相應(yīng)操作.
所以修改之后的代碼邏輯如下所示:
if ((isFling || Math.signum(mDownX - event.getX()) == mSwipeDirection)) {
// open
/**************新添加內(nèi)容****防止在已被拉開的item上向拉開的方向滑動(dòng)然后抬起手指時(shí),item有很大幾率關(guān)閉的問題******************/
if (Math.abs(mDownX - event.getX()) > (mMenuView.getWidth() / 2)) {
smoothOpenMenu();
} else {
//沒有item打開時(shí),且滑動(dòng)距離不滿足打開的條件才進(jìn)行關(guān)閉
if (!isOpen()) {
smoothCloseMenu();
}
}
/*******************************************/
} else {
// close
smoothCloseMenu();
return false;
}
這樣,我們就完美實(shí)現(xiàn)了qq的第二個(gè)效果,即如果你的手指放到當(dāng)前被拉出的item上,他不會(huì)隱藏這個(gè)item,并且可以正常響應(yīng)左右滑動(dòng)事件.
完整的修改之后的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
/**************新添加內(nèi)容****防止在已被拉開的item上向拉開的方向滑動(dòng)然后抬起手指時(shí),item有很大幾率關(guān)閉的問題******************/
if (Math.abs(mDownX - event.getX()) > (mMenuView.getWidth() / 2)) {
smoothOpenMenu();
} else {
//沒有item打開時(shí),且滑動(dòng)距離不滿足打開的條件才進(jìn)行關(guān)閉
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;
}
}
那么.由于我們的標(biāo)題名字起的是99.99%實(shí)現(xiàn)側(cè)滑刪除效果,那么我們就來看下效果圖,看看到底是不是99.99%的一樣:
QQ效果圖:
修改后的效果圖:

上面的兩張效果圖可能看不出什么,特別是我們上面說的那幾個(gè)效果,主要是視屏轉(zhuǎn)成gif之后又卡播放速度又快,所以想比較qq的效果和我們自己的效果,還是下載demo自己比較吧.我只能說改過之后的效果和QQ的效果基本就是一樣的.嘎嘎~~
- android ItemTouchHelper實(shí)現(xiàn)可拖拽和側(cè)滑的列表的示例代碼
- Android高仿QQ6.0側(cè)滑刪除實(shí)例代碼
- Android仿QQ微信側(cè)滑刪除效果
- Android開發(fā)中記一個(gè)SwipeMenuListView側(cè)滑刪除錯(cuò)亂的Bug
- Android recyclerview實(shí)現(xiàn)拖拽排序和側(cè)滑刪除
- Android 模仿QQ側(cè)滑刪除ListView功能示例
- android的RecyclerView實(shí)現(xiàn)拖拽排序和側(cè)滑刪除示例
- android ListView和GridView拖拽移位實(shí)現(xiàn)代碼
- android 大圖片拖拽并縮放實(shí)現(xiàn)原理
- Android使用ItemTouchHelper實(shí)現(xiàn)側(cè)滑刪除和拖拽
相關(guān)文章
利用SurfaceView實(shí)現(xiàn)下雨與下雪動(dòng)畫效果詳解(Kotlin語法)
這篇文章主要給大家介紹了關(guān)于利用SurfaceView實(shí)現(xiàn)下雨與下雪動(dòng)畫效果的相關(guān)資料,需要一些基本的View知識(shí)和會(huì)一些基礎(chǔ)Kotlin語法,文中給出了詳細(xì)的示例代碼供大家參考學(xué)習(xí),需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-09-09
Android自定義View實(shí)現(xiàn)字母導(dǎo)航欄
通常手機(jī)通訊錄都會(huì)有索引欄,這篇文章主要介紹了Android自定義View實(shí)現(xiàn)字母導(dǎo)航欄,現(xiàn)在分享給大家。2016-10-10
Android多線程處理機(jī)制中的Handler使用介紹
本文將為大家介紹下Android的Handler的使用方法,Handler可以發(fā)送Messsage和Runnable對(duì)象到與其相關(guān)聯(lián)的線程的消息隊(duì)列,感興趣的朋友可以了解下哈2013-06-06
Android實(shí)現(xiàn)簡(jiǎn)單計(jì)算器
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)簡(jiǎn)單計(jì)算器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-10-10
android基于socket的局域網(wǎng)內(nèi)服務(wù)器與客戶端加密通信
本篇文章主要介紹了android基于socket的局域網(wǎng)內(nèi)服務(wù)器與客戶端加密通信,這里整理了詳細(xì)的代碼,有需要的小伙伴可以參考下。2017-04-04
Android軟鍵盤的顯示隱藏功能實(shí)現(xiàn)過程
這篇文章主要介紹了Android軟鍵盤的顯示隱藏功能,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-03-03
Android編程實(shí)現(xiàn)自定義系統(tǒng)菜單背景的方法
這篇文章主要介紹了Android編程實(shí)現(xiàn)自定義系統(tǒng)菜單背景的方法,涉及Android菜單menu的實(shí)現(xiàn)及背景圖片的相關(guān)操作技巧,需要的朋友可以參考下2016-01-01
Android 三種延遲操作的實(shí)現(xiàn)方法
這篇文章主要介紹了Android 延遲操作的實(shí)現(xiàn)方法的相關(guān)資料,這里提供了三種實(shí)現(xiàn)方法,希望能幫助到大家,需要的朋友可以參考下2017-08-08
Android編程之殺毒的實(shí)現(xiàn)原理及具體實(shí)例
這篇文章主要介紹了Android編程之殺毒的實(shí)現(xiàn)原理及具體實(shí)例,結(jié)合實(shí)例形式分析了Android殺毒功能的原理與簡(jiǎn)單實(shí)現(xiàn)技巧,需要的朋友可以參考下2015-12-12

