Одиночный выбор в RecyclerView


Я знаю, что в классе recyclerview нет методов выбора по умолчанию, но я попытался следующим образом,

public void onBindViewHolder(ViewHolder holder, final int position) {
    holder.mTextView.setText(fonts.get(position).getName());
    holder.checkBox.setChecked(fonts.get(position).isSelected());

    holder.checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
            if(isChecked) {
                for (int i = 0; i < fonts.size(); i++) {
                    fonts.get(i).setSelected(false);
                }
                fonts.get(position).setSelected(isChecked);
            }
        }
    });
}

при попытке этого кода, я получил ожидаемый результат, но полностью нет.

я объясню вам с изображениями.

по умолчанию первый элемент выбирается из моего адаптера

затем я пытаюсь выбрать 2-й, а затем 3-й, а затем 4-й и, наконец, 5-й,

здесь только 5-й должен быть выбран,но все пять выбираются.

если я прокручиваю список вниз и снова возвращаюсь к началу,

Я получил то, что ожидал,

как я могу преодолеть эту проблему? И некоторое время, если я прокручиваю список очень быстро, выбирается какой-то другой элемент. Как преодолеть эту проблему?

обновление

пока я пытаюсь использовать notifyDataSetChanged() после fonts.get(position).setSelected(isChecked); я получил следующее исключение,

java.lang.IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling
        at android.support.v7.widget.RecyclerView.assertNotInLayoutOrScroll(RecyclerView.java:1462)
        at android.support.v7.widget.RecyclerView$RecyclerViewDataObserver.onChanged(RecyclerView.java:2982)
        at android.support.v7.widget.RecyclerView$AdapterDataObservable.notifyChanged(RecyclerView.java:7493)
        at android.support.v7.widget.RecyclerView$Adapter.notifyDataSetChanged(RecyclerView.java:4338)
        at com.app.myapp.screens.RecycleAdapter.onRowSelect(RecycleAdapter.java:111)

любое решение будет высоко ценится.

спасибо.

14 51

14 ответов:

решение проблемы:

public class yourRecyclerViewAdapter extends RecyclerView.Adapter<yourRecyclerViewAdapter.yourViewHolder> {

private static CheckBox lastChecked = null;
private static int lastCheckedPos = 0;

         ... ...

public void onBindViewHolder(ViewHolder holder, final int position) {

    holder.mTextView.setText(fonts.get(position).getName());
    holder.checkBox.setChecked(fonts.get(position).isSelected());
    holder.checkBox.setTag(new Integer(position));

    //for default check in first item
    if(position == 0 && fonts.get(0).isSelected() && holder.checkBox.isChecked())
    {
       lastChecked = holder.checkBox;
       lastCheckedPos = 0;
    }

    holder.checkBox.setOnClickListener(new View.OnClickListener() 
    {
        @Override
        public void onClick(View v) 
        {
           CheckBox cb = (CheckBox)v;
           int clickedPos = ((Integer)cb.getTag()).intValue(); 

           if(cb.isChecked)
           {
              if(lastChecked != null)
              {
                  lastChecked.setChecked(false);
                  fonts.get(lastCheckedPos).setSelected(false);
              }                       

              lastChecked = cb;
              lastCheckedPos = clickedPos;
          }
          else
             lastChecked = null;

          fonts.get(clickedPos).setSelected(cb.isChecked);
       }
   });
}
       ... ... 
}

надеюсь, что это поможет!

его слишком поздно все еще размещать это может помочь кому-то еще Используйте ниже код в качестве ссылки для проверки одного элемента в RecyclerView

/**
 * Created by subrahmanyam on 28-01-2016, 04:02 PM.
 */
public class SampleAdapter extends RecyclerView.Adapter<SampleAdapter.ViewHolder> {

    private final String[] list;
    private int lastCheckedPosition = -1;

    public SampleAdapter(String[] list) {
        this.list = list;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = View.inflate(parent.getContext(), R.layout.sample_layout, null);
        ViewHolder holder = new ViewHolder(view);
        return holder;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        holder.choiceName.setText(list[position]);
        holder.radioButton.setChecked(position == lastCheckedPosition);
    }

    @Override
    public int getItemCount() {
        return list.length;
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        @Bind(R.id.choice_name)
        TextView choiceName;
        @Bind(R.id.choice_select)
        RadioButton radioButton;

        public ViewHolder(View itemView) {
            super(itemView);
            ButterKnife.bind(this, itemView);
            radioButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    lastCheckedPosition = getAdapterPosition();
 //because of this blinking problem occurs so 
 //i have a suggestion to add notifyDataSetChanged();
                 //   notifyItemRangeChanged(0, list.length);//blink list problem
                      notifyDataSetChanged();

                }
            });
        }
    }
}

