Android Support Library 28.0.0-alpha1 RecyclerView-Selection API实现求助
Hey there! Let's walk through getting the RecyclerView Selection API working with a Contextual Action Bar (CAB) and your desired actions like delete/share. I'll break this down step by step based on the 28.0.0-alpha1 support library.
First, make sure your app-level build.gradle includes the required libraries:
dependencies { implementation 'com.android.support:recyclerview-v7:28.0.0-alpha1' implementation 'com.android.support:recyclerview-selection:28.0.0-alpha1' }
Your adapter needs to integrate with the Selection API by implementing ItemDetailsLookup.ItemDetails in its ViewHolder. This lets the tracker access item-specific data for selection logic.
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> { private List<MyItem> items; private SelectionTracker<Long> selectionTracker; // Constructor, onCreateViewHolder, and basic adapter methods... public class MyViewHolder extends RecyclerView.ViewHolder implements ItemDetailsLookup.ItemDetails<Long> { TextView itemTitle; public MyViewHolder(View itemView) { super(itemView); itemTitle = itemView.findViewById(R.id.item_title); } @Override public int getPosition() { return getAdapterPosition(); } @Override public Long getSelectionKey() { // Return a unique, stable key for each item (e.g., database ID) return items.get(getAdapterPosition()).getId(); } @Override public boolean inSelectionHotspot(MotionEvent e) { // Allow selection to trigger from anywhere on the item (adjust if needed) return true; } } // Setter for the selection tracker public void setSelectionTracker(SelectionTracker<Long> selectionTracker) { this.selectionTracker = selectionTracker; } @Override public void onBindViewHolder(MyViewHolder holder, int position) { MyItem item = items.get(position); holder.itemTitle.setText(item.getTitle()); // Update item appearance based on selection state boolean isSelected = selectionTracker.isSelected(item.getId()); holder.itemView.setActivated(isSelected); } // Helper methods to access and update data public List<MyItem> getItems() { return items; } public void updateItems(List<MyItem> newItems) { items = newItems; notifyDataSetChanged(); } }
Note: Ensure your item layout's root view uses android:background="?attr/selectableItemBackground" or a state-list drawable to show a visual highlight when selected.
In your Fragment's view initialization, set up the SelectionTracker, listen for selection changes to trigger the CAB, and handle action callbacks.
public class MyListFragment extends Fragment { private RecyclerView recyclerView; private MyAdapter adapter; private SelectionTracker<Long> selectionTracker; private ActionMode actionMode; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_list, container, false); } @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); recyclerView = view.findViewById(R.id.recycler_view); adapter = new MyAdapter(getSampleData()); // Replace with your data source recyclerView.setAdapter(adapter); recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); // Initialize SelectionTracker selectionTracker = new SelectionTracker.Builder<>( "unique-selection-id", // Unique ID for saving/restoring state recyclerView, new ItemKeyProviderImpl(adapter), new ItemDetailsLookupImpl(recyclerView), StorageStrategy.createLongStorage() ) .withSelectionPredicate(SelectionPredicates.createSelectAnything()) // Enable multi-selection .build(); // Restore selection state after configuration changes if (savedInstanceState != null) { selectionTracker.onRestoreInstanceState(savedInstanceState); } adapter.setSelectionTracker(selectionTracker); // Listen for selection changes to show/hide CAB selectionTracker.addObserver(new SelectionTracker.SelectionObserver<Long>() { @Override public void onSelectionChanged() { super.onSelectionChanged(); int selectedCount = selectionTracker.getSelectedItems().size(); if (selectedCount > 0) { if (actionMode == null) { actionMode = requireActivity().startActionMode(actionModeCallback); } actionMode.setTitle(getString(R.string.selected_items, selectedCount)); } else { if (actionMode != null) { actionMode.finish(); } } } }); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); selectionTracker.onSaveInstanceState(outState); } // CAB Action Callback private ActionMode.Callback actionModeCallback = new ActionMode.Callback() { @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { MenuInflater inflater = mode.getMenuInflater(); inflater.inflate(R.menu.menu_contextual, menu); return true; } @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { return false; } @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { switch (item.getItemId()) { case R.id.action_delete: deleteSelectedItems(); mode.finish(); return true; case R.id.action_share: shareSelectedItems(); mode.finish(); return true; default: return false; } } @Override public void onDestroyActionMode(ActionMode mode) { selectionTracker.clearSelection(); actionMode = null; } }; // Handle delete operation private void deleteSelectedItems() { Set<Long> selectedKeys = selectionTracker.getSelectedItems(); List<MyItem> updatedItems = new ArrayList<>(); for (MyItem item : adapter.getItems()) { if (!selectedKeys.contains(item.getId())) { updatedItems.add(item); } } adapter.updateItems(updatedItems); } // Handle share operation private void shareSelectedItems() { Set<Long> selectedKeys = selectionTracker.getSelectedItems(); StringBuilder shareContent = new StringBuilder(); for (MyItem item : adapter.getItems()) { if (selectedKeys.contains(item.getId())) { shareContent.append(item.getTitle()).append("\n"); } } Intent shareIntent = new Intent(Intent.ACTION_SEND); shareIntent.setType("text/plain"); shareIntent.putExtra(Intent.EXTRA_TEXT, shareContent.toString()); startActivity(Intent.createChooser(shareIntent, getString(R.string.share_via))); } // Custom ItemKeyProvider private static class ItemKeyProviderImpl extends ItemKeyProvider<Long> { private MyAdapter adapter; public ItemKeyProviderImpl(MyAdapter adapter) { super(SCOPE_CACHED); this.adapter = adapter; } @Override public Long getKey(int position) { return adapter.getItems().get(position).getId(); } @Override public int getPosition(Long key) { for (int i = 0; i < adapter.getItems().size(); i++) { if (adapter.getItems().get(i).getId().equals(key)) { return i; } } return RecyclerView.NO_POSITION; } } // Custom ItemDetailsLookup private static class ItemDetailsLookupImpl extends ItemDetailsLookup<Long> { private RecyclerView recyclerView; public ItemDetailsLookupImpl(RecyclerView recyclerView) { this.recyclerView = recyclerView; } @Override public ItemDetails<Long> getItemDetails(MotionEvent e) { View view = recyclerView.findChildViewUnder(e.getX(), e.getY()); if (view != null) { RecyclerView.ViewHolder holder = recyclerView.getChildViewHolder(view); if (holder instanceof MyAdapter.MyViewHolder) { return ((MyAdapter.MyViewHolder) holder); } } return null; } } // Replace with your actual data fetching logic private List<MyItem> getSampleData() { List<MyItem> items = new ArrayList<>(); // Add sample items here return items; } }
Add a menu resource (res/menu/menu_contextual.xml) for the CAB actions:
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/action_delete" android:title="@string/delete" app:showAsAction="ifRoom"/> <item android:id="@+id/action_share" android:title="@string/share" app:showAsAction="ifRoom"/> </menu>
- Stable Selection Keys: Never use item position as the selection key — positions change when items are added/removed. Use a unique, persistent ID instead.
- Activated State: Ensure your item view uses the
activatedstate to show selection highlights. - State Restoration: Always save and restore the SelectionTracker state to preserve selections across screen rotations.
- Selection Predicate: Use
SelectionPredicates.createSelectSingleAnything()if you need single-selection instead of multi-selection.
If you're still running into specific issues (like CAB not appearing, selection not persisting, or action callbacks failing), share the relevant code snippets or error logs and I can help troubleshoot further!
内容的提问来源于stack exchange,提问作者dmyoung9

