前言
最近碰到了RecyclerView展開折疊的需求狠持。
在CSDN上看到了一種寫法是在RecyclerView的Item布局外面嵌套了一層LinearLayout。
但是這樣子做有一個不好的地方是需要取消RecyclerView的重用機制瞻润。
后來又在同事的建議下嘗試了只使用一層RecyclerView來實現展開折疊的實現方式喘垂。
本文會把兩種寫法都記錄下來,大家可以直接去看第二種實現方式绍撞。
畢竟從性能等方面來考慮正勒,只用一層RecyclerView無疑是最優(yōu)解。
(于2017.11.26更新傻铣,提供了單層RecyclerView實現點擊展開/折疊的帶數據加載章贞、移除的寫法。)
(于2017.12.26更新非洲,修改github指向地址鸭限。增加首發(fā)說明蜕径。)
本文由作者三汪首發(fā)于簡書。
本文demo已上傳Github→戳這里
視頻轉換gif出了點問題败京,展示效果不好希望大家見諒兜喻。
一、嵌套實現
1.要點說明
- 要記得在
onCreateViewHolder()
中取消Recycler的重用機制viewHolder.setIsRecyclable(false);
否則會出現重復添加子布局和在未點擊展開時展開子布局的情況喧枷。 - 代碼中使用了item外嵌套的LinearLayout的
setTag()
虹统、getTag()
來判斷當前點擊要觸發(fā)的動作是展開還是折疊。當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點擊事件監(jiān)聽
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int flag = 1;//用于判斷當前是展開還是收縮狀態(tài)
//獲取外層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點擊事件監(jiān)聽
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));
}
}
}
以上。
希望我的文章對你能有所幫助观堂。
我不能保證文中所有說法的百分百正確让网,但我能保證它們都是我的理解和感悟以及拒絕復制黏貼。
有什么意見师痕、見解或疑惑寂祥,歡迎留言討論。