仿QQ侧滑删除Item效果Demo

时间:2021-07-03 10:52:56

Demo 传送门

关于仿QQ侧滑删除Item的代码,还是有许多实现的方式的,就比如上篇博客写的利用HorizontalScrollView嵌套来实现,但是体验效果却并不是特别的理想。之后也尝试了另外的一种方式来达到这种侧滑删除的效果,特别的理想,现在就把代码粘贴上来。


接下是来实现效果的几个步骤:
1.新建一个项目MainActivity类文件以及activity_main.xml布局文件,代码如下:

MainActivity类文件

package com.lw.widget.slideitem;

import android.content.Context;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

Context context;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

RecyclerView recyclerView = (RecyclerView) findViewById(R.id.rv_data_list);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.addItemDecoration(new LinearItemDecoration()); //设置分割线样式
SlideItemAdapter slideitemadapter = new SlideItemAdapter(context);
recyclerView.setAdapter(slideitemadapter);
slideitemadapter.setItemClickListener(new ItemClickListener() {
@Override
public void onItemClick(View view, int postion) {

Toast.makeText(getApplication(),"商品"+(postion+1),Toast.LENGTH_LONG).show();

}
});

}
}

activity_main.xml布局文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.lw.widget.slideitem.MainActivity"
android:background="#f6f6f6">


<android.support.v7.widget.RecyclerView

android:id="@+id/rv_data_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
>


</android.support.v7.widget.RecyclerView>

</RelativeLayout>

2.根据下边的这张图,创建关键类及接口:

仿QQ侧滑删除Item效果Demo

SlideItemAdapter 适配器

package com.lw.widget.slideitem;

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.List;

/**
* Created by Administrator on 2016-07-05.
*/


public class SlideItemAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements ItemSlideHelper.Callback{

private RecyclerView mRecyclerView;
private List<Integer> colors;
private Context mContext;
private ItemClickListener mItemClickListener;

public void setItemClickListener(ItemClickListener mItemClickListener) {

this.mItemClickListener = mItemClickListener;
}



public SlideItemAdapter(Context context) {

// 初始化
mContext = context;

// 填充list的内容模拟数据,否则应该异步执行
colors = new ArrayList<Integer>();
for (int i = 0; i < 15; i++) {
colors.add(R.color.colorAccent);
}
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_slide, parent, false);
return new TextVH(view);
}

@Override
public int getItemCount() {
return colors.size();
}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
// String text = "item: " + position;
TextVH textVH = (TextVH) holder;
textVH.item_layout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (null != mItemClickListener) {
mItemClickListener.onItemClick(v, position);
}
}
});
// textVH.textView.setText(text);
textVH.tv_delete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.i("kkkk","删除按钮有效==========="+position);
colors.remove(position);
// 刷新ListView内容
// notifyDataSetChanged();
notifyItemChanged(colors.size());
}

});

}


@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);

mRecyclerView = recyclerView;
mRecyclerView.addOnItemTouchListener(new ItemSlideHelper(mRecyclerView.getContext(), this));
}





@Override
public int getHorizontalRange(RecyclerView.ViewHolder holder) {

if(holder.itemView instanceof LinearLayout){
ViewGroup viewGroup = (ViewGroup) holder.itemView;
if(viewGroup.getChildCount() == 2){
return viewGroup.getChildAt(1).getLayoutParams().width;
}
}


return 0;
}

@Override
public RecyclerView.ViewHolder getChildViewHolder(View childView) {
return mRecyclerView.getChildViewHolder(childView);
}

@Override
public View findTargetView(float x, float y) {
return mRecyclerView.findChildViewUnder(x, y);
}


}

class TextVH extends RecyclerView.ViewHolder{

TextView textView,tv_delete;
LinearLayout item_layout;


public TextVH(View itemView) {
super(itemView);

textView = (TextView) itemView.findViewById(R.id.tv_text);
tv_delete = (TextView) itemView.findViewById(R.id.tv_delete);
item_layout = (LinearLayout) itemView.findViewById(R.id.item_layout);

}
}

item_slide.xml适配器布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#f6f6f6"
>


<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"

