美文网首页Android效果/自定义Android知识Android知识点和文章分享
RecyclerView点击展开、折叠效果的两种实现方式(只使用

RecyclerView点击展开、折叠效果的两种实现方式(只使用

作者: 三汪 | 来源:发表于2017-10-17 17:50 被阅读819次

    前言

    最近碰到了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));
            }
        }
    
    }
    

    以上。
    希望我的文章对你能有所帮助。
    我不能保证文中所有说法的百分百正确,但我能保证它们都是我的理解和感悟以及拒绝复制黏贴。
    有什么意见、见解或疑惑,欢迎留言讨论。

    相关文章

      网友评论

      • 夜星星星:父布局之间是大的分割线比如高10dp,展开后子布局间隔线是1dp,这样能做到么
        三汪:@夜星星星 你的代码逻辑不对。应该设置最后一个子展开的分割线才对
        夜星星星:@三汪 现在做法是子展开直接在父布局下面了,你可以试下把父布局分割线设为10,子展开后子是在父分割线下方,而不是在父分割线上方;看起来并没有真的展开。
        三汪:可以啊。想让它是多少还不是你代码决定的

      本文标题:RecyclerView点击展开、折叠效果的两种实现方式(只使用

      本文链接:https://www.haomeiwen.com/subject/mvtduxtx.html