将RecyclerView打造成自己SwipeRecyclerView
一、概述
最近在项目中用到一个将ListView的Item进行左滑,然后点击删除的功能。
在网上找了一些SwipeListView,看了下源码400多行,而且好多方法,感觉好复杂,于是自己动手参照着写了一个基于RecyclerView的SwipeRecyclerView。目前只实现了侧滑删除,后期准备加上头部,尾部以及其它效果。
具体的代码在Github上:
https://github.com/privatego/SwipeRecyclerView.git
如果觉得好用,请给我一颗星星哈!!,如果不好用,则留言或评论,尽管拍砖吧~~~
二、关于RecyclerView
关于RecyclerView的基础知识,鸿洋大神在《Android RecyclerView 使用完全解析 体验艺术般的控件》中已经详细介绍过了,在这不重复介绍,直接进入实际操作
RecyclerView是在support-v7中,可以通过如下方法导入:
compile 'com.android.support:recyclerview-v7:24.0.0'
三、打造属于自己的SwipeRecyclerView控件
先看看最终的运行效果:
具体实现:
1.定义RecyclerView的Item样式
<?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="100dp"
android:background="@color/white"
android:id="@+id/ll_item"
>
<!-- 屏幕的正常宽度 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv_item_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="item"
android:textSize="24dp"
/>
</LinearLayout>
<!-- 屏幕右侧外边部分,正常时在屏幕中处于不可见 -->
<LinearLayout
android:id="@+id/ll_hidden"
android:layout_width="100dp"
android:layout_height="match_parent"
android:background="#ff0000"
android:gravity="center"
>
<TextView
android:id="@+id/tv_item_delete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="删除"
android:textSize="24dp"
/>
</LinearLayout>
</LinearLayout>
注意到第二个LinearLayout是处于屏幕右侧外边的,当Item向左滑动时,等于整个Item左移,这样就Item右侧显示,左侧会隐藏部分内容。
2.自定义SwipeRecyclerView,继承RecyclerView,重写其构造方法和onTouchEvent方法。
import android.content.Context;
import android.graphics.Rect;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.animation.LinearInterpolator;
import android.widget.LinearLayout;
import android.widget.Scroller;
import android.widget.TextView;
/**
* Created by jingling on 16/6/29.
*/
public class SwipeRecyclerView extends RecyclerView{
private static final String TAG = "RecycleView";
private int maxLength, mTouchSlop;
private int xDown, yDown, xMove, yMove;
/**
* 当前选中的item索引(这个很重要)
*/
private int curSelectPosition;
private Scroller mScroller;
private LinearLayout mCurItemLayout, mLastItemLayout;
private LinearLayout mLlHidden;//隐藏部分
private TextView mItemContent;
private LinearLayout mItemDelete;
/**
* 隐藏部分长度
*/
private int mHiddenWidth;
/**
* 记录连续移动的长度
*/
private int mMoveWidth = 0;
/**
* 是否是第一次touch
*/
private boolean isFirst = true;
private Context mContext;
/**
* 删除的监听事件
*/
private OnRightClickListener mRightListener;
public void setRightClickListener(OnRightClickListener listener){
this.mRightListener = listener;
}
public SwipeRecyclerView(Context context) {
this(context, null);
}
public SwipeRecyclerView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SwipeRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mContext = context;
//滑动到最小距离
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
//滑动的最大距离
maxLength = ((int) (180 * context.getResources().getDisplayMetrics().density + 0.5f));
//初始化Scroller
mScroller = new Scroller(context, new LinearInterpolator(context, null));
}
@Override
public boolean onTouchEvent(MotionEvent e) {
int x = (int)e.getX();
int y = (int)e.getY();
switch (e.getAction()){
case MotionEvent.ACTION_DOWN:
//记录当前按下的坐标
xDown = x;
yDown = y;
//计算选中哪个Item
int firstPosition = ((LinearLayoutManager)getLayoutManager()).findFirstVisibleItemPosition();
Rect itemRect = new Rect();
final int count = getChildCount();
for (int i=0; i<count; i++){
final View child = getChildAt(i);
if (child.getVisibility() == View.VISIBLE){
child.getHitRect(itemRect);
if (itemRect.contains(x, y)){
curSelectPosition = firstPosition + i;
break;
}
}
}
if (isFirst){//第一次时,不用重置上一次的Item
isFirst = false;
}else {
//屏幕再次接收到点击时,恢复上一次Item的状态
if (mLastItemLayout != null && mMoveWidth > 0) {
//将Item右移,恢复原位
scrollRight(mLastItemLayout, (0 - mMoveWidth));
//清空变量
mHiddenWidth = 0;
mMoveWidth = 0;
}
}
//取到当前选中的Item,赋给mCurItemLayout,以便对其进行左移
View item = getChildAt(curSelectPosition - firstPosition);
if (item != null) {
//获取当前选中的Item
MyRecycleAdapter.MyViewHolder viewHolder = (MyRecycleAdapter.MyViewHolder) getChildViewHolder(item);
mCurItemLayout = viewHolder.mLlItem;
//找到具体元素(这与实际业务相关了~~)
mLlHidden = (LinearLayout)mCurItemLayout.findViewById(R.id.ll_hidden);
mItemContent = (TextView)mCurItemLayout.findViewById(R.id.tv_item_content);
mItemDelete = (LinearLayout)mCurItemLayout.findViewById(R.id.ll_hidden);
mItemDelete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mRightListener != null){
//删除
mRightListener.onRightClick(curSelectPosition, "");
getAdapter().notifyItemRemoved(curSelectPosition);
getAdapter().notifyItemRangeChanged(curSelectPosition, getAdapter().getItemCount());
}
}
});
//这里将删除按钮的宽度设为可以移动的距离
mHiddenWidth = mLlHidden.getWidth();
}
break;
case MotionEvent.ACTION_MOVE:
xMove = x;
yMove = y;
int dx = xMove - xDown;//为负时:手指向左滑动;为正时:手指向右滑动。这与Android的屏幕坐标定义有关
int dy = yMove - yDown;//
//左滑
if (dx < 0 && Math.abs(dx) > mTouchSlop && Math.abs(dy) < mTouchSlop){
int newScrollX = Math.abs(dx);
if (mMoveWidth >= mHiddenWidth){//超过了,不能再移动了
newScrollX = 0;
} else if (mMoveWidth + newScrollX > mHiddenWidth){//这次要超了,
newScrollX = mHiddenWidth - mMoveWidth;
}
//左滑,每次滑动手指移动的距离
scrollLeft(mCurItemLayout, newScrollX);
//对移动的距离叠加
mMoveWidth = mMoveWidth + newScrollX;
}else if (dx > 0){//右滑
//执行右滑,这里没有做跟随,瞬间恢复
scrollRight(mCurItemLayout, 0 - mMoveWidth);
mMoveWidth = 0;
}
break;
case MotionEvent.ACTION_UP://手抬起时
int scrollX = mCurItemLayout.getScrollX();
if (mHiddenWidth > mMoveWidth) {
int toX = (mHiddenWidth - mMoveWidth);
if (scrollX > mHiddenWidth / 2) {//超过一半长度时松开,则自动滑到左侧
scrollLeft(mCurItemLayout, toX);
mMoveWidth = mHiddenWidth;
} else {//不到一半时松开,则恢复原状
scrollRight(mCurItemLayout, 0 - mMoveWidth);
mMoveWidth = 0;
}
}
mLastItemLayout = mCurItemLayout;
break;
}
return super.onTouchEvent(e);
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
Log.e(TAG, "computeScroll getCurrX ->" + mScroller.getCurrX());
mCurItemLayout.scrollBy(mScroller.getCurrX(), 0);
invalidate();
}
}
/**
* 向左滑动
*/
private void scrollLeft(View item, int scorllX){
Log.e(TAG, " scroll left -> " + scorllX);
item.scrollBy(scorllX, 0);
}
/**
* 向右滑动
*/
private void scrollRight(View item, int scorllX){
Log.e(TAG, " scroll right -> " + scorllX);
item.scrollBy(scorllX, 0);
}
public interface OnRightClickListener{
boolean onRightClick(int position, String id);
}
}
上述代码中,主要是重定onTouchEvent事件方法:
当手指按下时,计算出当前选中的是哪个Item,并获取到该Item对象;然后判断手指移动方向,若左移,则滑动(在滑动之前,先恢复上次的状态);若右移,则恢复;当左移完成之后,“删除”按钮自然就“暴露”在屏幕上可点击的范围了;然后就可以对Item进行删除操作了。
3.最后,在Activity中进行调用
private void initView() {
//设置布局管理器
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
mRecycleView.setLayoutManager(layoutManager);
layoutManager.setOrientation(OrientationHelper.VERTICAL);
mRecycleView.setAdapter(mAdapter);
DividerItemDecoration dividerLine = new DividerItemDecoration(DividerItemDecoration.VERTICAL);
dividerLine.setSize(1);
dividerLine.setColor(0xffdddddd);
mRecycleView.addItemDecoration(dividerLine);
mRecycleView.setRightClickListener(new SwipeRecyclerView.OnRightClickListener() {
@Override
public void onRightClick(int position, String id) {
Toast.makeText(RecyclerViewActivity.this, " position = " + position , Toast.LENGTH_SHORT).show();
Log.e(TAG, " onRightClick position = " + position);
}
});
}