Как правильно выделить выбранный элемент на RecyclerView?
Я пытаюсь использовать RecyclerView как горизонтальная ListView. Я пытаюсь выяснить, как выделить выбранный элемент. Когда я нажимаю на один из элементов, он выбирается и подсвечивается правильно, но когда я нажимаю на другой, второй подсвечивается более старым.
вот моя функция onClick:
@Override
public void onClick(View view) {
if(selectedListItem!=null){
Log.d(TAG, "selectedListItem " + getPosition() + " " + item);
selectedListItem.setBackgroundColor(Color.RED);
}
Log.d(TAG, "onClick " + getPosition() + " " + item);
viewHolderListener.onIndexChanged(getPosition());
selectedPosition = getPosition();
view.setBackgroundColor(Color.CYAN);
selectedListItem = view;
}
здесь onBindViewHolder:
@Override
public void onBindViewHolder(ViewHolder viewHolder, int position) {
viewHolder.setItem(fruitsData[position]);
if(selectedPosition == position)
viewHolder.itemView.setBackgroundColor(Color.CYAN);
else
viewHolder.itemView.setBackgroundColor(Color.RED);
}
14 ответов:
Я написал базовый класс адаптера для автоматической обработки выбора элемента с помощью RecyclerView. Просто выведите свой адаптер из него и используйте рисованные списки состояний с state_selected, как и в представлении списка.
У меня есть Сообщение В Блоге Здесь об этом, но вот код:
public abstract class TrackSelectionAdapter<VH extends TrackSelectionAdapter.ViewHolder> extends RecyclerView.Adapter<VH> { // Start with first item selected private int focusedItem = 0; @Override public void onAttachedToRecyclerView(final RecyclerView recyclerView) { super.onAttachedToRecyclerView(recyclerView); // Handle key up and key down and attempt to move selection recyclerView.setOnKeyListener(new View.OnKeyListener() { @Override public boolean onKey(View v, int keyCode, KeyEvent event) { RecyclerView.LayoutManager lm = recyclerView.getLayoutManager(); // Return false if scrolled to the bounds and allow focus to move off the list if (event.getAction() == KeyEvent.ACTION_DOWN) { if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) { return tryMoveSelection(lm, 1); } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) { return tryMoveSelection(lm, -1); } } return false; } }); } private boolean tryMoveSelection(RecyclerView.LayoutManager lm, int direction) { int tryFocusItem = focusedItem + direction; // If still within valid bounds, move the selection, notify to redraw, and scroll if (tryFocusItem >= 0 && tryFocusItem < getItemCount()) { notifyItemChanged(focusedItem); focusedItem = tryFocusItem; notifyItemChanged(focusedItem); lm.scrollToPosition(focusedItem); return true; } return false; } @Override public void onBindViewHolder(VH viewHolder, int i) { // Set selected state; use a state list drawable to style the view viewHolder.itemView.setSelected(focusedItem == i); } public class ViewHolder extends RecyclerView.ViewHolder { public ViewHolder(View itemView) { super(itemView); // Handle item click and set the selection itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // Redraw the old selection and the new notifyItemChanged(focusedItem); focusedItem = getLayoutPosition(); notifyItemChanged(focusedItem); } }); } } }
Это очень простой способ сделать это.
есть
private int selectedPos = RecyclerView.NO_POSITION;в классе адаптера RecyclerView и в методе onBindViewHolder попробуйте:@Override public void onBindViewHolder(ViewHolder viewHolder, int position) { viewHolder.itemView.setSelected(selectedPos == position); }и в вашем событии OnClick изменить:
@Override public void onClick(View view) { notifyItemChanged(selectedPos); selectedPos = getLayoutPosition(); notifyItemChanged(selectedPos); }работает как шарм для навигационного ящика и других адаптеров элементов RecyclerView.
Я сделал это без использования каких-либо дополнительных методов :
обновление [26 / Jul / 2017]:
как Паван упоминается в комментарии об этом IDE предупреждение о том, чтобы не использовать это фиксированная позиция, я только что изменил свой код, как показано ниже. Щелчок слушатель перемещается в
ViewHolderи там я получаю позицию используяgetAdapterPosition()способ:int selected_position = 0; // You have to set this globally in the Adapter class @Override public void onBindViewHolder(ViewHolder holder, int position) { Item item = items.get(position); // Here I am just highlighting the background holder.itemView.setBackgroundColor(selected_position == position ? Color.GREEN : Color.TRANSPARENT); } public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { public ViewHolder(View itemView) { super(itemView); itemView.setOnClickListener(this); } @Override public void onClick(View v) { // Below line is just like a safety check, because sometimes holder could be null, // in that case, getAdapterPosition() will return RecyclerView.NO_POSITION if (getAdapterPosition() == RecyclerView.NO_POSITION) return; // Updating old as well as new positions notifyItemChanged(selected_position); selected_position = getAdapterPosition(); notifyItemChanged(selected_position); // Do your another stuff for your onClick } }надеюсь, что это поможет.
ваша реализация, вероятно, работает, если вы прокручиваете содержимое из поля зрения, а затем обратно в поле зрения. У меня была аналогичная проблема, когда я наткнулся на ваш вопрос.
следующий фрагмент файла работает для меня. Моя реализация была направлена на множественный выбор, но я бросил туда хак, чтобы заставить один выбор.(*1)
// an array of selected items (Integer indices) private final ArrayList<Integer> selected = new ArrayList<>(); // items coming into view @Override public void onBindViewHolder(final ViewHolder holder, final int position) { // each time an item comes into view, its position is checked // against "selected" indices if (!selected.contains(position)){ // view not selected holder.parent.setBackgroundColor(Color.LTGRAY); } else // view is selected holder.parent.setBackgroundColor(Color.CYAN); } // selecting items @Override public boolean onLongClick(View v) { // set color immediately. v.setBackgroundColor(Color.CYAN); // (*1) // forcing single selection here if (selected.isEmpty()){ selected.add(position); }else { int oldSelected = selected.get(0); selected.clear(); selected.add(position); // we do not notify that an item has been selected // because that work is done here. we instead send // notifications for items to be deselected notifyItemChanged(oldSelected); } return false; }Как отмечает в этом связанном вопросе, установка слушателей для viewHolders должна быть выполнена в onCreateViewHolder. Я забыл чтобы упомянуть об этом ранее.
Я думаю, я нашел лучший учебник о том, как использовать RecyclerView со всеми основными функциями, которые нам нужны (single+multiselection, highlight, ripple, click and remove in multiselection и т. д...).
вот он-->http://enoent.fr/blog/2015/01/18/recyclerview-basics/
на основе этого я смог создать библиотеку "FlexibleAdapter", которая расширяет SelectableAdapter. Я думаю, что это должно быть ответственность адаптера, на самом деле вам не нужно чтобы каждый раз переписывать основные функции адаптера, пусть это сделает библиотека, поэтому вы можете просто повторно использовать одну и ту же реализацию.
этот адаптер очень быстрый, он работает из коробки (вам не нужно его расширять); вы настраиваете элементы для всех типов представлений, которые вам нужны; ViewHolder предопределены: общие события уже реализованы: одиночный и длинный щелчок; он поддерживает состояние после поворота и много.
пожалуйста, посмотрите и не стесняйтесь чтобы реализовать его в своих проектах.
https://github.com/davideas/FlexibleAdapter
Вики также доступна.
посмотрите на мое решение. Я полагаю, что вы должны установить выбранную позицию в держателе и передать ее как тег представления. Представление должно быть установлено в onCreateViewHolder(...) метод. Существует также правильное место для установки прослушивателя для просмотра, такого как OnClickListener или LongClickListener.
пожалуйста, посмотрите на пример ниже и прочитать комментарии к коду.
public class MyListAdapter extends RecyclerView.Adapter<MyListAdapter.ViewHolder> { //Here is current selection position private int mSelectedPosition = 0; private OnMyListItemClick mOnMainMenuClickListener = OnMyListItemClick.NULL; ... // constructor, method which allow to set list yourObjectList @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { //here you prepare your view // inflate it // set listener for it final ViewHolder result = new ViewHolder(view); final View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.your_view_layout, parent, false); view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { //here you set your current position from holder of clicked view mSelectedPosition = result.getAdapterPosition(); //here you pass object from your list - item value which you clicked mOnMainMenuClickListener.onMyListItemClick(yourObjectList.get(mSelectedPosition)); //here you inform view that something was change - view will be invalidated notifyDataSetChanged(); } }); return result; } @Override public void onBindViewHolder(ViewHolder holder, int position) { final YourObject yourObject = yourObjectList.get(position); holder.bind(yourObject); if(mSelectedPosition == position) holder.itemView.setBackgroundColor(Color.CYAN); else holder.itemView.setBackgroundColor(Color.RED); } // you can create your own listener which you set for adapter public void setOnMainMenuClickListener(OnMyListItemClick onMyListItemClick) { mOnMainMenuClickListener = onMyListItemClick == null ? OnMyListItemClick.NULL : onMyListItemClick; } static class ViewHolder extends RecyclerView.ViewHolder { ViewHolder(View view) { super(view); } private void bind(YourObject object){ //bind view with yourObject } } public interface OnMyListItemClick { OnMyListItemClick NULL = new OnMyListItemClick() { @Override public void onMyListItemClick(YourObject item) { } }; void onMyListItemClick(YourObject item); } }
в RecyclerView нет селектора, такого как ListView и GridView, но вы пытаетесь ниже, что это сработало для меня
создать селектор drawable, как показано ниже
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_pressed="true"> <shape> <solid android:color="@color/blue" /> </shape> </item> <item android:state_pressed="false"> <shape> <solid android:color="@android:color/transparent" /> </shape> </item> </selector>затем установите этот drawable в качестве фона вашего макета строки RecyclerView как
android:background="@drawable/selector"
Это мое решение, вы можете установить на товар (или группу) и снимите его с другой кнопки:
private final ArrayList<Integer> seleccionados = new ArrayList<>(); @Override public void onBindViewHolder(final ViewHolder viewHolder, final int i) { viewHolder.san.setText(android_versions.get(i).getAndroid_version_name()); if (!seleccionados.contains(i)){ viewHolder.inside.setCardBackgroundColor(Color.LTGRAY); } else { viewHolder.inside.setCardBackgroundColor(Color.BLUE); } viewHolder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (seleccionados.contains(i)){ seleccionados.remove(seleccionados.indexOf(i)); viewHolder.inside.setCardBackgroundColor(Color.LTGRAY); } else { seleccionados.add(i); viewHolder.inside.setCardBackgroundColor(Color.BLUE); } } }); }
решение с интерфейсами и обратными вызовами. Создать интерфейс с выберите и отмените выбор государства:
public interface ItemTouchHelperViewHolder { /** * Called when the {@link ItemTouchHelper} first registers an item as being moved or swiped. * Implementations should update the item view to indicate it's active state. */ void onItemSelected(); /** * Called when the {@link ItemTouchHelper} has completed the move or swipe, and the active item * state should be cleared. */ void onItemClear(); }реализовать интерфейс в ViewHolder:
public static class ItemViewHolder extends RecyclerView.ViewHolder implements ItemTouchHelperViewHolder { public LinearLayout container; public PositionCardView content; public ItemViewHolder(View itemView) { super(itemView); container = (LinearLayout) itemView; content = (PositionCardView) itemView.findViewById(R.id.content); } @Override public void onItemSelected() { /** * Here change of item */ container.setBackgroundColor(Color.LTGRAY); } @Override public void onItemClear() { /** * Here change of item */ container.setBackgroundColor(Color.WHITE); } }выполнить изменение состояния при обратном вызове:
public class ItemTouchHelperCallback extends ItemTouchHelper.Callback { private final ItemTouchHelperAdapter mAdapter; public ItemTouchHelperCallback(ItemTouchHelperAdapter adapter) { this.mAdapter = adapter; } @Override public boolean isLongPressDragEnabled() { return true; } @Override public boolean isItemViewSwipeEnabled() { return true; } @Override public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN; int swipeFlags = ItemTouchHelper.END; return makeMovementFlags(dragFlags, swipeFlags); } @Override public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { ... } @Override public void onSwiped(final RecyclerView.ViewHolder viewHolder, int direction) { ... } @Override public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) { if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) { if (viewHolder instanceof ItemTouchHelperViewHolder) { ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder; itemViewHolder.onItemSelected(); } } super.onSelectedChanged(viewHolder, actionState); } @Override public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { super.clearView(recyclerView, viewHolder); if (viewHolder instanceof ItemTouchHelperViewHolder) { ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder; itemViewHolder.onItemClear(); } } }создать RecyclerView с обратным вызовом (пример):
mAdapter = new BuyItemsRecyclerListAdapter(MainActivity.this, positionsList, new ArrayList<BuyItem>()); positionsList.setAdapter(mAdapter); positionsList.setLayoutManager(new LinearLayoutManager(this)); ItemTouchHelper.Callback callback = new ItemTouchHelperCallback(mAdapter); mItemTouchHelper = new ItemTouchHelper(callback); mItemTouchHelper.attachToRecyclerView(positionsList);Смотрите больше в статье iPaulPro: https://medium.com/@ipaulpro/drag-and-swipe-with-recyclerview-6a6f0c422efd#.6gh29uaaz
Я имел же проблема и я решаю ее следующим образом:
xml-файл, который используется для создания строки внутри createViewholder, просто добавьте строку ниже:
android:clickable="true" android:focusableInTouchMode="true" android:background="?attr/selectableItemBackgroundBorderless"или если вы используете frameLayout в качестве родителя элемента строки, то:
android:clickable="true" android:focusableInTouchMode="true" android:foreground="?attr/selectableItemBackgroundBorderless"в Java Code inside view holder, где вы добавили на click listener:
@Override public void onClick(View v) { //ur other code here v.setPressed(true); }
Я не мог найти хорошее решение для этой проблемы и решить ее сам. Есть много людей, страдающих от этой проблемы. Поэтому я хочу поделиться своим решением здесь.
при прокрутке строки перерабатываются. Таким образом, отмеченные флажки и выделенные строки не работают должным образом. Я решил эту проблему, написав ниже класс адаптера.
Я также реализую полный проект. В этом проекте можно установить несколько флажков. Строки, включая выбранные флажки подсвечиваются. И что еще более важно, они не теряются при прокрутке. Вы можете скачать его по ссылке :
https://www.dropbox.com/s/ssm58w62gw32i29/recyclerView_checkbox_highlight.zip?dl=0
public class RV_Adapter extends RecyclerView.Adapter<RV_Adapter.ViewHolder> { public ArrayList<String> list; boolean[] checkBoxState; MainActivity mainActivity; MyFragment myFragment; View firstview; private Context context; FrameLayout framelayout; public RV_Adapter() { } public RV_Adapter(Context context, MyFragment m, ArrayList<String> list ) { this.list = list; myFragment = m; this.context = context; mainActivity = (MainActivity) context; checkBoxState = new boolean[list.size()]; // relativeLayoutState = new boolean[list.size()]; } public class ViewHolder extends RecyclerView.ViewHolder { public TextView textView; public CheckBox checkBox; RelativeLayout relativeLayout; MainActivity mainActivity; MyFragment myFragment; public ViewHolder(View v,MainActivity mainActivity,MyFragment m) { super(v); textView = (TextView) v.findViewById(R.id.tv_foodname); /**/ checkBox= (CheckBox) v.findViewById(R.id.checkBox); relativeLayout = (RelativeLayout)v.findViewById(R.id.relativelayout); this.mainActivity = mainActivity; this.myFragment = m; framelayout = (FrameLayout) v.findViewById(R.id.framelayout); framelayout.setOnLongClickListener(m); } } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); firstview = inflater.inflate(R.layout.row, parent, false); return new ViewHolder(firstview,mainActivity, myFragment); } @Override public void onBindViewHolder( final ViewHolder holder, final int position) { holder.textView.setText(list.get(position)); holder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { } }); // When action mode is active, checkboxes are displayed on each row, handle views(move icons) on each row are disappered. if(!myFragment.is_in_action_mode) { holder.checkBox.setVisibility(View.GONE); } else { holder.checkBox.setVisibility(View.VISIBLE); holder.checkBox.setChecked(false); } holder.checkBox.setTag(position); holder.checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener(){ @Override public void onCheckedChanged(CompoundButton compoundButton, boolean b) { if(compoundButton.isPressed()) // ekrandan kaybolan checkbox'lar otomatik olarak state degistiriyordu ve bu listener method cagiriliyordu, bunu onlemek icin isPressed() method'u ile kullanici mi basmis diye kontrol ediyorum. { int getPosition = (Integer) compoundButton.getTag(); // Here we get the position that we have set for the checkbox using setTag. checkBoxState[getPosition] = compoundButton.isChecked(); // Set the value of checkbox to maintain its state. //relativeLayoutState[getPosition] = compoundButton.isChecked(); if(checkBoxState[getPosition] && getPosition == position ) holder.relativeLayout.setBackgroundResource(R.color.food_selected); /** Change background color of the selected items in list view **/ else holder.relativeLayout.setBackgroundResource(R.color.food_unselected); /** Change background color of the selected items in list view **/ myFragment.prepareselection(compoundButton, getPosition, holder.relativeLayout); } } }); holder.checkBox.setChecked(checkBoxState[position]); if(checkBoxState[position] ) holder.relativeLayout.setBackgroundResource(R.color.food_selected); /** Change background color of the selected items in list view **/ else holder.relativeLayout.setBackgroundResource(R.color.food_unselected); } @Override public int getItemCount() { return list.size(); } public void updateList(ArrayList<String> newList){ this.list = newList; checkBoxState = new boolean[list.size()+1]; } public void resetCheckBoxState(){ checkBoxState = null; checkBoxState = new boolean[list.size()]; } }скриншоты приложения :
на конференции Google i / o 2018 android анонсировала новую библиотеку для обработки выбора RecyclerView, вы можете посмотреть, как ее использовать здесь.
Set
private int selected_position = -1;для того чтобы предотвратить от любого элемента, который был выбран на старте.@Override public void onBindViewHolder(final OrdersHolder holder, final int position) { final Order order = orders.get(position); holder.bind(order); if(selected_position == position){ //changes background color of selected item in RecyclerView holder.itemView.setBackgroundColor(Color.GREEN); } else { holder.itemView.setBackgroundColor(Color.TRANSPARENT); //this updated an order property by status in DB order.setProductStatus("0"); } holder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //status switch and DB update if (order.getProductStatus().equals("0")) { order.setProductStatus("1"); notifyItemChanged(selected_position); selected_position = position; notifyItemChanged(selected_position); } else { if (order.getProductStatus().equals("1")){ //calls for interface implementation in //MainActivity which opens a new fragment with //selected item details listener.onOrderSelected(order); } } } }); }
просто добавив
android:background="?attr/selectableItemBackgroundBorderless"должно работать, если у вас нет цвета фона, но не забудьте использовать метод setSelected. Если у вас другой цвет фона, я просто использовал это (я использую привязку данных);Set isSelected at onClick function
b.setIsSelected(true);и добавить это в xml;
android:background="@{ isSelected ? @color/{color selected} : @color/{color not selected} }"




Comments