28

I'm trying to figure out how to achieve the same effect of

mListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);

in a RecyclerView implementation. Please help.

Spike Flail
  • 683
  • 3
  • 8
  • 17

3 Answers3

30

There is no built-in support for a "choice mode" structure with RecyclerView. Your options are to either roll it yourself or use a third-party library that offers it. The DynamicRecyclerView library offers choice modes, but I have not tried it.

This sample app demonstrates implementing it yourself, in this case using the activated state to indicate which is the current choice. The overall pattern is:

  • Have your RecyclerView.ViewHolder detect a UI operation that indicates a choice (click on a row? click on a RadioButton in the row? etc.).

  • Keep track of the selection at the level of your RecyclerView.Adapter. In my case, a ChoiceCapableAdapter handles that, in conjunction with a SingleChoiceMode class that implements a ChoiceMode strategy.

  • When a choice is made, update the newly-chosen row to reflect the choice and update the previously-chosen row to reflect that it is no longer chosen. findViewHolderForPosition() on RecyclerView can help here -- if you track the position of the last choice, findViewHolderForPosition() can give you the ViewHolder for that choice, so you can "un-choose" it.

  • Keep track of the choice across configuration changes, by putting it in the saved instance state of the activity or fragment that is managing the RecyclerView.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • 1
    Hey, this solution seems pretty good but findViewHolderForPosition() is now deprecated. What can we use instead? – ThanosFisherman Jul 03 '15 at 01:34
  • 1
    @ThanosF: My gut instinct tells me this needs to change to `findViewHolderForAdapterPosition()`, as that seems to be a bit more conservative than is `findViewHolderForLayoutPosition()`. The now-deprecated `findViewHolderForPosition()` actually has the same behavior as `findViewHolderForLayoutPosition()`, so the latter would work, but it won't take into account any in-flight data set changes. – CommonsWare Jul 03 '15 at 10:50
  • Hm seems that both `findViewHolderForAdapterPosition()` and `findViewHolderForLayoutPosition()` have the same effect in your sample app which works fine but I don't understand the difference between them – ThanosFisherman Jul 03 '15 at 11:42
  • @ThanosF: Well, according to the docs, `findViewHolderForAdapterPosition()` will return `null` if there is an outstanding `notifyDataSetChanged()` call that has not yet been applied. `findViewHolderForLayoutPosition()` works solely off of the actual visible children of the `RelativeLayout`. So, for my sample, there probably is no net difference. – CommonsWare Jul 03 '15 at 11:43
  • Cool thanks I think I'm gonna go with the `findViewHolderForLayoutPosition()` in my case cause I need to have a item returned everytime no matter if `notifyDataSetChanged()` has been applied or not – ThanosFisherman Jul 03 '15 at 11:51
  • Unfortunatelly I couldn't manage to make it work in my app. I'll open a new question thread and try to explain exactly what I need – ThanosFisherman Jul 04 '15 at 08:26
  • could someone post the dependencies required to compile the library for "DynamicRecyclerView" – AlleyOOP Jul 20 '15 at 03:27
  • @CommonsWare i was able to use your demo project, but i am lacking one functionality as per my requirement that is if i want to make first item selected initially. how to make it work? – Vipul Asri Nov 15 '15 at 15:17
  • @vipul_asri: Presumably, call `onChecked(0, true)` in `onCreate()` after you create the adapter. If you have additional concerns, please ask a separate Stack Overflow question. – CommonsWare Nov 15 '15 at 15:21
  • I had done the same thing. Speaking about the support library, I find really confusing that the LayoutManager gets a chance to save its state while the Adapter does not. It does make sense conceptually, but then, there are pretty common things (like this) which 1. Can’t clearly be managed from a LayoutManager, and 2. Are state-dependent. – natario Feb 07 '16 at 12:45
1

I've created a library for this kind of choice mode applied to the RecyclerView, maybe it can help:

Description

This library has been created to help the integration of a multi-choice selection to the RecyclerView

Implementation

The integration with Gradle is very easy, you just need the jcenter repository and the library:

repositories {
    jcenter()
}
...

dependencies {
    compile 'com.davidecirillo.multichoicerecyclerview:multichoicerecyclerview:1.0.1'
}

Main steps for usage

Add the MultiChoiceRecyclerView to your xml file

<com.davidecirillo.multichoicesample.MultiChoiceRecyclerView
    android:id="@+id/multiChoiceRecyclerView"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

Instanciate you object and connect the view

MultiChoiceRecyclerView mMultiChoiceRecyclerView = (MultiChoiceRecyclerView) findViewById(R.id.multiChoiceRecyclerView);

Extend you adapter to the MultiChoiceAdapter and add it to the RecyclerView as per normal usage

public class MyAdapter extends MultiChoiceAdapter<MyViewHolder> {

    public MyAdapter(ArrayList<String> stringList, Context context) {
        this.mList = stringList;
        this.mContext = context;
    }

    ...
} 

MyAdapter myAdapter = new MyAdapter(mList, getApplicationContext());
mMultiChoiceRecyclerView.setAdapter(myAdapter);

For more information and customisations: https://github.com/dvdciri/MultiChoiceRecyclerView

dvdciri
  • 441
  • 6
  • 19
0

You can follow this:

– Data (String name, boolean selected)

– Adapter with itemClickListener

– Activity or fragment

– activity_main (recyclerView)

– list_item (TextView, CheckBox)

