就是常规意义上的那种二级列表,之所以写这篇文章,是因为就和我在专栏说明中说的一样,从0开始探索这个二级列表的功能,出现过各种意想不到的问题,也不难解决,但是浪费了大量的时间.
需求是这样的:开始没有数据,先添加一个一级列表的数据,然后开始一个一个往这个一级列表的子列表中添加数据,最新添加的数据放在最上面.子列表的Item中有一个Button,点击可以提交Item中EditText用户填写的信息,并在提交后隐藏Button,让EditText不可编辑.
严格来说我不是从0开始的哈,经过多番比较,我用了这位小哥的Demo,抽取基类的方式我非常欣赏,实际实现的时候写一个Adapter继承自基类,开关一级列表展示数据不要太方便,但是增加数据Demo中没有给出示例,我也是经过一番摸索,才实现.
稍微改动了一点的Adapter基类如下:
/**
* @author XTER
* @date 2019/8/9
*/
public abstract class FoldableRecyclerViewAdapter<K, V> extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private Context mContext;
/**
* 上级布局
*/
private int mGroupLayoutRes;
/**
* 下级布局
*/
private int mChildLayoutRes;
/**
* 数据
*/
private List<Unit<K, V>> mData;
/**
* 点击与长按监听接口
*/
public interface OnItemClickLitener {
void onItemClick(View view, int position);
void onItemLongClick(View view, int position);
}
private OnItemClickLitener itemClickLitener;
public void setOnItemClickLitener(OnItemClickLitener itemClickLitener) {
this.itemClickLitener = itemClickLitener;
}
public FoldableRecyclerViewAdapter(Context mContext, int mGroupLayoutRes, int mChildLayoutRes, List<Unit<K, V>> mData) {
this.mContext = mContext;
this.mGroupLayoutRes = mGroupLayoutRes;
this.mChildLayoutRes = mChildLayoutRes;
if (mData == null) {
this.mData = new ArrayList<>();
} else {
this.mData = mData;
}
}
@Override
public int getItemCount() {
if (mSize == 0) {
int totalSize = 0;
for (Unit unit : mData) {
totalSize += (unit.folded ? 1 : unit.children.size() + 1);
}
mSize = totalSize;
}
return mSize;
}
private int mSize = 0;
@Override
public int getItemViewType(int position) {
//通过位置判断type,因为数据传入后顺序不变,可通过数据来判断当前位置是哪一类数据
int currentPosition = -1;
for (Unit unit : mData) {
if (unit.folded) {
currentPosition = currentPosition + 1;
if (currentPosition == position) {
return FoldableViewHolder.GROUP;
}
} else {
//算上group
currentPosition = currentPosition + 1;
if (currentPosition == position) {
return FoldableViewHolder.GROUP;
}
//算上children,通过比较大小确定是否是当前Unit中的child
currentPosition = currentPosition + unit.children.size();
if (position <= currentPosition) {
return FoldableViewHolder.CHILD;
}
}
}
return FoldableViewHolder.GROUP;
}
/**
* 根据索引返回Unit中的K或V
*
* @param position 索引
* @return K/V
*/
public Object getItem(int position) {
int currentPosition = -1;
for (Unit unit : mData) {
if (unit.folded) {
currentPosition = currentPosition + 1;
if (currentPosition == position) {
return unit.group;
}
} else {
//算上group
currentPosition = currentPosition + 1;
if (currentPosition == position) {
return unit.group;
}
//算上children,通过计算确定是当前Unit的child的索引
currentPosition = currentPosition + unit.children.size();
if (position <= currentPosition) {
int unitChildIndex = unit.children.size() - 1 - (currentPosition - position);
return unit.children.get(unitChildIndex);
}
}
}
return null;
}
/**
* 根据索引确定返回某个数据集
*
* @param position 索引
* @return Unit
*/
private Unit<K, V> getUnit(int position) {
int currentPosition = -1;
for (Unit<K, V> unit : mData) {
//算上group
currentPosition += unit.folded ? 1 : unit.children.size() + 1;
if (position <= currentPosition)
return unit;
}
return null;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
if (viewType == FoldableViewHolder.CHILD) {
return new ChildViewHolder(LayoutInflater.from(mContext).inflate(mChildLayoutRes, viewGroup, false));
}
return new GroupViewHolder(LayoutInflater.from(mContext).inflate(mGroupLayoutRes, viewGroup, false));
}
@Override
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder viewHolder, int position) {
onBindView((FoldableViewHolder) viewHolder, position);
viewHolder.itemView.setOnClickListener(v -> {
if (viewHolder instanceof GroupViewHolder) {
Unit<K, V> unit = getUnit(viewHolder.getAdapterPosition());
unit.folded = !unit.folded;
mSize = 0;
LogUtils.d("onBindViewHolder: " + (viewHolder.getAdapterPosition() + 1) + " " + unit.children.size());
// notifyDataSetChanged();//最准确,但数据多时性能有影响
// notifyItemRangeChanged(viewHolder.getAdapterPosition()+1,getItemCount());//需要考虑到holder的旧索引问题,暂无太好的办法去规避
if (unit.folded) {
notifyItemRangeRemoved(viewHolder.getAdapterPosition() + 1, unit.children.size());
} else {
notifyItemRangeInserted(viewHolder.getAdapterPosition() + 1, unit.children.size());
}
}
if (itemClickLitener != null)
itemClickLitener.onItemClick(viewHolder.itemView, viewHolder.getLayoutPosition());
});
viewHolder.itemView.setOnLongClickListener(v -> {
if (itemClickLitener != null)
itemClickLitener.onItemLongClick(viewHolder.itemView, viewHolder.getLayoutPosition());
return true;
});
}
public abstract void onBindView(FoldableViewHolder holder, int position);
/*------------------ 关于数据的增删改 ------------------*/
public void add(Unit<K, V> element) {
mData.add(element);
mSize = 0;
notifyDataSetChanged();
}
public void add(List<Unit<K, V>> elemList) {
mData.addAll(elemList);
mSize = 0;
notifyDataSetChanged();
}
public void remove(Unit<K, V> elem) {
mData.remove(elem);
mSize = 0;
notifyDataSetChanged();
}
public void replace(List<Unit<K, V>> elemList) {
mData = elemList;
mSize = 0;
notifyDataSetChanged();
}
/*------------------ 一些准备工作,定义数据或Holder之类 ------------------*/
protected static abstract class FoldableViewHolder extends RecyclerView.ViewHolder {
static final int GROUP = 0;
static final int CHILD = 1;
private SparseArray<View> views = new SparseArray<>();
private View convertView;
public FoldableViewHolder(@NonNull View itemView) {
super(itemView);
this.convertView = itemView;
}
@SuppressWarnings("unchecked")
public <T extends View> T getView(int resId) {
View v = views.get(resId);
if (null == v) {
v = convertView.findViewById(resId);
views.put(resId, v);
}
return (T) v;
}
}
protected static class GroupViewHolder extends FoldableViewHolder {
public GroupViewHolder(@NonNull View itemView) {
super(itemView);
}
}
protected static class ChildViewHolder extends FoldableViewHolder {
public ChildViewHolder(@NonNull View itemView) {
super(itemView);
}
}
/**
* 数据实体,一对多
*
* @param <K>
* @param <V>
*/
public static class Unit<K, V> {
public K group;
public List<V> children;
public boolean folded = false;
public Unit(K group, List<V> children) {
this.group = group;
if (children == null) {
this.children = new ArrayList<>();
} else {
this.children = children;
}
}
public Unit(K group, List<V> children, boolean folded) {
this(group, children);
this.folded = folded;
}
}
}
泛型声明数据集,分开一级列表和二级列表,并且直接实现实现点击一级Item展开或收起对应二级列表的功能,你写一个继承它的子类,只用在onBinderView中分别给1级Item和2级Item绑定数据就可以了.
在Activity中声明声明数据集:
List<FoldableRecyclerViewAdapter.Unit<XXXBean, xxxBean>> mDataList = new ArrayList<>();
增加1个1级Item:
mDataList.add(new FoldableRecyclerViewAdapter.Unit(new XXXBean(), null));//创建不含2级Item的1级Item
增加1个2级Item:
mDataList.get(mDataList.size() - 1).children.add(0, new xxxBean());//在最后一个一级Item添加一个二级Item放在最前面
注意
为什么会浪费大量时间的原因所在.
原先在2级Item中有RadioGroup,点击不同的RadioButton,显示或隐藏EditText,我的实现方式是直接在OnBindView中给RadioGroup设置切换选项事件,在回调函数中让EditText显示或隐藏.这样导致的问题是界面状态混乱,因为回调函数被调用,也就是我们手动切换RadioGroup的时候,RecyclerView都已经刷新完整个界面了,这时候回调函数中的EditText对象还是那一个Item里面的对象吗?总结之,在onBindView中就是根据数据集展示Item,可以给子View设置各种事件,但是不能在事件中操作控件,只能自定义一个监听器,在回调函数中调用这个监听器,在Activity中更改数据集中某个状态值,然后调用notifyItemChanged方法,就可以立刻刷新这个Item.