可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I am trying to use a RecyclerView
as a horizontal ListView
. I am trying to figure out how to highlight the selected item. When I click on one of the items, it gets selected and it is highlighted properly but when I click on another one, the second one gets highlighted with the older one.
Here is my onClick function:
@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;
}
Here is the 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);
}
回答1:
I wrote a base adapter class to automatically handle item selection with a RecyclerView. Just derive your adapter from it and use drawable state lists with state_selected, like you would do with a list view.
I have a Blog Post Here about it, but here is the code:
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);
}
});
}
}
}
回答2:
This is much simple way to do it.
Have a private int selectedPos = RecyclerView.NO_POSITION;
in the RecyclerView Adapter class, and under onBindViewHolder method try:
@Override
public void onBindViewHolder(ViewHolder viewHolder, int position) {
viewHolder.itemView.setSelected(selectedPos == position);
}
And in your OnClick event modify:
@Override
public void onClick(View view) {
notifyItemChanged(selectedPos);
selectedPos = getLayoutPosition();
notifyItemChanged(selectedPos);
}
Works like a charm for Navigtional Drawer and other RecyclerView Item Adapters.
Note: Be sure to use a background color in your layout using a selector like colabug clarified:
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/pressed_color" android:state_pressed="true"/>
<item android:drawable="@color/selected_color" android:state_selected="true"/>
<item android:drawable="@color/focused_color" android:state_focused="true"/>
</selector>
Otherwise setSelected(..) will do nothing, rendering this solution useless.
回答3:
UPDATE [26/Jul/2017]:
As the Pawan mentioned in the comment about that IDE warning about not to using that
fixed position, I have just modified my code as below. The click
listener is moved to ViewHolder
, and there I am getting the position
using getAdapterPosition()
method
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
}
}
hope this'll help.
回答4:
Your implementation likely works if you scroll the content out of view then back into view. I was having a similar issue when I came upon your question.
The following file snippet is working for me. My implementation was aimed at multiple selection, but I threw a hack in there to force single selection.(*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;
}
As noted in this linked question, setting listeners for viewHolders should be done in onCreateViewHolder. I forgot to mention this previously.
回答5:
I think, I've found the best tutorial on how to use the RecyclerView with all basic functions we need (single+multiselection, highlight, ripple, click and remove in multiselection, etc...).
Here it is --> http://enoent.fr/blog/2015/01/18/recyclerview-basics/
Based on that, I was able to create a library "FlexibleAdapter", which extends a SelectableAdapter.
I think this must be a responsibility of the Adapter, actually you don't need to rewrite the basic functionalities of Adapter every time, let a library to do it, so you can just reuse the same implementation.
This Adapter is very fast, it works out of the box (you don't need to extend it); you customize the items for every view types you need; ViewHolder are predefined: common events are already implemented: single and long click; it maintains the state after rotation and much much more.
Please have a look and feel free to implement it in your projects.
https://github.com/davideas/FlexibleAdapter
A Wiki is also available.
回答6:
Look on my solution. I suppose that you should set selected position in holder and pass it as Tag of View.
The view should be set in the onCreateViewHolder(...) method. There is also correct place to set listener for view such as OnClickListener or LongClickListener.
Please look on the example below and read comments to code.
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);
}
}
回答7:
there is no selector in RecyclerView like ListView and GridView but you try below thing it worked for me
create a selector drawable as below
<?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>
then set this drawable as background of your RecyclerView row layout as
android:background="@drawable/selector"
回答8:
this is my solution, you can set on an item (or a group) and deselect it with another click:
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);
}
}
});
}
回答9:
Decision with Interfaces and Callbacks.
Create Interface with select and unselect states:
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();
}
Implement interface in 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);
}
}
Run state change on 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();
}
}
}
Create RecyclerView with Callback (example):
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);
See more in article of iPaulPro: https://medium.com/@ipaulpro/drag-and-swipe-with-recyclerview-6a6f0c422efd#.6gh29uaaz
回答10:
In the Google i/o 2018 conference the android announced a new library to handling RecyclerView Selection, you can take a look of how to use it here.
回答11:
I had same Issue and i solve it following way:
The xml file which is using for create a Row inside createViewholder, just add below line:
android:clickable="true"
android:focusableInTouchMode="true"
android:background="?attr/selectableItemBackgroundBorderless"
OR If you using frameLayout as a parent of row item then:
android:clickable="true"
android:focusableInTouchMode="true"
android:foreground="?attr/selectableItemBackgroundBorderless"
In java code inside view holder where you added on click listener:
@Override
public void onClick(View v) {
//ur other code here
v.setPressed(true);
}
回答12:
I couldn't find a good solution on the web for this problem and solved it myself. There are lot of people suffering from this problem. Thus i want to share my solution here.
While scrolling, rows are recycled. Thus, checked checkboxes and highlighted rows do not work properly. I solved this problem by writing the below adapter class.
I also implement a full project. In this project, you can select multiple checkboxes. Rows including selected checkboxes are highlighted. And more importantly, these are not lost while scrolling. You can download it from the link :
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()];
}
}
Screenshots of the app :
回答13:
Set private int selected_position = -1;
to prevent from any item being selected on start.
@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);
}
}
}
});
}
回答14:
Just adding android:background="?attr/selectableItemBackgroundBorderless"
should work if you don't have background color, but don't forget to use setSelected method. If you have different background color, I just used this (I'm using data-binding);
Set isSelected at onClick function
b.setIsSelected(true);
And add this to xml;
android:background="@{ isSelected ? @color/{color selected} : @color/{color not selected} }"