Data

public class MultipleData {

    private String mTitle;
    private boolean mBoolean;

    public MultipleData(String title, boolean mBoolean) {
        this.mTitle = title;
        this.mBoolean = mBoolean;
    }

    public String getTitle() {
        return mTitle;
    }

    public void setTitle(String mTitle) {
        this.mTitle = mTitle;
    }

    public boolean isBoolean() {
        return mBoolean;
    }

    public void setBoolean(boolean mBoolean) {
        this.mBoolean = mBoolean;
    }
}

Your views activity_main.xml (recyclerView)

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.thedeveloperworldisyours.fullrecycleview.multiple.MultipleFragment">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/multiple_fragment_recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</FrameLayout>

and list_item.xml (TextView, CheckBox)

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/multiple_list_item_text"
        android:layout_width="match_parent"
        android:layout_height="90dp"
        android:text="@string/app_name"
        android:typeface="monospace"
        android:layout_toLeftOf="@+id/multiple_list_item_check_button"
        android:gravity="center"
        android:textSize="@dimen/multiple_list_item_size_rock_stars"/>

    <RadioButton
        android:id="@+id/multiple_list_item_check_button"
        android:layout_width="wrap_content"
        android:layout_height="90dp"
        android:layout_alignParentRight="true"
        android:checked="false"
        android:clickable="false"
        android:focusable="false" />

</RelativeLayout>

Adapter with ClickListener

public class MultipleRecyclerViewAdapter extends RecyclerView
        .Adapter<MultipleRecyclerViewAdapter
        .DataObjectHolder> {

    private List<MultipleData> mList;
    private static MultipleClickListener sClickListener;

    MultipleRecyclerViewAdapter(List<MultipleData> mList) {
        this.mList = mList;
    }

    static class DataObjectHolder extends RecyclerView.ViewHolder
            implements View
            .OnClickListener {

        TextView mTextView;
        RadioButton mRadioButton;

         DataObjectHolder(View itemView) {
            super(itemView);
            mTextView = (TextView) itemView.findViewById(R.id.multiple_list_item_text);
            mRadioButton = (RadioButton) itemView.findViewById(R.id.multiple_list_item_check_button);
            itemView.setOnClickListener(this);
        }

        @Override
        public void onClick(View v) {
            sClickListener.onItemClick(getAdapterPosition(), v);
        }
    }

    void changedData(int position) {
            if (mList.get(position).isBoolean()) {
                mList.get(position).setBoolean(false);
            } else {
                mList.get(position).setBoolean(true);
            }
        notifyDataSetChanged();
    }

    void setOnItemClickListener(MultipleClickListener myClickListener) {
        this.sClickListener = myClickListener;
    }

    @Override
    public DataObjectHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.multiple_list_item, parent, false);

        DataObjectHolder dataObjectHolder = new DataObjectHolder(view);
        return dataObjectHolder;
    }

    @Override
    public void onBindViewHolder(DataObjectHolder holder, int position) {
        holder.mTextView.setText(mList.get(position).getTitle());
        holder.mRadioButton.setChecked(mList.get(position).isBoolean());
    }

    @Override
    public int getItemCount() {
        return mList.size();
    }

    interface MultipleClickListener {
        void onItemClick(int position, View v);
    }


}

Activity or fragment

public class MultipleFragment extends Fragment implements MultipleRecyclerViewAdapter.MultipleClickListener{

    MultipleRecyclerViewAdapter mAdapter;

    public MultipleFragment() {
        // Required empty public constructor
    }

    public static MultipleFragment newInstance() {
        return new MultipleFragment();
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.multiple_fragment, container, false);

        RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.multiple_fragment_recycler_view);

        MultipleData hendrix = new MultipleData("Jimi Hendrix", false);
        MultipleData bowie = new MultipleData("David Bowie", false);
        MultipleData morrison = new MultipleData("Jim Morrison", false);

        MultipleData presley = new MultipleData("Elvis Presley", false);
        MultipleData jagger = new MultipleData("Mick Jagger", false);
        MultipleData cobain = new MultipleData("Kurt Cobain", false);

        MultipleData dylan = new MultipleData("Bob Dylan", false);
        MultipleData lennon = new MultipleData("John Lennon", false);
        MultipleData mercury = new MultipleData("Freddie Mercury", false);

        MultipleData elton = new MultipleData("Elton John", false);
        MultipleData clapton = new MultipleData("Eric Clapton", false);

        List<MultipleData> list = new ArrayList<>();
        list.add(0, hendrix);
        list.add(1, bowie);
        list.add(2, morrison);

        list.add(3, presley);
        list.add(4, jagger);
        list.add(5, cobain);

        list.add(6, dylan);
        list.add(7, lennon);
        list.add(8, mercury);

        list.add(9, elton);
        list.add(10, clapton);

        mAdapter = new MultipleRecyclerViewAdapter(list);
        recyclerView.setAdapter(mAdapter);
        recyclerView.setHasFixedSize(true);
        RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getActivity());

        recyclerView.setLayoutManager(layoutManager);
        mAdapter.setOnItemClickListener(this);

        return view;
    }

    @Override
    public void onItemClick(int position, View v) {
        mAdapter.changedData(position);
    }
}

You can see this example in GitHub and this post for multiple choice, and this post for single choice Happy code!!!

Cabezas
  • 9,329
  • 7
  • 67
  • 69