Выделите выбранный элемент внутри RecyclerView

У меня возникли проблемы с выделением элемента в RecyclerView, аналогично настройке выбранного элемента в ListView.

На данный момент я настроил RecyclerView, имею LayoutManager по умолчанию и адаптер, который отображает все данные. Я только недавно заработал onClickListener() (хотя я не уверен, должен ли он идти в onCreateViewHolder() или onBindViewHolder*( - не уверен, когда вызывается onBindViewHolder()).

Я пробовал искать, и самое близкое, что я получил, это вопрос здесь. Однако я совершенно потерялся в этом вопросе. Где этот метод onClick() (внутри адаптера, внутри содержащего Activity/фрагмента и т. д.?). Что это viewHolderListener? Из чего состоит getPosition() и что именно он делает? По сути, я ничего не понял из этого вопроса, и это был лучший ресурс, который я смог найти до сих пор.

Вот мой текущий код для настройки RecyclerView:

// Sets up the main navigation
private void setupMainNav() {
    // use this setting to improve performance if you know that changes
    // in content do not change the layout size of the RecyclerView
    mMainRecyclerNav.setHasFixedSize(true);

    // use a linear layout manager
    LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
    mMainRecyclerNav.setLayoutManager(layoutManager);

    // Create and set the adapter for this recyclerView
    MainNavAdapter adapter = new MainNavAdapter(getActivity());
    mMainRecyclerNav.setAdapter(adapter);
}

Вот мой текущий код для адаптера:

class MainNavAdapter extends RecyclerView.Adapter<MainNavAdapter.ViewHolder> {
    Context mContext;

    // Holds the titles of every row
    String[] rowTitles;

    // Default constructor
    MainNavAdapter(Context context) {
        this.mContext = context;

        // Get the rowTitles - the necessary data for now - from resources
        rowTitles = context.getResources().getStringArray(R.array.nav_items);
    }

    // Simple class that holds all the views that need to be reused
    class ViewHolder extends RecyclerView.ViewHolder{
        View parentView; // The view which holds all the other views
        TextView rowTitle; // The title of this item

        // Default constructor, itemView holds all the views that need to be saved
        public ViewHolder(View itemView) {
            super(itemView);

            // Save the entire itemView, for setting listeners and usch later
            this.parentView = itemView;

            // Save the TextView- all that's supported at the moment
            this.rowTitle = (TextView) itemView.findViewById(R.id.row_title);
        }
    }

    // Create new views (invoked by the layout manager)
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
        // Create the view for this row
        View row = LayoutInflater.from(mContext)
                .inflate(R.layout.list_navmain_row, viewGroup, false);

        // Create a new viewHolder which caches all the views that needs to be saved
        ViewHolder viewHolder = new ViewHolder(row);

        return viewHolder;
    }

    // Replace the contents of a view (invoked by the layout manager)
    @Override
    public void onBindViewHolder(ViewHolder viewHolder, int i) {
        // - get element from your dataset at this position
        // - replace the contents of the view with that element
        viewHolder.rowTitle.setText(rowTitles[i]);

        // TODO: Make a better workaround for passing in the position to the listener
        final int position = i;



        // Set a listener for this entire view
        viewHolder.parentView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(getActivity(), "Clicked on row: " + position, Toast.LENGTH_SHORT).show();
            }
        });
    }

    // Return the size of your dataset (invoked by the layout manager)
    @Override
    public int getItemCount() {
        return rowTitles.length;
    }
}

Буду признателен за любую помощь. Спасибо.


person Jawad    schedule 09.12.2014    source источник
comment
может b это помочь выделить элемент списка в recycleview amolsawant88.blogspot.in/2015/08/   -  person Amol Sawant 96 Kuli    schedule 28.08.2015


Ответы (4)


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