похоже, здесь играют две вещи:

(1) представления используются повторно, поэтому старый слушатель все еще присутствует.

(2) вы изменяете данные без уведомления адаптера об изменении.

Я буду обращаться к каждому по отдельности.

(1) вид повторного использования

в основном, в onBindViewHolder вам дается уже инициализированный ViewHolder, который уже содержит представление. Это ViewHolder может или не может быть ранее обязательно какие-то данные!

обратите внимание на этот бит кода прямо здесь:

holder.checkBox.setChecked(fonts.get(position).isSelected());

если держатель был ранее привязан, то у флажка уже есть прослушиватель для изменения состояния checked! Этот слушатель запускается в этот момент, что и было причиной вашего IllegalStateException.

простым решением было бы удалить слушателя перед вызовом setChecked. Элегантное решение потребует большего знания ваших взглядов - я призываю вас искать более приятный способ справиться с этим.

(2) уведомлять адаптер при изменении данных

прослушиватель в вашем коде изменяет состояние данных без уведомления адаптера о каких-либо последующих изменениях. Я не знаю, как ваши взгляды работают, так что это может быть или не быть проблемой. Как правило, когда состояние вашего изменения данных, вам нужно, чтобы адаптер знать об этом.

RecyclerView.Adapter имеет много вариантов на выбор, в том числе notifyItemChanged, что говорит о том, что определенный элемент изменил состояние. Это может быть хорошо для вашего использования

if(isChecked) {
    for (int i = 0; i < fonts.size(); i++) {
        if (i == position) continue;
        Font f = fonts.get(i);
        if (f.isSelected()) {
            f.setSelected(false);
            notifyItemChanged(i); // Tell the adapter this item is updated
        }
    }
    fonts.get(position).setSelected(isChecked);
    notifyItemChanged(position);
}

Это может помочь тем, кто хочет, чтобы один радиобуттон работал --> Переключатель RecycleView-Gist

если лямбда-выражения не поддерживаются, используйте вместо этого:

View.OnClickListener listener = new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        notifyItemChanged(mSelectedItem); // to update last selected item.
        mSelectedItem = getAdapterPosition();
    }
};

Ура

нужно убрать OnCheckedChangeListener перед setChecked():

@Override
public void onBindViewHolder(final ViewHolder holder, int position) {

    holder.mRadioButton.setOnCheckedChangeListener(null);
    holder.mRadioButton.setChecked(position == mCheckedPosition);
    holder.mRadioButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {

            mCheckedPosition = position;
            notifyDataSetChanged();
        }
    });
}

таким образом, это не вызовет java.lang.IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling ошибка.

просто использовать mCheckedPosition сохранить статус

@Override
        public void onBindViewHolder(ViewHolder holder, int position) {

            holder.checkBox.setChecked(position == mCheckedPostion);
            holder.checkBox.setOnClickListener(v -> {
                if (position == mCheckedPostion) {
                    holder.checkBox.setChecked(false);
                    mCheckedPostion = -1;
                } else {
                    mCheckedPostion = position;
                    notifyDataSetChanged();
                }
            });
        }
            public class GetStudentAdapter extends 
            RecyclerView.Adapter<GetStudentAdapter.MyViewHolder> {

            private List<GetStudentModel> getStudentList;
            Context context;
            RecyclerView recyclerView;

            public class MyViewHolder extends RecyclerView.ViewHolder {
                TextView textStudentName;
                RadioButton rbSelect;

                public MyViewHolder(View view) {
                    super(view);
                    textStudentName = (TextView) view.findViewById(R.id.textStudentName);
                    rbSelect = (RadioButton) view.findViewById(R.id.rbSelect);
                }


            }

            public GetStudentAdapter(Context context, RecyclerView recyclerView, List<GetStudentModel> getStudentList) {
                this.getStudentList = getStudentList;
                this.recyclerView = recyclerView;
                this.context = context;
            }

            @Override
            public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
                View itemView = LayoutInflater.from(parent.getContext())
                        .inflate(R.layout.select_student_list_item, parent, false);
                return new MyViewHolder(itemView);
            }

            @Override
            public void onBindViewHolder(final MyViewHolder holder, final int position) {
                holder.textStudentName.setText(getStudentList.get(position).getName());
                holder.rbSelect.setChecked(getStudentList.get(position).isSelected());
                holder.rbSelect.setTag(position); // This line is important.
                holder.rbSelect.setOnClickListener(onStateChangedListener(holder.rbSelect, position));

            }

            @Override
            public int getItemCount() {
                return getStudentList.size();
            }
            private View.OnClickListener onStateChangedListener(final RadioButton checkBox, final int position) {
                return new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        if (checkBox.isChecked()) {
                            for (int i = 0; i < getStudentList.size(); i++) {

                                getStudentList.get(i).setSelected(false);

                            }
                            getStudentList.get(position).setSelected(checkBox.isChecked());

                            notifyDataSetChanged();
                        } else {

                        }

                    }
                };
            }

        }