android:layout_marginTop="5dp"
android:background="#fff"
>


<!--商品详情-->
<LinearLayout
android:id="@+id/item_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"

android:orientation="horizontal"
android:gravity="center|left"
android:background="#fff">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:layout_weight="7"
android:layout_marginLeft="15dp"
android:paddingTop="1dp"
android:paddingBottom="1dp"
android:paddingLeft="1dp"
android:paddingRight="1dp"
android:orientation="horizontal"
android:gravity="center|left"
android:background="#f6f6f6">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:gravity="center"
android:background="#fff">


<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:background="@mipmap/liucha"/>


</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="3"
android:layout_marginLeft="15dp"
android:layout_marginRight="10dp"
android:orientation="vertical"
android:gravity="center_horizontal|right">


<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="horizontal"
android:gravity="center|left">


<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#292421"
android:text="挑战不一样的fell,大茶网青春小茶合包 (五包)"
android:textSize="15dp"/>

</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="horizontal"
android:gravity="center_horizontal|left">


<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="horizontal"
android:gravity="center_horizontal|left">

<TextView
android:id="@+id/tv_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:text="数量:"
android:textColor="#808A87"
android:textSize="12dp"/>


<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:layout_marginBottom="5dp"
android:text="1"
android:textColor="#808A87"
android:textSize="12dp"/>

</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="horizontal"
android:gravity="center_horizontal|right">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:text="单价:"
android:textColor="#808A87"
android:textSize="12dp"/>


<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:layout_marginLeft="5dp"
android:text="¥172.00"
android:textColor="#808A87"
android:textSize="12dp"/>

</LinearLayout>

</LinearLayout>

</LinearLayout>
</LinearLayout>

</LinearLayout>

<LinearLayout
android:layout_width="80dp"
android:layout_height="match_parent"
>


<TextView
android:id="@+id/tv_delete"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginTop="5dp"
android:layout_weight="1"
android:text="删除"
android:background="@android:color/holo_red_light"
android:gravity="center"
android:textColor="@android:color/white"
/>

</LinearLayout>

</LinearLayout>

LinearItemDecoration 类文件

package com.lw.widget.slideitem;

import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.support.annotation.ColorInt;
import android.support.v7.widget.RecyclerView;
import android.util.TypedValue;
import android.view.View;

public class LinearItemDecoration extends RecyclerView.ItemDecoration{

private Paint mPaint;
private int mColor;

public LinearItemDecoration(@ColorInt int color) {
mPaint = new Paint();
mPaint.setColor(color);
mPaint.setAntiAlias(true);
mPaint.setDither(true);

}

public LinearItemDecoration() {
this(0);
}

@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
int height = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, parent.getResources().getDisplayMetrics());
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
View childView ;
RecyclerView.LayoutParams layoutParams;
int childCount = layoutManager.getChildCount();
Rect drawRect = new Rect();
int top,left,right,bottom;

for(int childIndex = 0 ; childIndex < childCount - 1; childIndex++){
childView = layoutManager.getChildAt(childIndex);
layoutParams = (RecyclerView.LayoutParams) childView.getLayoutParams();
top = childView.getBottom() + layoutParams.bottomMargin;
left = childView.getLeft() + layoutParams.leftMargin + childView.getPaddingLeft();
right = childView.getRight() - childView.getPaddingRight() - layoutParams.rightMargin;
bottom = top + height;
drawRect.set(left,top, right, bottom);
c.drawRect(drawRect, mPaint);
}
}

@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
int height = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, parent.getResources().getDisplayMetrics());
outRect.set(0, 0, 0, height);
}
}

ItemSlideHelper类文件

package com.lw.widget.slideitem;

import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Rect;
import android.support.v4.view.GestureDetectorCompat;
import android.support.v4.view.MotionEventCompat;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;

/**
* 帮助显示左滑菜单
*/

