如何正确突出显示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); }
我写了一个基类适配器类来自动处理与RecyclerView的项目select。 只需从中获取适配器,并使用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 = 0;
在RecyclerView Adapter类中,并在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); }
像Navigtional抽屉和其他RecyclerView项目适配器的魅力。
我没有使用任何额外的方法😉:
UPDATE [26 / Jul / 2017]:
Pawan在关于不使用这个固定位置的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 } }
希望这会有所帮助。
如果您将内容从视图中滚动回到视图中,您的实现可能会起作用。 当我提到你的问题时,我也有类似的问题。
以下文件片段正在为我工作。 我的实现是针对多种select的,但是我在那里做了一个黑客攻击来强制单一select(* 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中完成。 我以前忘了提这个。
我想,我已经find了关于如何使用我们需要的所有基本function(单选+多选,突出显示,波纹,点击并在多选中删除等等)的RecyclerView的最佳教程。
这里是 – > http://enoent.fr/blog/2015/01/18/recyclerview-basics/
基于此,我能够创build一个库“FlexibleAdapter”,它扩展了SelectableAdapter。 我认为这一定是Adapter的责任,实际上你不需要每次都重写Adapter的基本function,让一个库去做,所以你可以重复使用相同的实现。
这个适配器速度非常快,可以直接使用(不需要扩展)。 您可以为您需要的每种视图types自定义项目; ViewHolder是预定义的:公共事件已经实现:单击和长按; 它保持旋转之后的状态以及更多 。
请看一看,并随意在您的项目中实现它。
https://github.com/davideas/FlexibleAdapter
Wiki也是可用的。
看看我的解决scheme。 我想你应该在持有人中设置选定的位置,并将其作为视图标签传递。 视图应该在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那样的select器,但是你可以在下面试试它为我工作的东西
创build一个如下所示的select器
<?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>
然后将此可绘制设置为您的RecyclerView行布局的背景
android:background="@drawable/selector"
这是我的解决scheme,你可以设置一个项目(或一个组),并通过另一次点击取消select:
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); } } }); }
接口和callback的决定。 使用select和unselect状态创build接口:
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); } }
在callback中运行状态更改:
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(); } } }
使用callback创buildRecyclerView(示例):
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
我有同样的问题,我解决它的方式如下:
用于在createViewholder中创build一个Row的xml文件,只需添加如下行:
android:clickable="true" android:focusableInTouchMode="true" android:background="?attr/selectableItemBackgroundBorderless"
或者,如果使用frameLayout作为行项目的父项,则:
android:clickable="true" android:focusableInTouchMode="true" android:foreground="?attr/selectableItemBackgroundBorderless"
在视图保持器里面的java代码里你添加的点击侦听器:
@Override public void onClick(View v) { //ur other code here v.setPressed(true); }
我找不到在这个问题上的一个很好的解决scheme,并自己解决。 有很多人遭受这个问题。 因此我想在这里分享我的解决scheme。
滚动时,行被回收。 因此,选中的checkbox和突出显示的行无法正常工作。 我通过编写下面的适配器类来解决这个问题。
我也执行一个完整的项目。 在这个项目中,你可以select多个checkbox。 包含选定checkbox的行将突出显示。 更重要的是,这些在滚动时不会丢失。 您可以从链接下载:
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()]; } }
应用程序的屏幕截图:
只要添加android:background="?attr/selectableItemBackgroundBorderless"
可以工作,如果你没有背景颜色,但不要忘记使用setSelected方法。 如果你有不同的背景颜色,我只是用这个(我使用数据绑定);
在onClick函数中设置isSelected
b.setIsSelected(true);
并添加到XML;
android:background="@{ isSelected ? @color/{color selected} : @color/{color not selected} }"
设置private int selected_position = -1;
防止从开始select任何项目。
@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); } } } }); }