Первое, что вы можете сделать, это объявить представление вашего элемента кликабельным в конструкторе `ViewHolder:

    public ViewHolder(View itemView) {
        super(itemView);

        // Make this view clickable
        itemView.setClickable(true);

        // ...
    } 

Затем, если вы хотите выделить выделение, вы должны отслеживать выбранные строки в вашем адаптере (например, с помощью списка индексов) и в вашем методе onBindViewHolder:

@Override 
public void onBindViewHolder(ViewHolder viewHolder, int i) {
    // mark  the view as selected:
    viewHolder.parentview.setSelected(mSelectedRows.contains(i));
} 

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

person XGouchet    schedule 11.02.2015
comment
Спасибо за ответ на этот старый открытый вопрос! - person Jawad; 14.02.2015
comment
@MDragon00 MDragon00 Если вам также нужно переместить выделение с помощью клавиатуры, проверьте мое решение здесь: stackoverflow.com/questions/27194044/ - person Greg Ennis; 03.03.2015
comment
Я думал, что onBindViewHolder предполагается использовать, когда необходимо отобразить элемент в строке. Я предполагаю, что вопрос касался выделения элемента только тогда, когда он был выбран, а не во время его создания. Почему тогда вы предложили использовать setSelectedметод внутри onBindViewHolder? Поскольку ваш ответ уже принят, вы можете ответить на мой вопрос :) stackoverflow.com/questions/29695811/ - person Cris; 17.04.2015
comment
Потому что метод onBind принимает переработанное представление, которое можно было выбрать или отменить ранее (и больше не использовать), и извлекает данные из адаптера внутри него. Вот почему вам нужно знать, был ли этот элемент уже выбран или не выбран. Проверьте мой github.com/davideas/FlexibleAdapter - person Davideas; 29.06.2015
comment
Это нормально для включения выбранного элемента в RecyclerView, но как насчет выключения элемента, когда выбрано что-то еще? Поскольку просмотры перерабатываются, позиции больше не действительны. Или я что-то упускаю? - person SMBiggs; 16.05.2016
comment
@ScottBiggs Позиции в вашей структуре данных все еще действительны, и вам предоставляется позиция в onBindHolder. Поэтому, если вы обновите свою структуру данных, которая содержит правильно выбранные элементы, тогда onBindViewHolder должен отображаться правильно. - person Shawn Lauzon; 09.06.2016
comment
@ScottBiggs Ааа, я понимаю, что ты говоришь. Да, я сейчас наблюдаю то же самое. Обычно это работает, сохраняя ссылку на представление, но поскольку они переработаны, это не работает, если вы прокручиваете экран слишком далеко. Я пока не нашел решения :( - person Shawn Lauzon; 09.06.2016
comment
Привет @ScottBiggs Смотрите мой ответ ниже, который решает проблему :) - person Shawn Lauzon; 09.06.2016

Есть простой способ сделать это:

  int row_index=0;
    @Override
    public void onBindViewHolder(MyViewHolder holder, final int position) {
        Movie movie = moviesList.get(position);
        holder.title.setText(movie.getTitle());
        holder.genre.setText(movie.getGenre());
        holder.year.setText(movie.getYear());

        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                row_index=position;
                notifyDataSetChanged();
            }
        });

        if(row_index==position){
            holder.itemView.setBackgroundColor(Color.parseColor("#567845"));
        }
        else
        {
            holder.itemView.setBackgroundColor(Color.parseColor("#ffffff"));
        }

    }
person Victor Ruiz.    schedule 19.01.2017
comment
вы используете notifyDataSetChanged, я думаю, вам следует использовать notifyItemChanged (mSelectedPosition); уведомить об изменении элемента (получить старую позицию ()); - person Android is everything for me; 09.05.2017

Я расширяю ответ @XGouchet, потому что, несмотря на то, что он поставил меня на правильный путь, мне все равно нужно было проделать дополнительную работу, чтобы поддерживать выбор строк AND UNSELECTING (например, переключатель). И @ScottBiggs, и я нуждались в такой поддержке.

Код удерживает mSelectedPosition, что позволяет правильно установить выбор в onBindViewHolder и mSelectedView, что позволяет правильно переключать выбор (как включать, так и выключать) при нажатии.

private class AsanaAdapter extends RecyclerView.Adapter<AsanaViewHolder> {
    private RecyclerView mRecyclerView;

    private final List<Asana> mAsanas;
    private int mSelectedPosition;
    private View mSelectedView;

    public AsanaAdapter(RecyclerView recyclerView, List<Asana> items) {
        mRecyclerView = recyclerView;
        mAsanas = items;
        setHasStableIds(true);
        setSelected(0);
    }

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

    @Override
    public AsanaViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View newView = getLayoutInflater().inflate(
                R.layout.performance_builder_list_content, parent, false);
        newView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (!v.isSelected()) {
                    // We are selecting the view clicked
                    if (mSelectedView != null) {
                        // deselect the previously selected view
                        mSelectedView.setSelected(false);
                    }
                    mSelectedPosition = mRecyclerView.getChildAdapterPosition(v);
                    mSelectedView = v;
                    setTitle(mAsanas.get(mSelectedPosition).getName());
                } else {
                    // We are deselecting the view clicked
                    setTitle("");
                    mSelectedPosition = -1;
                    mSelectedView = null;
                }

                // toggle the item clicked
                v.setSelected(!v.isSelected());
            }
        });
        return new AsanaViewHolder(newView);
    }

    @Override
    public void onBindViewHolder(AsanaViewHolder holder, int position) {
        if (position == mSelectedPosition) {
            holder.itemView.setSelected(true);

            // keep track of the currently selected view when recycled
            mSelectedView = holder.itemView;
        } else {
            holder.itemView.setSelected(false);
        }
    }
}
person Shawn Lauzon    schedule 09.06.2016
comment
Потребовалось немного времени, чтобы протестировать ваш код (мне пришлось переработать прослушиватели кликов и выяснить несколько нечетных вызовов методов), но, похоже, он работает нормально. Большой палец вверх, спасибо! - person SMBiggs; 13.06.2016

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

1.

class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
    @BindView(R.id.appIcon)
    ImageView appIcon;
    @BindView(R.id.appName)
    TextView appName;

    public ViewHolder(View itemView) {
        super(itemView);
        itemView.setOnClickListener(this);
        ButterKnife.bind(this, itemView);
    }

    @Override
    public void onClick(View v) {
        v.setSelected(true);
        new Handler().postDelayed(() -> v.setSelected(false), 100);
        //or new Handler().postDelayed(new Runnable...

        //your onClick action
    }
}
  1. создайте list_item_selector.xml в вашем каталоге Drawable

    <?xml version="1.0" encoding="utf-8"?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:state_selected="true" android:drawable="@color/colorPrimary" />
        <item android:drawable="@android:color/transparent" />
    </selector>
    
  2. Установите свой селектор в корневом макете вашего отдельного элемента

    android:background="@drawable/list_item_selector"
    
person Bartosz Wilk    schedule 21.07.2016