public class ItemSlideHelper implements RecyclerView.OnItemTouchListener, GestureDetector.OnGestureListener {


private static final String TAG = "ItemSwipeHelper";


private final int DEFAULT_DURATION = 200;

private View mTargetView;

private int mActivePointerId;

private int mTouchSlop;
private int mMaxVelocity;
private int mMinVelocity;
private int mLastX;
private int mLastY;


private boolean mIsDragging;

private Animator mExpandAndCollapseAnim;

private GestureDetectorCompat mGestureDetector;

private Callback mCallback;


public ItemSlideHelper(Context context, Callback callback) {
this.mCallback = callback;

//手势用于处理fling
mGestureDetector = new GestureDetectorCompat(context, this);

ViewConfiguration configuration = ViewConfiguration.get(context);
mTouchSlop = configuration.getScaledTouchSlop();
mMaxVelocity = configuration.getScaledMaximumFlingVelocity();
mMinVelocity = configuration.getScaledMinimumFlingVelocity();
}

@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
Log.d(TAG, "onInterceptTouchEvent: " + e.getAction());


int action = MotionEventCompat.getActionMasked(e);
int x = (int) e.getX();
int y = (int) e.getY();



//如果RecyclerView滚动状态不是空闲targetView不是空
if(rv.getScrollState() != RecyclerView.SCROLL_STATE_IDLE){
if(mTargetView != null){
//隐藏已经打开
smoothHorizontalExpandOrCollapse(DEFAULT_DURATION / 2);
mTargetView = null;
}

return false;
}

//如果正在运行动画 ,直接拦截什么都不做
if(mExpandAndCollapseAnim != null && mExpandAndCollapseAnim.isRunning()){
return true;
}

boolean needIntercept = false;
switch (action) {
case MotionEvent.ACTION_DOWN:


mActivePointerId = MotionEventCompat.getPointerId(e, 0);
mLastX = (int) e.getX();
mLastY = (int) e.getY();

/*
* 如果之前有一个已经打开的项目,当此次点击事件没有发生在右侧的菜单中则返回TRUE,
* 如果点击的是右侧菜单那么返回FALSE这样做的原因是因为菜单需要响应Onclick
* */

if(mTargetView != null){
return !inView(x, y);
}

//查找需要显示菜单的view;
mTargetView = mCallback.findTargetView(x, y);

break;
case MotionEvent.ACTION_MOVE:

int deltaX = (x - mLastX);
int deltaY = (y - mLastY);

if(Math.abs(deltaY) > Math.abs(deltaX))
return false;

//如果移动距离达到要求,则拦截
needIntercept = mIsDragging = mTargetView != null && Math.abs(deltaX) >= mTouchSlop;
break;

case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
/*
* 走这是因为没有发生过拦截事件
* */

if(isExpanded()){

if (inView(x, y)) {
// 如果走这那行这个ACTION_UP的事件会发生在右侧的菜单中
Log.d(TAG, "click item");
}else{
//拦截事件,防止targetView执行onClick事件
needIntercept = true;
}

//折叠菜单
smoothHorizontalExpandOrCollapse(DEFAULT_DURATION / 2);
}

mTargetView = null;
break;
}

return needIntercept ;
}



private boolean isExpanded() {
return mTargetView != null && mTargetView.getScrollX() == getHorizontalRange();
}

private boolean isCollapsed() {

return mTargetView != null && mTargetView.getScrollX() == 0;
}

/*
* 根据targetView的scrollX计算出targetView的偏移,这样能够知道这个point
* 是在右侧的菜单中
* */

private boolean inView(int x, int y) {

if (mTargetView == null)
return false;

int scrollX = mTargetView.getScrollX();
int left = mTargetView.getWidth() - scrollX;
int top = mTargetView.getTop();
int right = left + getHorizontalRange() ;
int bottom = mTargetView.getBottom();
Rect rect = new Rect(left, top, right, bottom);
return rect.contains(x, y);
}



@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
Log.d(TAG, "onTouchEvent: " + e.getAction());

if(mExpandAndCollapseAnim != null && mExpandAndCollapseAnim.isRunning() || mTargetView == null)
return;

//如果要响应fling事件设置将mIsDragging设为false
if (mGestureDetector.onTouchEvent(e)) {
mIsDragging = false;
return;
}