это происходит потому, что RecyclerView, как следует из названия, делает хорошую работу на обработка его ViewHolders. Это означает, что каждый ViewHolder, когда он уходит из поля зрения (на самом деле, это занимает немного больше, чем уход из поля зрения, но имеет смысл упростить его таким образом), он перерабатывается; это означает, что RecyclerView принимает этот ViewHolder, который уже раздут и заменяет его элементы элементами другого элемента в вашем наборе данных.

, что здесь происходит то, что как только вы прокрутите вниз и ваши первые, выбранные, ViewHolders исчезнут из поля зрения, они будут переработаны и использованы для других позиций Вашего набора данных. После того, как вы снова подниметесь, ViewHolders, которые были привязаны к первым 5 пунктам не обязательно то же самое, теперь.

вот почему вы должны держать внутренняя переменная в адаптере, которая запоминает состояние выбора каждого элемента. Таким образом, в onBindViewHolder метод, вы можете знать, если элемент, чей ViewHolder в настоящее время привязан, был выбран или нет, и соответственно измените вид, в этом случае состояние вашего RadioButton (хотя я бы предложил использовать флажок, если вы планируете выбрать несколько элементов).

Если вы хотите узнать больше о RecyclerView и его внутренней работе, я предлагаю вам проверить FancyAdapters, проект, который я начал на GitHub. Это набор адаптеров, которые реализуют выбор, drag & drop of элементы и салфетки уволить возможностей. Возможно, проверив код, вы сможете получить хорошее представление о том, как работает RecyclerView.

этот простой работал для меня

private RadioButton lastCheckedRB = null;
...
@Override
public void onBindViewHolder(final CoachListViewHolder holder, final int position) {
  holder.priceRadioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
    @Override
    public void onCheckedChanged(RadioGroup group, int checkedId) {
        RadioButton checked_rb = (RadioButton) group.findViewById(checkedId);
        if (lastCheckedRB != null) {
            lastCheckedRB.setChecked(false);
        }
        //store the clicked radiobutton
        lastCheckedRB = checked_rb;
    }
});

следующее Может быть полезно для RecyclerView с одним выбором.

три шага, чтобы сделать это, 1) объявить глобальную переменную типа integer,

private int mSelectedItem = -1;

2) в onBindViewHolder

 mRadio.setChecked(position == mSelectedItem);

3) в onClickListener

mSelectedItem = getAdapterPosition();
notifyItemRangeChanged(0, mSingleCheckList.size());
mAdapter.onItemHolderClick(SingleCheckViewHolder.this);

пожалуйста, попробуйте это... Это работает для меня..

в адаптере возьмите разреженный логический массив.

SparseBooleanArray sparseBooleanArray;

В конструкторе инициализировать этот,

   sparseBooleanArray=new SparseBooleanArray();

в держателе привязки добавить,

@Override
public void onBindViewHolder(DispositionViewHolder holder, final int position) {
   holder.tv_disposition.setText(dispList.get(position).getName());
    if(sparseBooleanArray.get(position,false))
    {
        holder.rd_disp.setChecked(true);
    }
    else
    {
        holder.rd_disp.setChecked(false);


    }
    setClickListner(holder,position);
}

private void setClickListner(final DispositionViewHolder holder, final int position) {
    holder.rd_disp.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            sparseBooleanArray.clear();
            sparseBooleanArray.put(position, true);
            notifyDataSetChanged();


        }
    });
}

rd_disp-это переключатель в xml-файле.

поэтому, когда вид recycler загружает элементы, в держателе bindView он проверяет, содержит ли sparseBooleanArray значение "true", соответствующее его положению.

Если возвращаемое значение равно true затем мы устанавливаем переключатель выбор true.Еще мы устанавливаем выбор false. В onclickHolder я очистил sparseArray и установил значение true, соответствующее этой позиции. Когда я вызываю notify datasetChange, он снова вызывает onBindViewHolder, и условие проверяется снова. Это делает наш выбор только для выбора конкретного радио.

Я хочу поделиться аналогичной вещью, которую я достиг, может быть, это поможет кому-то. ниже код из приложения, чтобы выбрать адрес из списка адресов, которые отображаются в cardview(cvAddress), Так что по щелчку конкретного item(cardview) the imageView внутри элемента должен быть установлен другой ресурс (выбрать/отменить выбор)

    @Override
public void onBindViewHolder(final AddressHolder holder, final int position)
{
    holderList.add(holder);
    holder.tvAddress.setText(addresses.get(position).getAddress());
    holder.cvAddress.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            selectCurrItem(position);
        }
    });
}

private void selectCurrItem(int position)
{
    int size = holderList.size();
    for(int i = 0; i<size; i++)
    {
        if(i==position)
            holderList.get(i).ivSelect.setImageResource(R.drawable.select);
        else
            holderList.get(i).ivSelect.setImageResource(R.drawable.unselect);
    }
}

Я не знаю, это лучшее решение или нет, но это сработало для меня.

вот как это выглядит

Single selection recyclerview best way

внутри вашего адаптера

private int selectedPosition = -1;

и onBindViewHolder

@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {

    if (selectedPosition == position) {
        holder.itemView.setSelected(true); //using selector drawable
        holder.tvText.setTextColor(ContextCompat.getColor(holder.tvText.getContext(),R.color.white));
    } else {
        holder.itemView.setSelected(false);
        holder.tvText.setTextColor(ContextCompat.getColor(holder.tvText.getContext(),R.color.black));
    }

    holder.itemView.setOnClickListener(v -> {
        if (selectedPosition >= 0)
            notifyItemChanged(selectedPosition);
        selectedPosition = holder.getAdapterPosition();
        notifyItemChanged(selectedPosition);
    });
}

вот оно! Как вы можете видеть, я просто уведомления(обновление) предыдущий выбранный элемент и вновь выбранный элемент

мой Drawable установить его в качестве фона для recyclerview дочерних представлений

<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="false" android:state_selected="true">
    <shape android:shape="rectangle">
        <solid android:color="@color/blue" />
    </shape>
</item>

вот как выглядит класс адаптера:

public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewHolder>{

Context context;
ArrayList<RouteDetailsFromFirestore> routeDetailsFromFirestoreArrayList_;
public int  lastSelectedPosition=-1;


public MyRecyclerViewAdapter(Context context, ArrayList<RouteDetailsFromFirestore> routeDetailsFromFirestoreArrayList)
{
    this.context = context;
    this.routeDetailsFromFirestoreArrayList_ = routeDetailsFromFirestoreArrayList;
}

@NonNull
@Override
public MyRecyclerViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i)
{
    // LayoutInflater layoutInflater = LayoutInflater.from(mainActivity_.getBaseContext());
    LayoutInflater layoutInflater = LayoutInflater.from(viewGroup.getContext());
    View view = layoutInflater.inflate(R.layout.route_details, viewGroup, false);
    return new MyRecyclerViewHolder(view);
}

@Override
public void onBindViewHolder(@NonNull final MyRecyclerViewHolder myRecyclerViewHolder, final int i) {

    /* This is the part where the appropriate checking and unchecking of radio button happens appropriately */
    myRecyclerViewHolder.mRadioButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
            if(b) {
                if (lastSelectedPosition != -1) {
     /* Getting the reference to the previously checked radio button and then unchecking it.lastSelectedPosition has the index of the previously selected radioButton */  
                    //RadioButton rb = (RadioButton) ((MainActivity) context).linearLayoutManager.getChildAt(lastSelectedPosition).findViewById(R.id.rbRadioButton);
                    RadioButton rb = (RadioButton) ((MainActivity) myRecyclerViewHolder.mRadioButton.getContext()).linearLayoutManager.getChildAt(lastSelectedPosition).findViewById(R.id.rbRadioButton);
                    rb.setChecked(false);
                }
                    lastSelectedPosition = i;
                    /* Checking the currently selected radio button */
                    myRecyclerViewHolder.mRadioButton.setChecked(true);
                }
        }
    });
}

@Override
public int getItemCount() {
    return routeDetailsFromFirestoreArrayList_.size();
}
} // End of Adapter Class

Внутри MainActivity.java мы называем ctor класса адаптера, как это. Контекст перешел в класс MainActivity в конструктор адаптера :

myRecyclerViewAdapter = new MyRecyclerViewAdapter(MainActivity.this, routeDetailsList);