Android RecycleView实现防QQ左滑删除

Android RecycleView实现防QQ左滑删除

仿QQ消息列表左滑弹出菜单,标记已读和删除

话不多说,看到这样的效果心动了么?

这里写图片描述

1.先上build .gradle,dependencies里面要这样写,其实就是导入v7里面的recyclerView。要用butterknife的话, 记得加到这里来:

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:support-v4:23.4.0' compile 'com.android.support:recyclerview-v7:23.2.0' //butterknife compile 'com.jakewharton:butterknife:6.1.0' }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

2.再来列表的item, item_msg_remind.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="72dp" android:background="@color/white" android:orientation="horizontal"> <!-- 消息主体,width记得match_parent,直接把后面两个布局顶出去 --> <LinearLayout android:id="@+id/ll_msg_remind_main" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/white" android:layout_marginLeft="15dp" android:layout_marginRight="8dp" android:orientation="horizontal"> <View android:id="@+id/msg_remind_point" android:layout_width="15dp" android:layout_height="15dp" android:layout_gravity="center_vertical" android:background="@drawable/shape_remind_point_theme" /> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="6dp"> <TextView android:id="@+id/tv_remind_title" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:text="隔壁的二蛋" /> <TextView android:id="@+id/tv_remind_content" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="2" android:ellipsize="end" android:maxLines="2" android:text="对方撤回了一条消息并砍了你的狗" /> </LinearLayout> </LinearLayout> <LinearLayout android:layout_width="90dp" android:layout_height="match_parent" android:background="#D1BA74"> <TextView android:id="@+id/tv_msg_remind_check" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:text="标记已读" android:textColor="@color/white" /> </LinearLayout> <LinearLayout android:layout_width="60dp" android:layout_height="match_parent" android:background="@color/theme_color"> <TextView android:id="@+id/tv_msg_remind_delete" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:text="删除" android:textColor="@color/white" /> </LinearLayout> </LinearLayout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76

3.那什么,Adapter来了,MsgRemindAdapter:

package com.simbaliu3.swiperecyclerlist;

import android.content.Context;
import android.support.v7.widget.RecyclerView;
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; import butterknife.ButterKnife; import butterknife.InjectView; /** * 消息adapter * <p/> * -->特别注意extends后面Adapter<>里面要写自己定义的ViewHolder */ public class MsgRemindAdapter extends RecyclerView.Adapter<MsgRemindAdapter.RemindViewHolder> implements ItemSlideHelper.Callback { private Context context; private List<MsgVo> mDatas = new ArrayList<MsgVo>(); private RecyclerView mRecyclerView; public MsgRemindAdapter(Context context, List<MsgVo> mDatas) { this.context = context; this.mDatas = mDatas; } @Override public RemindViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()). inflate(R.layout.item_msg_remind, parent, false); return new RemindViewHolder(view); } /** * 将recyclerView绑定Slide事件 * * @param recyclerView */ @Override public void onAttachedToRecyclerView(RecyclerView recyclerView) { super.onAttachedToRecyclerView(recyclerView); mRecyclerView = recyclerView; mRecyclerView.addOnItemTouchListener(new ItemSlideHelper(mRecyclerView.getContext(), this)); } @Override public void onBindViewHolder(final RemindViewHolder holder, final int position) { /** * 消息状态 */ if (mDatas.get(position).isChecked()) { holder.msgRemindPoint.setBackgroundResource(R.drawable.shape_remind_point_gray); } else { holder.msgRemindPoint.setBackgroundResource(R.drawable.shape_remind_point_theme); } //消息标题 holder.tvRemindTitle.setText(mDatas.get(position).getTitle()); //消息内容 holder.tvRemindContent.setText(mDatas.get(position).getContent()); /** * -->特别注意,敲黑板了啊!!!在执行notify的时候,取position要取holder.getAdapterPosition(), * 消息被删除之后,他原来的position是final的,所以取到的值不准确,会报数组越界。 */ //消息主体监听,这里我是让他添加一条数据,替换成你需要的操作即可 holder.llMsgRemindMain.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { addData(mDatas.size()); } }); //标记已读监听 holder.tvMsgRemindCheck.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { mDatas.get(holder.getAdapterPosition()).setChecked(true); notifyItemChanged(holder.getAdapterPosition()); } }); //删除监听 holder.tvMsgRemindDelete.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { removeData(holder.getAdapterPosition()); } }); } @Override public int getItemCount() { return mDatas.size(); } /** * 此方法用来计算水平方向移动的距离 * * @param holder * @return */ @Override public int getHorizontalRange(RecyclerView.ViewHolder holder) { if (holder.itemView instanceof LinearLayout) { ViewGroup viewGroup = (ViewGroup) holder.itemView; //viewGroup包含3个控件,即消息主item、标记已读、删除,返回为标记已读宽度+删除宽度 return viewGroup.getChildAt(1).getLayoutParams().width + viewGroup.getChildAt(2).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); } /** * 自定义的ViewHolder */ public class RemindViewHolder extends RecyclerView.ViewHolder { @InjectView(R.id.msg_remind_point) View msgRemindPoint; @InjectView(R.id.tv_remind_title) TextView tvRemindTitle; @InjectView(R.id.tv_remind_content) TextView tvRemindContent; @InjectView(R.id.ll_msg_remind_main) LinearLayout llMsgRemindMain; @InjectView(R.id.tv_msg_remind_check) TextView tvMsgRemindCheck; @InjectView(R.id.tv_msg_remind_delete) TextView tvMsgRemindDelete; public RemindViewHolder(View itemView) { super(itemView); ButterKnife.inject(this, itemView); } } /** * 添加单条数据 * * @param position */ public void addData(int position) { MsgVo vo = new MsgVo(); if (position % 2 == 1) { vo.setChecked(false); vo.setTitle("隔壁的二蛋"); vo.setContent("对方撤回了一条消息并砍了你的狗,问你服不服。"); } else { vo.setChecked(false); vo.setTitle("对面的三娃"); vo.setContent("今天晚上开黑,4缺1,来不来?"); } mDatas.add(position, vo); notifyItemInserted(position); } /** * 删除单条数据 * * @param position */ public void removeData(int position) { mDatas.remove(position); notifyItemRemoved(position); } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184

4.要做这么高难度的滑动,是要一个ItemSlideHelper的:

package com.simbaliu3.swiperecyclerlist;

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.view.GestureDetector; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; /** * 消息列表左滑菜单帮助类 */ public class ItemSlideHelper implements RecyclerView.OnItemTouchListener, GestureDetector.OnGestureListener { 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) { 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的事件会发生在右侧的菜单中 } 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) { 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; } @Override public void onAnimationCancel(Animator animation) { //onAnimationEnd(animation); mExpandAndCollapseAnim = null; } @Override public void onAnimationRepeat(Animator animation) { } }); mExpandAndCollapseAnim.start(); 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LuckyTHP

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值