简体   繁体   中英

How to make single selection work in a nested RecyclerView

For our application I had to implement a nested RecyclerView. I'm getting a list of Tables from JSON and every Table has another list with groups from each table. I can get everything on the screen as requested, the problem is the selection.

I have 2 different RecyclerViews on the screen and I can not seem to get a single selection working in this environment, especially after scrolling. Every group and every table has a Toggle Button, and only one can be active at a time.

This is how the main screen looks like

So far I've tried putting a boolean isSelected on the Model but that didn't work out at all. The closest solution I came up with was a helper class that searches every CompoundButton on-screen and deselects them all when one is selected. The problem is this helper class cant get the Buttons which are off-screen.

How I populate ParentAdapter (in MainActivity):

public void setAdapter(List<Table> tableList)
    {
        RecyclerView recycler_view_parent = findViewById(R.id.recyclerparent);
        LinearLayoutManager manager=new LinearLayoutManager(MainActivity.this);
        manager.setOrientation(LinearLayoutManager.HORIZONTAL);
        recycler_view_parent.setLayoutManager(manager);
        recycler_view_parent.setHasFixedSize(true);
        recycler_view_parent.setItemViewCacheSize(tableList.size());
        ParentAdapter parentAdapter=new ParentAdapter(tableList,MainActivity.this);
        recycler_view_parent.setAdapter(parentAdapter);
    }

How i populate ChildAdapter (in onBindViewHolder of ParentAdapter):

FlexboxLayoutManager manager = new FlexboxLayoutManager(context);
            manager.setFlexDirection(FlexDirection.COLUMN);
            manager.setJustifyContent(JustifyContent.FLEX_START);
            manager.setFlexWrap(FlexWrap.WRAP);
            manager.setAlignItems(AlignItems.BASELINE);
            holder.recycler_view_child.setLayoutManager(manager);
            holder.recycler_view_child.setHasFixedSize(true);
            adapter = new ChildAdapter(tableList, tableList.get(position).getGroups(), context);
            holder.recycler_view_child.setAdapter(adapter);

The desired output should be only 1 Table OR Group at a time can be toggled (in total, not one from every RecyclerView) and the state should be the same after scrolling/device rotation).

I did a lot of research over the last days on this subject and I can not seem to find a working example of nested RecyclerView with single selection over both RVs.

So does anyone have an idea on how to solve this? I think the biggest issue is telling the Parent that a Button in Child was toggled and vice-versa.

I think for the ParentAdapter it should look something like this:

@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, final int position) {
    final Table table = tablelist.get(position);
    ViewHolder viewHolder = (ViewHolder) holder;

    if (table.isTableSelected()) {
        viewHolder.toggletable.setChecked(true);
        lastToggled = position;
    } else {
        viewHolder.toggletable.setChecked(false);
    }

    viewHolder.toggletable.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
            if (b) {
                table.setTableSelected(true);
// notify ChildAdapter and group.setGroupSelected(false)
                if (lastToggled >= 0) {
                    tablelist.get(lastToggled).setTableSelected(false);
// notify ChildAdapter and group.setGroupSelected(false)
                    notifyItemChanged(lastToggled);
                }
                lastToggled = position;
            } else {
                table.setTableSelected(false);
            }
        }
    });
}

Thanks in advance.

UPDATE: Managed to come up with a solution myself, although 100% sure, not the best approach.

First of all, implement Greenrobots EventBus:

implementation 'org.greenrobot:eventbus:3.1.1'

Now in the Activity where you hold both RecyclerViews register the Event listener:

 @Override
    protected void onStart() {
        super.onStart();
        EventBus.getDefault().register(this);
    }

and subscribe 2 methods. One for Parent Events and one for Children Events. This methods will trigger every time an item is selected!

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onParentEventClicked(ParentAdapter.ParentEvent event) {
// to access the inner adapter here you must set it to public in the ParentAdapter(public ChildAdapter adapter;)
        adapter.adapter.deSelectChild();
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onChildEventClicked(ChildAdapter.ChildEvent event) {
// normal ParentAdapter reference(ParentAdapter adapter;)
        adapter.deSelectParent();
    }

Inside your ParentAdapter create a method to deselect all parent items and a static class to fire the event:

public void deSelectParent()
    {
        for (int i=0;i<data.size();i++)
        {
            data.get(i).setSelected(false);
        }
        notifyDataSetChanged();
    }

public static class ParentEvent {
        View view;
        int position;
    }

Inside your ChildAdapter create a method to deselect all child items and a static class to fire the event:

public void deSelectChild()
    {
        for (int i=0;i<data.size();i++)
        {
            datachild.get(i).setSelected(false);
        }
        notifyDataSetChanged();
    }

public static class ChildEvent {
        View view;
        int position;
    }

now in both Parent and Child onBindViewHolders, you need similar logic for your models:

if (item.isSelected()) {
                holder.yourbutton.setChecked(true);
            } else {
                holder.yourbutton.setChecked(false);
            }

holder.yourbutton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    ParentEvent event = new ParentEvent();
                    event.view = holder.yourbutton;
                    event.position = position;
                    EventBus.getDefault().post(event);
                    if (holder.yourbutton.isChecked()) {
                        for (int i = 0; i < data.size(); i++) {
                            data.get(i).setSelected(false);
                        }
                        data.get(position).setSelected(true);
                    } else {
                        data.get(position).setSelected(false);
                    }
                    notifyDataSetChanged();
                }
            });

And thats pretty much it, every click on a ParentItem will trigger the deselect method for ChildAdapter and vice-versa. Due to the high usage of notifyDataSetChanged() I recommend using this line to get rid of the blinking:

 recycler_view_parent.setItemAnimator(null);

Any problems let me know!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM