ListView的小插曲
其实ListView
或者说它们这一类都是实现了单选多选模式的,如果你使用CheckedTextview
等实现了Checkable
的子类的话,根本不用写什么代码,单选多选模式就搞定了。
* Register a callback to be invoked when an item in this AdapterView has
* been selected.
*
* @param listener The callback that will run
*/
public void setOnItemSelectedListener(@Nullable OnItemSelectedListener listener) {
mOnItemSelectedListener = listener;
}
但是,但是,这个回调在 ListView
或者 GridView
里面似乎并没有什么卵用呢!这是为撒呢?!
在 AdapterView
里面有个handleDataChanged()
的方法,这个方法,就是正常执行回调OnItemSelectedListener
滴,但是,这个方法被AbsListView
给重写了,而且,没有再去回调对应的OnItemSelectedListener
了,所以,在ListView和GridView中设置了这个回调也是没有用的。详细的自己去扒一扒源码看看吧,这里不细说了。。
Why is my onItemSelectedListener not called in a ListView?
虽然没有了相关回调,但是我们可以在适当的地方通过调用方法:SparseBooleanArray positions = mListView.getCheckedItemPositions()
来获取我们选中的位置的。
RecyclerView实现单选和多选模式
上面说了 ListView
里面的单选多选模式,接下来就是 RecyclerView
的实现了,首先考虑需要处理哪些问题:
选择标识的添加和相关选择状态保存,item的点击事件(普通模式点击响应,选择模式下刷新为选中或非选中状态),最后就是要弄好回调方法。
这个库还是基于之前的Android RecycleView轻松实现下拉刷新、加载更多,所以呢,这次创建了一个SelectRefreshRecycleAdapter
的类用来专门处理单选多选模式,当然,它肯定是继承自RefreshRecycleAdapter<T>
滴。在这个类中,根据是选择模式或者是普通模式来进行了不同的点击处理。处理了第一个问题。
选择标识的添加 选择状态保存
对于选择标识的添加,首先这个一般来说应该和各种业务没有关系,所以不应该定义在json数据中,当然啦,如果选择状态你真的已经定义了相关字段并且时刻根据后台数据刷新的话,这里也是可以解决滴。
首先我这里定义了一个接口Iselect
,然后,这里接着也定义好了一个实现类了。
public interface ISelect {
boolean isSelected();
void setSelected(boolean selected);
}
public class SelectBean implements ISelect, Parcelable {
private boolean isSelected;
public boolean isSelected() {
return isSelected;
}
public void setSelected(boolean selected) {
isSelected = selected;
}
...
}
在使用的时候,如果你的Bean中已经有了相关选中状态的字段了,那么实现Iselect
接口,如果没有定义相关字段,那么直接继承SelectBean
,其他的就不用去管了。这是关于添加选择标识的解决,然后就是选中状态的保存,这个很简单了,内部维护了一个集合,选中了添加进去,取消选中就移除了。
Item的点击及同步刷新
因为这里有单选模式和多选模式两种情况嘛,接下来一次分析下。
如果是单选模式的话,我们在选中item2之后,不仅要刷新item2的状态,而且还要将之前的选中item的状态更新,意思就是可能要更新两个item。所以这里要定义一个prePos
的字段来保存之前的那个item。
如果是多选模式的话,那么就比较简单了,糊糊选就好了(不要纠结为什么用糊糊)。
得益于RecyclerView的后天优势,我们每次只需要调用notifyItemChanged(pos)
的方法来刷新指定的item就行了。
@Override
public void performClick(final View itemView, final int position) {
final T testBean = list.get(position);
if (isSelectMode) {
Log.e("TAG", "onViewHolderBind: " + position + "点击了!!");
//点击后取反当前位置
boolean selected = !testBean.isSelected();
testBean.setSelected(selected);
dispatchSelected(itemView, position, testBean, selected);
if (currentMode == SingleMode && position != prePos && testBean.isSelected()) {
//单选模式的prePos处理
list.get(prePos).setSelected(false);
dispatchSelected(itemView, prePos, testBean, false);
notifyItemChanged(prePos);
}
notifyItemRangeChanged(position, 1);
prePos = position;
} else {
//不是选择模式就正常回调咯
if (listener != null) {
listener.onItemClick(itemView, position);
}
}
}
最后就是Viewholder里面的具体实现了:
private static class MyViewHolder extends RecyclerView.ViewHolder {
private final CheckedTextView mTv;
public MyViewHolder(View itemView) {
super(itemView);
mTv = (CheckedTextView) itemView.findViewById(R.id.text);
}
public void bindDateView(TestBean s) {
mTv.setText(s.isSelected() ? "选中:" + s.getName() : s.getName());
mTv.setChecked(s.isSelected());
}
}
相关回调
根据上面 ListView
的那个问题,如果在选择过程中,没有相关回调我们是会抓狂的。参照 ListView
的相关接口,有了以下的定义:
interface OnItemSelectedListener {
void onItemSelected(View view, int position, boolean isSelected);
void onNothingSelected();
}
额,最后加了一个isSelected
的参数,感觉就说你选中了撒没有选中的不说是不是有点儿不厚道?!万一你自己要在这里去维护自己的一个数据呢?那不是坑你啦,其实我不想给你说你完全可以通过getSelectedBeans()
的方法来直接获取已经选中的集合。
private void dispatchSelected(View itemView, int position, T testBean, boolean isSelected) {
if (isSelected) {
selectedBeans.add(testBean);
} else {
selectedBeans.remove(testBean);
if (selectedListener != null && selectedBeans.isEmpty()) {
selectedListener.onNothingSelected();
}
}
if (selectedListener != null) {
selectedListener.onItemSelected(itemView, position, isSelected);
}
}
额外赠送
bug修复
之前写的版本中,加载更多还没有适配GridLayoutManager
,这里也做了相关的适配,并且提供了回调,方便你指定对应的某个 position
的spanSize
:
public void setLayoutManager(final RecyclerView.LayoutManager manager) {
this.manager = manager;
if (manager instanceof GridLayoutManager) {
((GridLayoutManager) manager).setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
switch (adapter.getItemViewType(position)) {
case TYPE_BOTTOM:
return ((GridLayoutManager) manager).getSpanCount();
default:
return (spanSizeCallBack != null ? spanSizeCallBack.getSpanSize(position) : 0) == 0 ? 1 : spanSizeCallBack.getSpanSize(position);
}
}
});
}
mRecyclerView.setLayoutManager(manager);
}
你只需要这么调用:
mRecycleView.setLayoutManager(manager);
mRecycleView.setSpanSizeCallBack(new SwipeRefreshRecycleView.SpanSizeCallBack() {
@Override
public int getSpanSize(int position) {
return 1;
}
});
强大的ItemDecoration
因为说到了GridLayoutManager
了嘛,那么必须说下这个ItemDecoration
,例如我们需要让多个item的留白是等距离的,那么就要使用这个东东了。
另外它还可以来用作头布局,还可以做出sticky的效果。上面的核心代码大概就是酱紫的:
/**
*
* @param space item之间的空间
* @param count 列数
* @param showEdge 是否显示左右边缘
*/
public SpacesItemDecoration(int space, int count, boolean showEdge) {
this.spacing = space;
this.spanCount = count;
this.showEdge = showEdge;
pre = spacing * 1.0f / spanCount;
}
@Override
public void getItemOffsets(Rect outRect, View view,
RecyclerView parent, RecyclerView.State state) {
int position = parent.getChildLayoutPosition(view);
int column = position % 3;
if (showEdge) {
outRect.left = (int) (spacing - column * pre);//left
outRect.right = (int) ((column + 1) * pre);//right
} else {
outRect.left = (int) (column * pre);
outRect.right = (int) (spacing - (column + 1) * pre);
}
if (position < spanCount) { // top
outRect.top = spacing;
}
outRect.bottom = spacing; // bottom
}
到这里,RecyclerView
的单选或者多选模式就搞定了!如果对于这个 Adapter
有相关疑问的可以先去看看上面的一篇哟。
附上最新的相关效果:
仓库地址:https://github.com/lovejjfg/PowerRecyclerView
---- Edit By Joe At 2016 11 26 ----