前言
最近碰到了RecyclerView展開折疊的需求。
在CSDN上看到了一種寫法是在RecyclerView的Item布局外面嵌套了一層LinearLayout。
但是這樣子做有一個(gè)不好的地方是需要取消RecyclerView的重用機(jī)制。
后來又在同事的建議下嘗試了只使用一層RecyclerView來實(shí)現(xiàn)展開折疊的實(shí)現(xiàn)方式。
本文會(huì)把兩種寫法都記錄下來,大家可以直接去看第二種實(shí)現(xiàn)方式。
畢竟從性能等方面來考慮,只用一層RecyclerView無疑是最優(yōu)解。
(于2017.11.26更新,提供了單層RecyclerView實(shí)現(xiàn)點(diǎn)擊展開/折疊的帶數(shù)據(jù)加載、移除的寫法。)
(于2017.12.26更新,修改github指向地址。增加首發(fā)說明。)
本文由作者三汪首發(fā)于簡(jiǎn)書。
本文demo已上傳Github→戳這里

效果展示
視頻轉(zhuǎn)換gif出了點(diǎn)問題,展示效果不好希望大家見諒。
一、嵌套實(shí)現(xiàn)
1.要點(diǎn)說明
- 要記得在
onCreateViewHolder()中取消Recycler的重用機(jī)制viewHolder.setIsRecyclable(false);否則會(huì)出現(xiàn)重復(fù)添加子布局和在未點(diǎn)擊展開時(shí)展開子布局的情況。 - 代碼中使用了item外嵌套的LinearLayout的
setTag()、getTag()來判斷當(dāng)前點(diǎn)擊要觸發(fā)的動(dòng)作是展開還是折疊。當(dāng)item離開當(dāng)前屏幕時(shí)就會(huì)被銷毀,因此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的重用機(jī)制。沒有這句話子布局subView會(huì)被重復(fù)添加。
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點(diǎn)擊事件監(jiān)聽
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int flag = 1;//用于判斷當(dāng)前是展開還是收縮狀態(tài)
//獲取外層linearlayout布局
LinearLayout linearLayout = view.findViewById(R.id.main_tree_root_layout);
//new一個(gè)RecyclerView來當(dāng)展開的子布局。
RecyclerView subView = new RecyclerView(view.getContext());
SubViewAdapter adapter = new SubViewAdapter();
subView.setLayoutManager(new LinearLayoutManager(view.getContext()));
subView.setAdapter(adapter);
//當(dāng)flag不為空的時(shí)候,獲取flag的值。
if (linearLayout.getTag() != null) {
flag = (int) linearLayout.getTag();
}
//當(dāng)flag為1時(shí),添加子布局。否則,移除子布局。
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實(shí)現(xiàn)
1.要點(diǎn)說明
- 當(dāng)前寫法采用了兩個(gè)SparseArray來分別保存第一級(jí)布局和第二級(jí)布局的數(shù)據(jù)對(duì)象。
- 第一級(jí)布局的數(shù)據(jù)對(duì)象中增加了
isExpand字段來標(biāo)志當(dāng)前布局是否被點(diǎn)擊展開過,以及增加了addedSubNum字段來儲(chǔ)存點(diǎn)擊展開后新增的item個(gè)數(shù)。 - 點(diǎn)擊折疊移除數(shù)據(jù)時(shí),由于SparseArray的特性,必須使用一個(gè)新的SparseArray來作為中轉(zhuǎn),暫時(shí)保存需要修改key的數(shù)據(jù)。等remove動(dòng)作做完以后再重新put回原來的SparseArray。
2.代碼展示(僅展示主要代碼)
TheFirstBean.java
/**
* 第一層布局?jǐn)?shù)據(jù)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);
//數(shù)據(jù)初始化
List<TheFirstBean> list = new ArrayList<>();
for (int i = 0; i<20;i++){
list.add(new TheFirstBean());
}
//設(shè)置RecyclerView
recyclerView = (RecyclerView) findViewById(R.id.recycler_two);
recyclerView.setAdapter(new TwoRecyclerViewAdapter(list));
recyclerView.setLayoutManager(new LinearLayoutManager(this));
}
}
TwoRecyclerViewAdapter .java
/**
* 使用一層recyclerView實(shí)現(xiàn)點(diǎn)擊展開二層布局效果
* Created by wolfgy on 2017/10/16.
*/
public class TwoRecyclerViewAdapter extends RecyclerView.Adapter {
private SparseArray<TheFirstBean> firstBeanSparseArray = new SparseArray<>();//存儲(chǔ)每日流水?dāng)?shù)據(jù)
private SparseArray<TheSecondBean> secondBeanSparseArray = new SparseArray<>();//存儲(chǔ)每條流水?dāng)?shù)據(jù)
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) {
//根據(jù)ViewType實(shí)例化布局
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點(diǎn)擊事件監(jiān)聽
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (firstBeanSparseArray.get(getLayoutPosition()).isExpand()){
//設(shè)置第二級(jí)布局是否展開的flag
firstBeanSparseArray.get(getLayoutPosition()).setExpand(false);
//獲取要移除的第二級(jí)布局個(gè)數(shù)
int addedSubNum = firstBeanSparseArray.get(getLayoutPosition()).getAddedSubNum();
//移除第二級(jí)布局
removeItems(getLayoutPosition(),addedSubNum);
notifyItemRangeRemoved(getLayoutPosition()+1,addedSubNum);
}else{
//設(shè)置第二級(jí)布局是否展開的flag
firstBeanSparseArray.get(getLayoutPosition()).setExpand(true);
//加載數(shù)據(jù)并獲取載入的第二級(jí)布局個(gè)數(shù)
List<TheSecondBean> list = new ArrayList<>();
for (int i =0;i<5;i++){
list.add(new TheSecondBean());
}
int addedSubNum = setEachFlows(getLayoutPosition(),list);
//添加第二級(jí)布局
firstBeanSparseArray.get(getLayoutPosition()).setAddedSubNum(addedSubNum);
notifyItemRangeInserted(getLayoutPosition()+1,addedSubNum);
}
}
});
}
}
private class SecondViewHolder extends RecyclerView.ViewHolder{
public SecondViewHolder(View itemView) {
super(itemView);
}
}
/**
* 點(diǎn)擊展開時(shí)加載相應(yīng)數(shù)據(jù)
* @param parentPosition
* @param list
* @return
*/
public int setEachFlows(int parentPosition , List<TheSecondBean> list) {
//更新position大于當(dāng)前點(diǎn)擊的position的第一級(jí)布局的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大于當(dāng)前點(diǎn)擊的position的第二級(jí)布局的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);
}
//把獲取到的數(shù)據(jù)根據(jù)相應(yīng)的position放入SparseArray中。
for (int i = 0 ;i < list.size() ; i++ ){
secondBeanSparseArray.put(parentPosition+i+1,list.get(i));
}
return list.size();
}
/**
* 點(diǎn)擊折疊時(shí)移除相應(yīng)數(shù)據(jù)
* @param clickPosition
* @param addedSubNum
*/
private void removeItems(int clickPosition,int addedSubNum){
//更新position大于當(dāng)前點(diǎn)擊的position的第一級(jí)布局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大于當(dāng)前點(diǎn)擊的position的第二級(jí)布局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++) {
//移除被折疊的第二級(jí)布局?jǐn)?shù)據(jù)
secondBeanSparseArray.remove(clickPosition+i);
}
for (int i=0;i<temp2.size();i++ ){
int key = temp2.keyAt(i);
secondBeanSparseArray.put(key,temp2.get(key));
}
}
}
以上。
希望我的文章對(duì)你能有所幫助。
我不能保證文中所有說法的百分百正確,但我能保證它們都是我的理解和感悟以及拒絕復(fù)制黏貼。
有什么意見、見解或疑惑,歡迎留言討論。