前提说明
最近遇到了一个需求,要添加recyclerview侧滑删除的功能,本着不重复造轮子的精神,google一通,https://github.com/yanzhenjie/SwipeRecyclerView 这个库写的非常牛逼,基本上覆盖了测滑需求。但正因为他的优点,我只是想做一个测滑删除功能,不需要其他功能,所以引入有点代价。再加上他重写了recyclerview,总有点不放心,可能心理原因吧,并不是这个库不优秀。我于是自己造了轮子。
撸个demo
1. 先看效果视频
2. 给一个demo 代码
public class MainActivity extends Activity {
RecyclerView mRecyclerView;
List<String> data;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRecyclerView = findViewById(R.id.recycler);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mRecyclerView.addItemDecoration(new DividerItemDecoration(this, LinearLayoutManager.VERTICAL));
// mRecyclerView.setHasFixedSize(true);
// mRecyclerView.setNestedScrollingEnabled(false);
data = new ArrayList<>();
for(int i=0;i<15;i++){
data.add(String.valueOf(i));
}
//Adapter 为业务本身Adapter,onBindViewHolder(final ViewHolder holder, final int position)
//中holder的类型为Recyclerview.ViewHolder,需要手动强转成自定义的ViewHolder
Adapter adapter = new Adapter(this, data);
// adapter.setHasStableIds(true);
//核心就是构建SlipReAdapter
SlipReAdapter.Builder builder = new SlipReAdapter.Builder()
.setAdapter(adapter)
.setISlipClickAction(new ISlipClickAction() {
@Override
public void onAction(int position) {
data.remove(position);
}
})
.setMode(SlipReAdapter.MODE_DELETE)
.setSlipViewId(R.layout.item_remove);
mRecyclerView.setAdapter(builder.build());
}
说明:
- 如果Scrollview嵌套了RecyclerView,需要在Scrollview的子view中加入属性android:descendantFocusability=“blocksDescendants”
- 也可以提供测滑view的宽度给SlipReAdapter,调用builder的setSlipWidth(int width)
- 回弹和滑动基于HorizontalScrollView,可放心使用。
- MODE_DELETE是点击模式,目前有两种,一种删除,另外一种只是点击。demo演示的是删除模式。效果图中的“内容点击“这个按钮,是具体的内容,只是为了演示滑动是否对内容布局的点击等事件有影响。
3.原理(核心要点,提供demo)
- 自定义一个HorizontalScrollView,重写onTouchEvent,主要覆盖ACTION_UP事件,因为要做手指抬起自动item复原的效果,其他事件交给HorizontalScrollView。核心代码如下:
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (ev == null) {
return super.onTouchEvent(ev);
} else {
return commOnTouchEvent(ev);
}
}
private boolean commOnTouchEvent(MotionEvent ev) {
int action = ev.getAction();
int length = threshold;
switch (action) {
case MotionEvent.ACTION_DOWN:
x = ev.getX();
break;
case MotionEvent.ACTION_UP:
//复原位置
if((ev.getX() - x)>0){
if(getScrollX()>length/2){
smoothScrollTo(length,0);
}else {
smoothScrollTo(0,0);
}
}else {
if(getScrollX()>length/2){
smoothScrollTo(length,0);
}else {
smoothScrollTo(0,0);
}
}
return true;
case MotionEvent.ACTION_MOVE:
return super.onTouchEvent(ev);
default:
return true;
}
return true;
}
需要传入threshold,提供复原阈值,目前默认是滑动距离不到一半就复原回去。
- 包装器 SlipReAdapter。通过Builder,传入真正的Adapter等参数 来构建包装器SlipReAdapter。SlipReAdapter会提供完整的测滑布局,不需要使用者提供,onCreateViewHolder,onBindViewHolder,getItemCount等核心方法中会调用真正Adapter的方法,这里需要注意一点,也是修改的唯一一点,Adapter覆盖的 public void onBindViewHolder(final ViewHolder holder, final int position) ,holder 是系统的,使用的时候需要强转成自定义的。一些细节可以参考demo。
贴出SlipAdapter的完整代码
public class SlipReAdapter extends RecyclerView.Adapter<RViewHolder> {
private RecyclerView.Adapter mAdapter;
private ISlipClickAction mISlipClickAction;
private int mSlipViewId;
public final static int MODE_DELETE = 0;
public final static int MODE_CLICK = 0;
private int mMode = MODE_DELETE;
private int mSlipWidth = 0;
public static class Builder {
private RecyclerView.Adapter mAdapter;
private ISlipClickAction mISlipClickAction;
private int mSlipViewId;
private int mMode;
private int mSlipWidth;
public Builder setAdapter(RecyclerView.Adapter adapter) {
mAdapter = adapter;
return this;
}
public Builder setISlipClickAction(
ISlipClickAction ISlipClickAction) {
mISlipClickAction = ISlipClickAction;
return this;
}
public Builder setSlipViewId(int slipViewId) {
mSlipViewId = slipViewId;
return this;
}
public Builder setMode(int mode) {
mMode = mode;
return this;
}
public Builder setSlipWidth(float slipWidth) {
mSlipWidth = (int) slipWidth;
return this;
}
public SlipReAdapter build() {
SlipReAdapter slipReAdapter = new SlipReAdapter();
slipReAdapter.setAdapter(mAdapter);
slipReAdapter.setISlipClickAction(mISlipClickAction);
slipReAdapter.setMode(mMode);
slipReAdapter.setSlipViewId(mSlipViewId);
slipReAdapter.setSlipWidth(mSlipWidth);
return slipReAdapter;
}
}
public SlipReAdapter() {
}
public void setAdapter(RecyclerView.Adapter adapter) {
mAdapter = adapter;
}
public void setISlipClickAction(
ISlipClickAction ISlipClickAction) {
mISlipClickAction = ISlipClickAction;
}
public void setSlipViewId(int slipViewId) {
mSlipViewId = slipViewId;
}
public void setMode(int mode) {
mMode = mode;
}
public void setSlipWidth(int slipWidth) {
mSlipWidth = slipWidth;
}
@Override
public RViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_slip, parent, false);
LinearLayout contentLL = view.findViewById(R.id.content_ll);
LinearLayout deleteLl = view.findViewById(R.id.delete_ll);
View delete = LayoutInflater.from(parent.getContext()).inflate(mSlipViewId, null, false);
deleteLl.addView(delete);
LayoutParams layoutParams = new LayoutParams(
parent.getResources().getDisplayMetrics().widthPixels,
ViewGroup.LayoutParams.WRAP_CONTENT);
ViewHolder viewHolder = mAdapter.onCreateViewHolder(parent, viewType);
viewHolder.itemView.setLayoutParams(layoutParams);
contentLL.addView(viewHolder.itemView);
return new RViewHolder(view, viewHolder, mSlipWidth);
}
@Override
public void onBindViewHolder(final RViewHolder holder, final int position) {
mAdapter.onBindViewHolder(holder.mViewHolder, position);
holder.deleteLl.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Log.i("SlipReAdapter", "slip action and the pos is:" + holder.getAdapterPosition());
if (mISlipClickAction != null) {
mISlipClickAction.onAction(holder.getAdapterPosition());
holder.mElasticHorizontalScrollView.reset();
}
if (mMode == MODE_DELETE) {
notifyItemRemoved(holder.getAdapterPosition());
} else if (mMode == MODE_CLICK) {
notifyItemChanged(holder.getAdapterPosition());
}
}
});
}
@Override
public int getItemCount() {
return mAdapter != null ? mAdapter.getItemCount() : 0;
}
public static class RViewHolder extends RecyclerView.ViewHolder {
private View deleteLl;
private ElasticHorizontalScrollView mElasticHorizontalScrollView;
private ViewHolder mViewHolder;
public RViewHolder(View itemView, ViewHolder viewHolder, int threshold) {
super(itemView);
mViewHolder = viewHolder;
deleteLl = itemView.findViewById(R.id.delete_ll);
mElasticHorizontalScrollView = itemView.findViewById(R.id.ElasticHorizontalScrollView);
if (threshold != 0) {
LayoutParams layoutParams = new LayoutParams(threshold,
ViewGroup.LayoutParams.WRAP_CONTENT);
deleteLl.setLayoutParams(layoutParams);
mElasticHorizontalScrollView.setThreshold(threshold);
} else {
deleteLl.post(new Runnable() {
@Override
public void run() {
int width = deleteLl.getWidth();
mElasticHorizontalScrollView.setThreshold(width);
}
});
}
}
}
public interface ISlipClickAction {
public void onAction(int position);
}
}
总结
这个侧滑的组件简单,实用,对原有的recyclerview无侵入性,不影响引入其他效果,比如下拉,上拉,拖拽,但是如果只想要侧滑,那么这个轮子非常适合你。想要recyclerview的全家桶,那么开篇给的SwipeRecyclerViewk库适合你,看业务场景,挑选最合适的一个。https://github.com/hanshengjian/recyclerviewdemo