前言
最近碰到了RecyclerView展开折叠的需求。
在CSDN上看到了一种写法是在RecyclerView的Item布局外面嵌套了一层LinearLayout。
但是这样子做有一个不好的地方是需要取消RecyclerView的重用机制。
后来又在同事的建议下尝试了只使用一层RecyclerView来实现展开折叠的实现方式。
本文会把两种写法都记录下来,大家可以直接去看第二种实现方式。
毕竟从性能等方面来考虑,只用一层RecyclerView无疑是最优解。
(于2017.11.26更新,提供了单层RecyclerView实现点击展开/折叠的带数据加载、移除的写法。)
(于2017.12.26更新,修改github指向地址。增加首发说明。)
本文由作者三汪首发于简书。
本文demo已上传Github→戳这里
视频转换gif出了点问题,展示效果不好希望大家见谅。
一、嵌套实现
1.要点说明
- 要记得在
onCreateViewHolder()
中取消Recycler的重用机制viewHolder.setIsRecyclable(false);
否则会出现重复添加子布局和在未点击展开时展开子布局的情况。 - 代码中使用了item外嵌套的LinearLayout的
setTag()
、getTag()
来判断当前点击要触发的动作是展开还是折叠。当item离开当前屏幕时就会被销毁,因此Tag的值也就不存在了。
2.代码展示(仅展示主要代码)
MainActivity.java
public class MainActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = (RecyclerView) findViewById(R.id.recycler_main);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
MainRecyclerViewAdapter recyclerAdapter = new MainRecyclerViewAdapter();
recyclerView.setAdapter(recyclerAdapter);
button = (Button) findViewById(R.id.button_main);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(MainActivity.this,TwoActivity.class);
startActivity(intent);
}
});
}
}
MainRecyclerViewAdapter.java
public class MainRecyclerViewAdapter extends RecyclerView.Adapter<MainRecyclerViewAdapter.MainRecyclerViewHolder> {
@Override
public MainRecyclerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View item = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_recycler,parent,false);
MainRecyclerViewHolder viewHolder = new MainRecyclerViewHolder(item);
viewHolder.setIsRecyclable(false);//取消viewHolder的重用机制。没有这句话子布局subView会被重复添加。
return viewHolder;
}
@Override
public void onBindViewHolder(MainRecyclerViewHolder holder, int position) {
}
@Override
public int getItemCount() {
return 20;
}
protected class MainRecyclerViewHolder extends RecyclerView.ViewHolder {
private TextView textTime, textPrice;
public MainRecyclerViewHolder(View itemView) {
super(itemView);
textTime = itemView.findViewById(R.id.text_first_time);
textPrice = itemView.findViewById(R.id.text_first_price);
//item点击事件监听
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int flag = 1;//用于判断当前是展开还是收缩状态
//获取外层linearlayout布局
LinearLayout linearLayout = view.findViewById(R.id.main_tree_root_layout);
//new一个RecyclerView来当展开的子布局。
RecyclerView subView = new RecyclerView(view.getContext());
SubViewAdapter adapter = new SubViewAdapter();
subView.setLayoutManager(new LinearLayoutManager(view.getContext()));
subView.setAdapter(adapter);
//当flag不为空的时候,获取flag的值。
if (linearLayout.getTag() != null) {
flag = (int) linearLayout.getTag();
}
//当flag为1时,添加子布局。否则,移除子布局。
if (flag == 1) {
linearLayout.addView(subView);
subView.setTag(101);
linearLayout.setTag(2);
} else {
linearLayout.removeView(view.findViewWithTag(101));
linearLayout.setTag(1);
}
}
});
}
}
//subView的adapter
private class SubViewAdapter extends RecyclerView.Adapter{
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new SubViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_recycler_detail,parent,false));
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
}
@Override
public int getItemCount() {
return 5;
}
private class SubViewHolder extends RecyclerView.ViewHolder{
private SubViewHolder(View itemView){
super(itemView);
}
}
}
}
二、单层RecyclerView实现
1.要点说明
- 当前写法采用了两个SparseArray来分别保存第一级布局和第二级布局的数据对象。
- 第一级布局的数据对象中增加了
isExpand
字段来标志当前布局是否被点击展开过,以及增加了addedSubNum
字段来储存点击展开后新增的item个数。 - 点击折叠移除数据时,由于SparseArray的特性,必须使用一个新的SparseArray来作为中转,暂时保存需要修改key的数据。等remove动作做完以后再重新put回原来的SparseArray。
2.代码展示(仅展示主要代码)
TheFirstBean.java
/**
* 第一层布局数据bean
* Created by 2dog on 2017/11/26.
*/
public class TheFirstBean {
private boolean isExpand = false;
private int addedSubNum;
public TheFirstBean() {
}
public void setExpand(boolean expand) {
isExpand = expand;
}
public void setAddedSubNum(int addedSubNum) {
this.addedSubNum = addedSubNum;
}
public boolean isExpand() {
return isExpand;
}
public int getAddedSubNum() {
return addedSubNum;
}
}
TwoActivity.java
public class TwoActivity extends AppCompatActivity {
private RecyclerView recyclerView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_two);
//数据初始化
List<TheFirstBean> list = new ArrayList<>();
for (int i = 0; i<20;i++){
list.add(new TheFirstBean());
}
//设置RecyclerView
recyclerView = (RecyclerView) findViewById(R.id.recycler_two);
recyclerView.setAdapter(new TwoRecyclerViewAdapter(list));
recyclerView.setLayoutManager(new LinearLayoutManager(this));
}
}
TwoRecyclerViewAdapter .java
/**
* 使用一层recyclerView实现点击展开二层布局效果
* Created by wolfgy on 2017/10/16.
*/
public class TwoRecyclerViewAdapter extends RecyclerView.Adapter {
private SparseArray<TheFirstBean> firstBeanSparseArray = new SparseArray<>();//存储每日流水数据
private SparseArray<TheSecondBean> secondBeanSparseArray = new SparseArray<>();//存储每条流水数据
private static final int TYPE_FIRST = 0;//第一层布局
private static final int TYPE_SECOND = 1;//第二层布局
public TwoRecyclerViewAdapter(List<TheFirstBean> list) {
for (int i=0;i<list.size();i++){
firstBeanSparseArray.put(i,list.get(i));
}
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//根据ViewType实例化布局
switch (viewType){
case TYPE_FIRST:
return new FirstViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_recycler,parent,false));
case TYPE_SECOND:
return new FirstViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_recycler_detail,parent,false));
}
return new FirstViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_recycler,parent,false));
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
}
@Override
public int getItemCount() {
return firstBeanSparseArray.size()+secondBeanSparseArray.size();
}
@Override
public int getItemViewType(int position) {
if (secondBeanSparseArray.get(position) != null){
return TYPE_SECOND;
}
return TYPE_FIRST;
}
private class FirstViewHolder extends RecyclerView.ViewHolder{
public FirstViewHolder(final View itemView) {
super(itemView);
//item点击事件监听
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (firstBeanSparseArray.get(getLayoutPosition()).isExpand()){
//设置第二级布局是否展开的flag
firstBeanSparseArray.get(getLayoutPosition()).setExpand(false);
//获取要移除的第二级布局个数
int addedSubNum = firstBeanSparseArray.get(getLayoutPosition()).getAddedSubNum();
//移除第二级布局
removeItems(getLayoutPosition(),addedSubNum);
notifyItemRangeRemoved(getLayoutPosition()+1,addedSubNum);
}else{
//设置第二级布局是否展开的flag
firstBeanSparseArray.get(getLayoutPosition()).setExpand(true);
//加载数据并获取载入的第二级布局个数
List<TheSecondBean> list = new ArrayList<>();
for (int i =0;i<5;i++){
list.add(new TheSecondBean());
}
int addedSubNum = setEachFlows(getLayoutPosition(),list);
//添加第二级布局
firstBeanSparseArray.get(getLayoutPosition()).setAddedSubNum(addedSubNum);
notifyItemRangeInserted(getLayoutPosition()+1,addedSubNum);
}
}
});
}
}
private class SecondViewHolder extends RecyclerView.ViewHolder{
public SecondViewHolder(View itemView) {
super(itemView);
}
}
/**
* 点击展开时加载相应数据
* @param parentPosition
* @param list
* @return
*/
public int setEachFlows(int parentPosition , List<TheSecondBean> list) {
//更新position大于当前点击的position的第一级布局的item的position
for (int i = getItemCount()-1 ; i > parentPosition ; i-- ){
int index = firstBeanSparseArray.indexOfKey(i);
if (index<0){
continue;
}
TheFirstBean dailyFlow = firstBeanSparseArray.valueAt(index);
firstBeanSparseArray.removeAt(index);
firstBeanSparseArray.put(list.size()+i,dailyFlow);
}
//更新position大于当前点击的position的第二级布局的item的position
for (int i = getItemCount()-1 ; i > parentPosition ; i-- ){
int index = secondBeanSparseArray.indexOfKey(i);
if (index<0){
continue;
}
TheSecondBean eachFlow = secondBeanSparseArray.valueAt(index);
secondBeanSparseArray.removeAt(index);
secondBeanSparseArray.append(list.size()+i,eachFlow);
}
//把获取到的数据根据相应的position放入SparseArray中。
for (int i = 0 ;i < list.size() ; i++ ){
secondBeanSparseArray.put(parentPosition+i+1,list.get(i));
}
return list.size();
}
/**
* 点击折叠时移除相应数据
* @param clickPosition
* @param addedSubNum
*/
private void removeItems(int clickPosition,int addedSubNum){
//更新position大于当前点击的position的第一级布局item的position
SparseArray<TheFirstBean> temp = new SparseArray();
for (int i = getItemCount()-1 ; i > clickPosition+addedSubNum ; i-- ){
int index = firstBeanSparseArray.indexOfKey(i);
if (index<0){
continue;
}
TheFirstBean dailyFlow = firstBeanSparseArray.valueAt(index);
firstBeanSparseArray.removeAt(index);
temp.put(i-addedSubNum,dailyFlow);
}
for (int i=0;i<temp.size();i++ ){
int key = temp.keyAt(i);
firstBeanSparseArray.put(key,temp.get(key));
}
//更新position大于当前点击的position的第二级布局item的position
SparseArray<TheSecondBean> temp2 = new SparseArray();
for (int i = getItemCount()-1 ; i > clickPosition+addedSubNum ; i-- ){
int index = secondBeanSparseArray.indexOfKey(i);
if (index<0){
continue;
}
TheSecondBean eachFlow = secondBeanSparseArray.valueAt(index);
secondBeanSparseArray.removeAt(index);
temp2.put(i-addedSubNum,eachFlow);
}
for (int i = 1; i <= addedSubNum; i++) {
//移除被折叠的第二级布局数据
secondBeanSparseArray.remove(clickPosition+i);
}
for (int i=0;i<temp2.size();i++ ){
int key = temp2.keyAt(i);
secondBeanSparseArray.put(key,temp2.get(key));
}
}
}
以上。
希望我的文章对你能有所帮助。
我不能保证文中所有说法的百分百正确,但我能保证它们都是我的理解和感悟以及拒绝复制黏贴。
有什么意见、见解或疑惑,欢迎留言讨论。