int x = (int) e.getX();
int y = (int) e.getY();
int action = MotionEventCompat.getActionMasked(e);
switch (action) {
case MotionEvent.ACTION_DOWN:
//RecyclerView 不会转发这个Down事件

break;
case MotionEvent.ACTION_MOVE:
int deltaX = (int) (mLastX - e.getX());
if(mIsDragging) {
horizontalDrag(deltaX);
}
mLastX = x;
break;

case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:

if(mIsDragging){
if(!smoothHorizontalExpandOrCollapse(0) && isCollapsed())
mTargetView = null;

mIsDragging = false;
}

break;
}


}

@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {

}

/**
*
* 根据touch事件来滚动View的scrollX
*
* @param delta
*/

private void horizontalDrag(int delta) {
int scrollX = mTargetView.getScrollX();
int scrollY = mTargetView.getScrollY();
if ((scrollX + delta) <= 0) {
mTargetView.scrollTo(0, scrollY);
return;
}


int horRange = getHorizontalRange();
scrollX += delta;
if (Math.abs(scrollX) < horRange) {
mTargetView.scrollTo(scrollX, scrollY);
} else {
mTargetView.scrollTo(horRange, scrollY);
}


}


/**
* 根据当前scrollX的位置判断是展开还是折叠
*
* @param velocityX
* 如果不等于0那么这是一次fling事件,否则是一次ACTION_UP或者ACTION_CANCEL
*/

private boolean smoothHorizontalExpandOrCollapse(float velocityX) {

int scrollX = mTargetView.getScrollX();
int scrollRange = getHorizontalRange();

if (mExpandAndCollapseAnim != null)
return false;


int to = 0;
int duration = DEFAULT_DURATION;

if (velocityX == 0) {
//如果已经展一半,平滑展开
if (scrollX > scrollRange / 2) {
to = scrollRange;
}
} else {


if (velocityX > 0)
to = 0;
else
to = scrollRange;

duration = (int) ((1.f - Math.abs(velocityX) / mMaxVelocity) * DEFAULT_DURATION);
}

if(to == scrollX)
return false;

mExpandAndCollapseAnim = ObjectAnimator.ofInt(mTargetView, "scrollX", to);
mExpandAndCollapseAnim.setDuration(duration);
mExpandAndCollapseAnim.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {

}

@Override
public void onAnimationEnd(Animator animation) {
mExpandAndCollapseAnim = null;
if (isCollapsed())
mTargetView = null;

Log.d(TAG, "onAnimationEnd");
}

@Override
public void onAnimationCancel(Animator animation) {
//onAnimationEnd(animation);
mExpandAndCollapseAnim = null;

Log.d(TAG, "onAnimationCancel");
}

@Override
public void onAnimationRepeat(Animator animation) {

}
});
mExpandAndCollapseAnim.start();

return true;
}




public int getHorizontalRange( ) {
RecyclerView.ViewHolder viewHolder = mCallback.getChildViewHolder(mTargetView);
return mCallback.getHorizontalRange(viewHolder);
}


@Override
public boolean onDown(MotionEvent e) {
return false;
}

@Override
public void onShowPress(MotionEvent e) {

}

@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}

@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return false;
}

@Override
public void onLongPress(MotionEvent e) {

}

@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {

if(Math.abs(velocityX) > mMinVelocity && Math.abs(velocityX) < mMaxVelocity) {
if(!smoothHorizontalExpandOrCollapse(velocityX) ) {
if(isCollapsed())
mTargetView = null;
return true;
}
}
return false;
}




public interface Callback {

int getHorizontalRange(RecyclerView.ViewHolder holder);

RecyclerView.ViewHolder getChildViewHolder(View childView);

View findTargetView(float x, float y);

}
}

ItemClickListener Item监听接口

package com.lw.widget.slideitem;

import android.view.View;

/**
* Created by Administrator on 2016-07-05.
*/

public interface ItemClickListener {

/**
* Item 普通点击
*/

public void onItemClick(View view, int postion);

}

3.在app目录下的build文件下添加如下代码:

dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.1.1'
compile 'com.android.support:recyclerview-v7:23.1.1'
}

实现之后的效果图如下:

仿QQ侧滑删除Item效果Demo

仿QQ侧滑删除Item效果Demo