Keywords: RecyclerView | Item Selection | State Management | Android Development | UI Optimization
Abstract: This article provides an in-depth exploration of implementing proper item selection highlighting in Android RecyclerView. By analyzing common problem sources, it presents optimized solutions based on state management, detailing the implementation using setSelected() with state list selectors, and offering complete code examples and best practices. The article also discusses handling multi-selection scenarios and performance optimization strategies, providing developers with a reusable and efficient solution.
Problem Analysis and Common Pitfalls
Implementing item selection highlighting in RecyclerView is a common requirement in Android development, but many developers encounter issues with incorrect state updates. The main problems in the original code include:
Directly modifying view background colors in onClick method without updating adapter state:
@Override
public void onClick(View view) {
if(selectedListItem!=null){
selectedListItem.setBackgroundColor(Color.RED);
}
view.setBackgroundColor(Color.CYAN);
selectedListItem = view;
}
This implementation has serious flaws. When RecyclerView recycles views, previously set color states are lost, causing multiple items to appear selected simultaneously.
Core Solution: State Management Mechanism
The correct solution should be based on state management, where the adapter maintains the current selected position and sets visual effects accordingly in onBindViewHolder.
First, define the selected position variable in the adapter:
private int selectedPos = RecyclerView.NO_POSITION;
Set selection state based on position in onBindViewHolder:
@Override
public void onBindViewHolder(ViewHolder viewHolder, int position) {
viewHolder.itemView.setSelected(selectedPos == position);
}
Complete Implementation Solution
Based on the best answer, the complete adapter implementation is as follows:
public abstract class TrackSelectionAdapter<VH extends TrackSelectionAdapter.ViewHolder> extends RecyclerView.Adapter<VH> {
private int focusedItem = 0;
@Override
public void onAttachedToRecyclerView(final RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
recyclerView.setOnKeyListener(new View.OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
RecyclerView.LayoutManager lm = recyclerView.getLayoutManager();
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 (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) {
viewHolder.itemView.setSelected(focusedItem == i);
}
public class ViewHolder extends RecyclerView.ViewHolder {
public ViewHolder(View itemView) {
super(itemView);
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
notifyItemChanged(focusedItem);
focusedItem = getLayoutPosition();
notifyItemChanged(focusedItem);
}
});
}
}
}
Key Role of State List Selectors
Using setSelected() method must be combined with state list selectors to take effect. Create res/drawable/item_background.xml:
<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>
Set background for itemView in layout file:
android:background="@drawable/item_background"
Performance Optimization and Best Practices
Using notifyItemChanged() instead of notifyDataSetChanged() significantly improves performance:
@Override
public void onClick(View v) {
notifyItemChanged(selectedPos);
selectedPos = getAdapterPosition();
notifyItemChanged(selectedPos);
}
This method only redraws changed items, avoiding unnecessary view reconstruction.
Multi-Selection Scenario Extension
For scenarios requiring multi-selection support, use Set to store selected positions:
private Set<Integer> selectedPositions = new HashSet<>();
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.itemView.setSelected(selectedPositions.contains(position));
}
public void toggleSelection(int position) {
if (selectedPositions.contains(position)) {
selectedPositions.remove(position);
} else {
selectedPositions.add(position);
}
notifyItemChanged(position);
}
Conclusion
By combining state management with state list selectors, efficient and reliable item selection highlighting can be achieved in RecyclerView. Key points include: maintaining selection state, properly using notifyItemChanged(), and configuring correct state list selectors. This solution not only solves the original problem but also provides good extensibility and performance.