前提說明
最近遇到了一個需求,要添加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