Come implementare il menu contestuale per RecyclerView?
Apparentemente la chiamata registerForContextMenu(recyclerView)
non funziona. Lo chiamo da un frammento. Qualcuno ha avuto successo nell'implementarlo?
Come implementare il menu contestuale per RecyclerView?
Apparentemente la chiamata registerForContextMenu(recyclerView)
non funziona. Lo chiamo da un frammento. Qualcuno ha avuto successo nell'implementarlo?
Risposte:
Non puoi implementare direttamente questi metodi come onClickListener , OnContextMenuListener ecc. Perché RecycleView estende android.view.ViewGroup . Quindi non possiamo usare direttamente questi metodi. Possiamo implementare questi metodi nella classe dell'adattatore ViewHolder . Possiamo usare il menu contestuale in RecycleView in questo modo:
public static class ViewHolder extends RecyclerView.ViewHolder implements OnCreateContextMenuListener {
TextView tvTitle;
ImageView ivImage;
public ViewHolder(View v) {
super(v);
tvTitle =(TextView)v.findViewById(R.id.item_title);
v.setOnCreateContextMenuListener(this);
}
Ora seguiamo la stessa procedura mentre implementa il menu contestuale.
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
menu.setHeaderTitle("Select The Action");
menu.add(0, v.getId(), 0, "Call");//groupId, itemId, order, title
menu.add(0, v.getId(), 0, "SMS");
}
In caso di difficoltà chiedi un commento.
onContextItemSelected
livello di attività / frammento. getTitle
funziona, getItemId
funziona, ma getMenuInfo
fornisce null. Quindi, come ottenere il ViewHolder
?
getMenuInfo()
restituisce null in onContextItemSelected()
. Forse le persone che l'hanno fatto funzionare hanno un onCreateContextMenu()
metodo in una vista più in alto nella gerarchia (il RecyclerView
o Fragment
)? Può funzionare, ma poi ci porta ad altre risposte a questa domanda.
menu.add(this.getAdapterPosition(), v.getId(), 0, "Call");
e quindi nel test del metodo di richiamata per item.getGroupId()
ottenere la posizione
Grazie per le informazioni e i commenti. Sono stato in grado di ottenere ContextMenu
per articoli in Recyclerview
.
Ecco cosa ho fatto
nel onViewCreated
metodo Fragment o nel metodo Activity onCreate
:
registerForContextMenu(mRecyclerView);
Quindi in Adapter aggiungi
private int position;
public int getPosition() {
return position;
}
public void setPosition(int position) {
this.position = position;
}
fai ViewHolder
implementare la classeOnCreateContextMenuListener
public static class ViewHolder extends RecyclerView.ViewHolder
implements View.OnCreateContextMenuListener {
public ImageView icon;
public TextView fileName;
public ImageButton menuButton;
public ViewHolder(View v) {
super(v);
icon = (ImageView)v.findViewById(R.id.file_icon);
fileName = (TextView)v.findViewById(R.id.file_name);
menuButton = (ImageButton)v.findViewById(R.id.menu_button);
v.setOnCreateContextMenuListener(this);
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenu.ContextMenuInfo menuInfo) {
//menuInfo is null
menu.add(Menu.NONE, R.id.ctx_menu_remove_backup,
Menu.NONE, R.string.remove_backup);
menu.add(Menu.NONE, R.id.ctx_menu_restore_backup,
Menu.NONE, R.string.restore_backup);
}
}
onBindViewHolder
metodo aggiungi OnLongClickListener
su holder.itemView per acquisire la posizione prima del caricamento del menu contestuale:
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
setPosition(holder.getPosition());
return false;
}
});
Quindi onViewRecycled
rimuovere il Listener in modo che non ci siano problemi di riferimento. (potrebbe non essere richiesto).
@Override
public void onViewRecycled(ViewHolder holder) {
holder.itemView.setOnLongClickListener(null);
super.onViewRecycled(holder);
}
Infine nel frammento / attività sovrascrivi onContextItemSelected
come sotto:
@Override
public boolean onContextItemSelected(MenuItem item) {
int position = -1;
try {
position = ((BackupRestoreListAdapter)getAdapter()).getPosition();
} catch (Exception e) {
Log.d(TAG, e.getLocalizedMessage(), e);
return super.onContextItemSelected(item);
}
switch (item.getItemId()) {
case R.id.ctx_menu_remove_backup:
// do your stuff
break;
case R.id.ctx_menu_restore_backup:
// do your stuff
break;
}
return super.onContextItemSelected(item);
}
La risposta attuale non è corretta. Ecco un'implementazione funzionante:
public class ContextMenuRecyclerView extends RecyclerView {
private RecyclerViewContextMenuInfo mContextMenuInfo;
@Override
protected ContextMenu.ContextMenuInfo getContextMenuInfo() {
return mContextMenuInfo;
}
@Override
public boolean showContextMenuForChild(View originalView) {
final int longPressPosition = getChildPosition(originalView);
if (longPressPosition >= 0) {
final long longPressId = getAdapter().getItemId(longPressPosition);
mContextMenuInfo = new RecyclerViewContextMenuInfo(longPressPosition, longPressId);
return super.showContextMenuForChild(originalView);
}
return false;
}
public static class RecyclerViewContextMenuInfo implements ContextMenu.ContextMenuInfo {
public RecyclerViewContextMenuInfo(int position, long id) {
this.position = position;
this.id = id;
}
final public int position;
final public long id;
}
}
Nel tuo frammento (o attività):
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mRecyclerView = view.findViewById(R.id.recyclerview);
registerForContextMenu(mRecyclerView);
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
// inflate menu
MenuInflater inflater = getActivity().getMenuInflater();
inflater.inflate(R.menu.my_context_menu, menu);
}
@Override
public boolean onContextItemSelected(MenuItem item) {
return super.onContextItemSelected(item);
RecyclerViewContextMenuInfo info = (RecyclerViewContextMenuInfo) item.getMenuInfo();
// handle menu item here
}
E infine, nel tuo ViewHolder:
class MyViewHolder extends RecyclerView.View.ViewHolder {
...
private void onLongClick() {
itemView.showContextMenu();
}
}
getChildPosition()
ora è deprecato. Ho usato getChildAdapterPosition()
invece.
getChildPosition()
è deprecato in com.android.support:recyclerview-v7:22.0.0
.
Prova questo per un View
elemento in recycleView
.setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() {
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
menu.add("delete").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
//do what u want
return true;
}
});
}
});
Puoi usarlo con l'impostazione dei dati su un ViewHolder
elemento
La risposta di Prabhakar è corretta, ma non ha spiegato come ottenere un dato, relativo all'elemento premuto, quando è selezionata una voce del menu contestuale. Possiamo usare onContextItemSelected
callback, ma ContextMenuInfo
non è disponibile ( null
) in questo caso (se il getContextMenuInfo()
metodo non è sovrascritto per una vista premuta). Quindi, la soluzione più semplice è aggiungere OnMenuItemClickListener
direttamente al file MenuItem
.
private class ViewHolder extends RecyclerView.ViewHolder {
private final TextView mTitleTextView;
private MyItemData mData;
public ViewHolder(View view) {
super(view);
mTitleTextView = (TextView)view.findViewById(R.id.title);
view.setOnCreateContextMenuListener(mOnCreateContextMenuListener);
}
public void bind(@NonNull MyItemData data) {
mData = data;
String title = mData.getTitle();
mTitleTextView.setText(title);
}
private final View.OnCreateContextMenuListener mOnCreateContextMenuListener = new View.OnCreateContextMenuListener() {
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
if (mData!= null) {
MenuItem myActionItem = menu.add("My Context Action");
myActionItem.setOnMenuItemClickListener(mOnMyActionClickListener);
}
}
};
private final MenuItem.OnMenuItemClickListener mOnMyActionClickListener = new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
//todo: process item click, mData is available here!!!
return true;
}
};
}
RecyclerView
solo per sovrascrivere getContextMenuInfo
ecc., Anche se non è efficiente quanto lasciare che il frammento / attività gestisca i clic. Gli ascoltatori avranno accesso ai dati nel titolare, quindi non dovresti aver bisogno di posizione. E teoricamente potresti comunque memorizzare la posizione al momento del binding nell'adattatore e utilizzare la delega per chiamare il tuo titolare se necessario, sebbene l'uso Context
di una delle viste associate a volte possa essere sufficiente.
La risposta di @ Renaud ha funzionato per me, ma prima ha richiesto diverse correzioni di codice. È come se avesse pubblicato frammenti di diverse iterazioni del suo codice. Le modifiche da apportare sono:
RecyclerContextMenuInfo
e RecyclerViewContextMenuInfo
sono della stessa classe. Scegli un nome e mantienilo.ViewHolder
deve implementare View.OnLongClickListener
e ricordare di richiamare setOnLongClickListener()
l'elemento nel costruttore.onLongClick()
ascoltatore, getView().showContextMenu()
è completamente sbagliato. Devi chiamare il showContextMenuForChild()
tuo ContextMenuRecyclerView
, altrimenti ContextMenuInfo
entri onCreateContextMenu()
e onContextItemSelected()
sarà nullo.Il mio codice modificato di seguito:
ContextMenuRecyclerView:
public class ContextMenuRecyclerView extends RecyclerView {
private RecyclerViewContextMenuInfo mContextMenuInfo;
@Override
protected ContextMenu.ContextMenuInfo getContextMenuInfo() {
return mContextMenuInfo;
}
@Override
public boolean showContextMenuForChild(View originalView) {
final int longPressPosition = getChildPosition(originalView);
if (longPressPosition >= 0) {
final long longPressId = getAdapter().getItemId(longPressPosition);
mContextMenuInfo = new RecyclerViewContextMenuInfo(longPressPosition, longPressId);
return super.showContextMenuForChild(originalView);
}
return false;
}
public static class RecyclerViewContextMenuInfo implements ContextMenu.ContextMenuInfo {
public RecyclerViewContextMenuInfo(int position, long id) {
this.position = position;
this.id = id;
}
final public int position;
final public long id;
}
}
Nel tuo frammento:
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mRecyclerView = view.findViewById(R.id.recyclerview);
registerForContextMenu(mRecyclerView);
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
// inflate menu here
// If you want the position of the item for which we're creating the context menu (perhaps to add a header or something):
int itemIndex = ((ContextMenuRecyclerView.RecyclerViewContextMenuInfo) menuInfo).position;
}
@Override
public boolean onContextItemSelected(MenuItem item) {
ContextMenuRecyclerView.RecyclerViewContextMenuInfo info = (ContextMenuRecyclerView.RecyclerViewContextMenuInfo) item.getMenuInfo();
// handle menu here - get item index or ID from info
return super.onContextItemSelected(item);
}
Nel tuo ViewHolder:
class MyViewHolder extends RecyclerView.ViewHolder implements View.OnLongClickListener {
public MyViewHolder( View itemView ) {
super( itemView );
itemView.setOnLongClickListener( this );
}
@Override public boolean onLongClick() {
recyclerView.showContextMenuForChild( v );
return true;
}
}
Inoltre, assicurati di sostituire RecyclerView
con ContextMenuRecyclerView
nel tuo layout!
recyclerView.showContextMenuForChild(itemView);
con itemView.showContextMenu()
.
Nella classe Adapter:
/**
* Custom on long click item listener.
*/
onLongItemClickListener mOnLongItemClickListener;
public void setOnLongItemClickListener(onLongItemClickListener onLongItemClickListener) {
mOnLongItemClickListener = onLongItemClickListener;
}
public interface onLongItemClickListener {
void ItemLongClicked(View v, int position);
}
In onBindViewHolder
hook il listener personalizzato:
// Hook our custom on long click item listener to the item view.
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if (mOnLongItemClickListener != null) {
mOnLongItemClickListener.ItemLongClicked(v, position);
}
return true;
}
});
In MainActivity (Activity / Fragment) creare un campo:
private int mCurrentItemPosition;
Nell'oggetto Adapter imposta il listener personalizzato:
mAdapter.setOnLongItemClickListener(new FileAdapter.onLongItemClickListener() {
@Override
public void ItemLongClicked(View v, int position) {
mCurrentItemPosition = position;
}
});
Ora hai una posizione appetitosa per qualsiasi oggetto su cui ci hai cliccato a lungo 😋
In res -> menu
Crea un file contenente la tua voce di menu context_menu_main.xml
:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/delete" android:title="Delete"/>
<item android:id="@+id/share" android:title="Share"/>
</menu>
In MainActivity:
implementa sia onCreateContextMenu
e onContextItemSelected
:
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.context_menu_main, menu);
}
@Override
public boolean onContextItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.delete) {
}
if (id == R.id.share) {
}
return true;
}
mostra il menu contestuale.
registerForContextMenu(mRecyclerView);
mAdapter.setOnLongItemClickListener(new FileAdapter.onLongItemClickListener() {
@Override
public void ItemLongClicked(View v, int position) {
mCurrentItemPosition = position;
v.showContextMenu();
}
});
Spero di non dimenticare nulla 🤔
Maggiori informazioni su Menus Documentation
Ecco un modo più semplice per farlo con Kotlin che ha funzionato per me. La sfida principale è capire la posizione dell'oggetto che è stato premuto. All'interno del tuo adattatore, puoi inserire questo frammento di codice e sarà in grado di catturare la posizione dell'elemento per il quale viene mostrato il menu contestuale; È tutto.
override fun onBindViewHolder(holder: YourViewHolder, position: Int) {
...
holder.view.setOnCreateContextMenuListener { contextMenu, _, _ ->
contextMenu.add("Add").setOnMenuItemClickListener {
longToast("I'm pressed for the item at position => $position")
true
}
}
}
Ho combinato la mia soluzione con la soluzione di @Hardik Shah:
Nell'attività ho:
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
if (v.getId() == R.id.rvQuests) {
getMenuInflater().inflate(R.menu.list_menu, menu);
}
}
Nell'adattatore ho:
private MainActivity context;
private int position;
public int getPosition() {
return position;
}
public void setPosition(int position) {
this.position = position;
}
public QuestsAdapter(MainActivity context, List<Quest> objects) {
this.context = context;
this.quests.addAll(objects);
}
public class QuestViewHolder extends RecyclerView.ViewHolder {
private QuestItemBinding questItemBinding;
public QuestViewHolder(View v) {
super(v);
questItemBinding = DataBindingUtil.bind(v);
v.setOnCreateContextMenuListener(context);
}
}
@Override
public void onBindViewHolder(final QuestViewHolder holder, int position) {
Quest quest = quests.get(position);
holder.questItemBinding.setQuest(quest);
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
setPosition(holder.getAdapterPosition());
return false;
}
});
}
@Override
public void onViewRecycled(QuestViewHolder holder) {
holder.itemView.setOnLongClickListener(null);
super.onViewRecycled(holder);
}
In frammento ho:
@Override
public boolean onContextItemSelected(MenuItem item) {
int position = ((QuestsAdapter) questsList.getAdapter()).getPosition();
switch (item.getItemId()) {
case R.id.menu_delete:
Quest quest = questsAdapter.getItem(position);
App.getQuestManager().deleteQuest(quest);
questsAdapter.remove(quest);
checkEmptyList();
return true;
default:
return super.onContextItemSelected(item);
}
}
Forse sono in ritardo alla festa ma ho una soluzione funzionante . Ho fatto un succo per questo.
Aggiungi menu contestuale a RecyclerView
ActivityName.java
//Import Statements
public class ActivityName extends AppCompatActivity {
private RecyclerView mRecyclerView;
private RecyclerView.Adapter mAdapter;
private RecyclerView.LayoutManager mLayoutManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view_birthdays);
//Recycle View
mRecyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);
mLayoutManager = new LinearLayoutManager(getApplicationContext());
mRecyclerView.setLayoutManager(mLayoutManager);
mAdapter = new BirthdaysListAdapter(data, this);
mRecyclerView.setAdapter(mAdapter);
}
RecyclerAdapter.java
//Import Statements
public class BirthdaysListAdapter extends RecyclerView.Adapter<BirthdaysListAdapter.ViewHolder> {
static Context ctx;
private List<typeOfData> Data;
public BirthdaysListAdapter(List<typeOfData> list, Context context) {
Data = list;
this.ctx = context;
}
BirthdaysListAdapter() {
}
public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnCreateContextMenuListener {
public TextView name;
public TextView Birthday;
public ImageView colorAlphabet;
public TextView textInImg;
public ViewHolder(View v) {
super(v);
name = (TextView) v.findViewById(R.id.name);
Birthday = (TextView) v.findViewById(R.id.Birthday);
colorAlphabet = (ImageView) v.findViewById(R.id.colorAlphabet);
textInImg = (TextView) v.findViewById(R.id.textInImg);
v.setOnCreateContextMenuListener(this); //REGISTER ONCREATE MENU LISTENER
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v //CREATE MENU BY THIS METHOD
ContextMenu.ContextMenuInfo menuInfo) {
new BirthdaysListAdapter().info = (AdapterView.AdapterContextMenuInfo) menuInfo;
MenuItem Edit = menu.add(Menu.NONE, 1, 1, "Edit");
MenuItem Delete = menu.add(Menu.NONE, 2, 2, "Delete");
Edit.setOnMenuItemClickListener(onEditMenu);
Delete.setOnMenuItemClickListener(onEditMenu);
}
//ADD AN ONMENUITEM LISTENER TO EXECUTE COMMANDS ONCLICK OF CONTEXT MENU TASK
private final MenuItem.OnMenuItemClickListener onEditMenu = new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
DBHandler dbHandler = new DBHandler(ctx);
List<WishMen> data = dbHandler.getWishmen();
switch (item.getItemId()) {
case 1:
//Do stuff
break;
case 2:
//Do stuff
break;
}
return true;
}
};
}
public List<ViewBirthdayModel> getData() {
return Data;
}
@Override
public long getItemId(int position) {
return super.getItemId(position);
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_view_birthdays, parent, false);
ViewHolder vh = new ViewHolder(view);
return vh;
}
@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
holder.name.setText(Data.get(position).getMan().getName());
holder.Birthday.setText(Data.get(position).getMan().getBday());
holder.colorAlphabet.setBackgroundColor(Color.parseColor(Data.get(position).getColor()));
holder.textInImg.setText(String.valueOf(Data.get(position).getMan().getName().toUpperCase().charAt(0)));
}
@Override
public int getItemCount() {
return Data.size();
}
private int position;
public int getPosition() {
return position;
}
public void setPosition(int position) {
this.position = position;
}
}
Ciao ragazzi, è uscito con un'alternativa che funziona per me. Ho appena registrato il mio itemView con registerContextMenu e ViewHolder Constructor, ho anche impostato un onLongClikcListener sulla stessa vista. Nell'implementazione onLongClick (View v), ottengo semplicemente la posizione cliccata con getLayoutPosition () e salvo in una variabile di istanza (ho creato una classe per rappresentare questi dati, proprio come ci si aspetta che ContextMenuInfo funzioni), ma più importante è fare sicuro di restituire false in questo metodo. Tutto quello che devi fare ora è in te su onContextItemSelected (voce MenuItem), leggere i dati che memorizzi nella tua variabile di istanza e se sono validi procedi con le tue azioni. Ecco uno snippet.
public MyViewHolder(View itemView){
super(itemView);
registerForContextMenu(itemView);
itemView.setOnLongClickListener(this);
}
Faccio che ViewHolder implementi OnLongClickListener, ma puoi farlo nel modo che preferisci.
@Override
public boolean onLongClick(View v){
mCurrentLongItem = new ListItemInfo(v.getId(), getLayoutPosition());
return false; // REMEMBER TO RETURN FALSE.
}
Puoi anche impostarlo nell'adattatore o su un'altra vista che hai nel ViewHolder (cioè un TextView). L'importante è l'implementazione di onLongClik ().
@Override
public boolean onContextItemSelected(MenuItem item) {
switch (item.getItemId()){
case R.id.client_edit_context_menu:
if(mCurrentLongItem != null){
int position = mCurrentLongItem.position;
//TAKE SOME ACTIONS.
mCurrentLongItem = null;
}
return true;
}
return super.onContextItemSelected(item);
}
La parte migliore è che puoi ancora elaborare l'evento LongClick che restituisce true nei casi che desideri e il conextMenu non verrà visualizzato.
Questo metodo funziona perché registerForContextView rende la visualizzazione LongClickable e quando è il momento di elaborare ContextMenu, il sistema chiama performLongClick, che prima chiama un'implementazione onLongClick e, se restituisce false, chiama showContextMenu.
Uso questa soluzione da un po 'di tempo e ha funzionato abbastanza bene per me.
public class CUSTOMVIEWNAME extends RecyclerView {
public CUSTOMVIEWNAME(Context context) {
super(context);
}
public CUSTOMVIEWNAME (Context context, AttributeSet attrs) {
super(context, attrs);
}
public CUSTOMVIEWNAME (Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
private RecyclerContextMenuInfo mContextMenuInfo;
@Override
protected ContextMenu.ContextMenuInfo getContextMenuInfo() {
return mContextMenuInfo;
}
@Override
public boolean showContextMenuForChild(View originalView) {
final int longPressPosition = getChildAdapterPosition(originalView);
if (longPressPosition >= 0) {
final long longPressId = getAdapter().getItemId(longPressPosition);
mContextMenuInfo = new RecyclerContextMenuInfo(longPressPosition, ` longPressId);
return super.showContextMenuForChild(originalView);
}
return false;
}
public class RecyclerContextMenuInfo implements ContextMenu.ContextMenuInfo {
public RecyclerContextMenuInfo(int position, long id) {
this.position = position;
this.id = id;
}
final public int position;
final public long id;
}
}
Ora nel tuo frammento o attività implementa i seguenti metodi.
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
// Inflate Menu from xml resource
MenuInflater menuInflater = getMenuInflater();
menuInflater.inflate(R.menu.context_menu, menu);
}
@Override
public boolean onContextItemSelected(MenuItem item) {
ContextMenuRecyclerView.RecyclerContextMenuInfo info = (ContextMenuRecyclerView.RecyclerContextMenuInfo) item.getMenuInfo();
Toast.makeText(InstanceOfContext , " User selected " + info.position, Toast.LENGTH_LONG).show();
return false;
}
Infine registrati per il menu contestuale su recyclerview
//for showing a popup on LongClick of items in recycler.
registerForContextMenu(recyclerView);
Dovrebbe funzionare!
Ecco come implementare il menu di scelta rapida per RecyclerView e ottenere la posizione dell'elemento per il quale è stata selezionata la voce del menu di scelta rapida:
public class YourAdapter extends RecyclerView.Adapter<YourAdapter.ViewHolder> {
...
@Override
public void onBindViewHolder(@NonNull final ViewHolder viewHolder, int position) {
...
viewHolder.itemView.setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() {
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
menu.add(0, R.id.mi_context_disable, 0, R.string.text_disable)
.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
// can do something with item at position given below,
// viewHolder is final
viewHolder.getAdapterPosition();
return true;
}
});
menu.add(0, R.id.mi_context_remove, 1, R.string.text_remove)
.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
// can do something with item at position given below,
// viewHolder is final
viewHolder.getAdapterPosition();
return true;
}
});
}
});
}
static class ViewHolder extends RecyclerView.ViewHolder {
private View itemView;
private ViewHolder(@NonNull View itemView) {
super(itemView);
this.itemView = itemView;
}
}
}
Una soluzione per coloro che desiderano ottenere l'ID oggetto durante la chiamata ContextMenu
.
Se hai un RecyclerView
con elementi come questo (contenente cliccabile ImageView
):
allora dovresti ricevere callback da onClickListener
.
Adattatore
class YourAdapter(private val contextMenuCallback: ContextMenuCallback) :
RecyclerView.Adapter<YourAdapter.ViewHolder>() {
private var items: MutableList<Item> = mutableListOf()
...
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
val item = items[position] as Item
updateItem(viewHolder, item)
setOnClickListener(viewHolder.itemView, items[position].id, items[position].title)
}
private fun setOnClickListener(view: View, id: Int, title: String) {
// view.setOnClickListener { v -> }
// A click listener for ImageView `more`.
view.more.setOnClickListener {
// Here we pass item id, title, etc. to Fragment.
contextMenuCallback.onContextMenuClick(view, id, title)
}
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val titleTextView: TextView = itemView.title
}
class Item(
val id: Int,
val title: String
)
interface ContextMenuCallback {
fun onContextMenuClick(view: View, id: Int, title: String)
}
}
Frammento
class YourFragment : Fragment(), YourAdapter.ContextMenuCallback {
private var adapter: YourAdapter? = null
private var linearLayoutManager: LinearLayoutManager? = null
private var selectedItemId: Int = -1
private lateinit var selectedItemTitle: String
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
adapter = YourAdapter(this)
view.recycler_view.apply {
layoutManager = linearLayoutManager
adapter = this@YourFragment.adapter
setHasFixedSize(true)
}
registerForContextMenu(view.recycler_view)
}
override fun onCreateContextMenu(menu: ContextMenu?, v: View?,
menuInfo: ContextMenu.ContextMenuInfo?) {
activity?.menuInflater?.inflate(R.menu.menu_yours, menu)
}
override fun onContextItemSelected(item: MenuItem?): Boolean {
super.onContextItemSelected(item)
when (item?.itemId) {
R.id.action_your -> yourAction(selectedItemId, selectedItemTitle)
...
}
return true
}
override fun onContextMenuClick(view: View, id: Int, title: String) {
// Here we accept item id, title from adapter and show context menu.
selectedItemId = id
selectedItemTitle = title
view.showContextMenu()
}
}
Avvertimento!
Se utilizzi un ViewPager
basato su un frammento (tutte le pagine sono elenchi simili), dovrai affrontare un problema. Quando esegui l'override onContextItemSelected
per capire quale voce di menu è stata selezionata, otterrai un ID voce di elenco dalla prima pagina! Per superare questo problema, vedere il frammento errato in ViewPager riceve la chiamata onContextItemSelected .
Ho lottato su questo perché Android non lo gestisce bene per me in RecyclerView, che funzionava molto bene per ListView.
La parte più difficile è che la parte ContextMenuInfo è incorporata in una vista, che non puoi facilmente allegare se non sovrascrivendo la vista.
Quindi avrai bisogno di un wrapper che ti aiuti a fornire le informazioni sulla posizione all'attività.
public class RecyclerContextMenuInfoWrapperView extends FrameLayout {
private RecyclerView.ViewHolder mHolder;
private final View mView;
public RecyclerContextMenuInfoWrapperView(View view) {
super(view.getContext());
setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
mView = view;
addView(mView);
}
public void setHolder(RecyclerView.ViewHolder holder) {
mHolder = holder;
}
@Override
protected ContextMenu.ContextMenuInfo getContextMenuInfo() {
return new RecyclerContextMenuInfo(mHolder.getPosition(), mHolder.getItemId());
}
public static class RecyclerContextMenuInfo implements ContextMenu.ContextMenuInfo {
public RecyclerContextMenuInfo(int position, long id) {
this.position = position;
this.id = id;
}
final public int position;
final public long id;
}
}
Quindi nel tuo RecyclerAdapter, quando crei ViewHolders, devi impostare il wrapper come vista radice e registrare contextMenu su ogni vista.
public static class AdapterViewHolder extends RecyclerView.ViewHolder {
public AdapterViewHolder( View originalView) {
super(new RecyclerContextMenuInfoWrapperView(originalView);
((RecyclerContextMenuInfoWrapperView)itemView).setHolder(this);
yourActivity.registerForContextMenu(itemView);
itemView.setOnCreateContextMenuListener(yourListener);
}
}
Infine, nella tua attività, sarai in grado di fare ciò che fai di solito:
@Override
public boolean onContextItemSelected(MenuItem item) {
int position = ((RecyclerContextMenuInfoWrapperView.RecyclerContextMenuInfo)item.getMenuInfo()).position;
// do whatever you need as now you have access to position and id and everything
La cosa migliore era utilizzare il menu contestuale con la visualizzazione riciclatore è se si crea una visualizzazione riciclatore personalizzata e si sovrascrive il getContextMenuInfo()
metodo e si restituisce la propria istanza dell'oggetto informazioni del menu contestuale in modo da poter recuperare le posizioni quando è stato creato e quando si fa clic sul menu:
@Override
protected ContextMenu.ContextMenuInfo getContextMenuInfo() {
return mContextMenuInfo;
}
Dai un'occhiata a questa sintesi che ho creato:
Espandendo un po 'alcune delle risposte sopra, se si desidera evitare di definire manualmente il menu nel codice in Adapter / ViewHolder, è possibile utilizzare un PopupMenu e gonfiare le opzioni di menu da un file di risorse menu.xml standard.
L'esempio seguente mostra questo, inclusa la capacità di trasmettere un listener che puoi implementare nel tuo frammento / attività per rispondere ai clic del menu contestuale.
public class CustomAdapter extends RecyclerView.Adapter<CustomAdapter.ViewHolder> {
private List<CustomObject> objects;
private OnItemSelectedListener listener;
private final boolean withContextMenu;
class ViewHolder extends RecyclerView.ViewHolder
implements View.OnClickListener, View.OnCreateContextMenuListener, PopupMenu.OnMenuItemClickListener {
@BindView(R.id.custom_name)
TextView name;
@BindView(R.id.custom_value)
TextView value;
ViewHolder(View view) {
super(view);
ButterKnife.bind(this, view);
view.setOnClickListener(this);
if (withContextMenu) {
view.setOnCreateContextMenuListener(this);
}
}
@Override
public void onClick(View v) {
int position = getAdapterPosition();
if (listener != null) {
listener.onCustomerSelected(objects.get(position));
}
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
PopupMenu popup = new PopupMenu(v.getContext(), v);
popup.getMenuInflater().inflate(R.menu.custom_menu, popup.getMenu());
popup.setOnMenuItemClickListener(this);
popup.show();
}
@Override
public boolean onMenuItemClick(MenuItem item) {
if (listener != null) {
CustomObject object = objects.get(getAdapterPosition());
listener.onCustomerMenuAction(object, item);
}
return false;
}
}
public CustomerAdapter(List<CustomObject> objects, OnItemSelectedListener listener, boolean withContextMenu) {
this.listener = listener;
this.objects = objects;
this.withContextMenu = withContextMenu;
}
public interface OnItemSelectedListener {
void onSelected(CustomObject object);
void onMenuAction(CustomObject object, MenuItem item);
}
@Override
public CustomerAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.snippet_custom_object_line, parent, false);
return new ViewHolder(v);
}
@Override
public void onBindViewHolder(CustomAdapter.ViewHolder holder, int position) {
CustomObject object = objects.get(position);
holder.name.setText(object.getName());
holder.value.setText(object.getValue());
}
@Override
public int getItemCount() {
return objects.size();
}
}
Riepilogo completo qui https://gist.github.com/brettwold/45039b7f02ce752ae0d32522a8e2ad9c
Puoi passare OnCreateContextMenuListener a ViewHolder durante il bind. Questo ascoltatore può creare menu personalizzati per ogni elemento di dati. Aggiungi semplicemente setOnCreateContextMenuListener nel tuo ViewHolder e chiamalo durante l'associazione.
public static class ItemViewHolder extends RecyclerView.ViewHolder
{
public ItemViewHolder(View itemView) {
super(itemView);
}
void setOnCreateContextMenuListener(View.OnCreateContextMenuListener listener) {
itemView.setOnCreateContextMenuListener(listener);
}
}
Nell'adattatore:
@Override
public void onBindViewHolder(ItemViewHolder viewHolder,
int position) {
final MyObject myObject = mData.get(position);
viewHolder.setOnCreateContextMenuListener(new OnCreateContextMenuListener(){
@Override
public void onCreateContextMenu(ContextMenu menu,
View v, ContextMenuInfo menuInfo) {
switch (myObject.getMenuVariant() {
case MNU_VARIANT_1:
menu.add(Menu.NONE, CTX_MNU_1,
Menu.NONE,R.string.ctx_menu_item_1);
menu.add(Menu.NONE, CTX_MNU_2,Menu.NONE, R.string.ctx_menu_item_2);
break;
case MNU_VARIANT_2:
menu.add(Menu.NONE, CTX_MNU_3,Menu.NONE, R.string.ctx_menu_item_3);
break;
default:
menu.add(Menu.NONE, CTX_MNU_4,
Menu.NONE, R.string.ctx_menu_item_4);
}
}
});
}
Nel mio caso ho dovuto utilizzare i dati del mio frammento nel onContextItemSelected()
metodo. La soluzione che ho scelto è stata quella di passare un'istanza del frammento nel mio adattatore e registrare l'elemento di visualizzazione nel contenitore della visualizzazione:
@Override
public void onBindViewHolder(final MyListAdapter.ViewHolder viewHolder, int position) {
final Object rowObject = myListItems.get(position);
// Do your data binding here
viewHolder.itemView.setTag(position);
fragment.registerForContextMenu(viewHolder.itemView);
}
Quindi onCreateContextMenu()
puoi salvare l'indice in una variabile locale:
selectedViewIndex = (int)v.getTag();
e recuperalo in onContextItemSelected()
La prima volta che mi sono imbattuto in questo problema con normali adattatori, ho finito per creare la mia sottoclasse di visualizzazione personalizzata e memorizzare le cose di cui avevo bisogno. Non mi piaceva davvero quella soluzione e ho passato molto tempo a guardare le grandi idee che le persone hanno proposto, e ho deciso che non mi piacevano di più. Quindi ho messo tutto insieme, l'ho scosso per un po 'e sono uscito con qualcosa di nuovo che mi piace.
Iniziamo con un paio di lezioni di utilità. ContextMenuHandler è un'interfaccia per qualsiasi oggetto gestirà il menu contestuale. In pratica, questa sarà una sottoclasse ViewHolder, ma in teoria potrebbe essere qualsiasi cosa
/**
* Interface for objects that wish to create and handle selections from a context
* menu associated with a view
*/
public interface ContextMenuHandler extends View.OnCreateContextMenuListener {
boolean onContextItemSelected(MenuItem item);
}
La prossima è un'interfaccia che deve essere implementata da qualsiasi vista che verrà utilizzata come figlio immediato di un RecyclerView.
public interface ViewWithContextMenu {
public void setContextMenuHandler(FragmentWithContextMenu fragment, ContextMenuHandler handler);
public ContextMenuHandler getContextMenuHandler();
}
Successivamente, qualsiasi vista che creerà un menu di scelta rapida come figlio di RecylcerView deve implementare ViewWIthContextMenu. Nel mio caso, avevo solo bisogno di una sottoclasse di LinearLayout.
public class LinearLayoutWithContextMenu extends LinearLayout implements ViewWithContextMenu {
public LinearLayoutWithContextMenu(Context context) {
super(context);
}
public LinearLayoutWithContextMenu(Context context, AttributeSet attrs) {
super(context, attrs);
}
private ContextMenuHandler handler;
@Override
public void setContextMenuHandler(FragmentWithContextMenu fragment, ContextMenuHandler handler) {
this.handler = handler;
setOnCreateContextMenuListener(fragment);
}
@Override
public ContextMenuHandler getContextMenuHandler() {
return handler;
}
}
Infine, abbiamo bisogno di una classe Fragment truccata per intercettare le chiamate del menu contestuale e reindirizzarle al gestore appropriato.
public class FragmentWithContextMenu extends Fragment {
ContextMenuHandler handler = null;
@Override
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, view, menuInfo);
handler = null;
if (view instanceof ViewWithContextMenu) {
handler = ((ViewWithContextMenu)view).getContextMenuHandler();
if (handler != null) handler.onCreateContextMenu(menu, view, menuInfo);
}
}
@Override
public boolean onContextItemSelected(MenuItem item) {
if (handler != null) {
if (handler.onContextItemSelected(item)) return true;
}
return super.onContextItemSelected(item);
}
}
Con tutto questo in atto, l'implementazione finale è piuttosto semplice. Il frammento principale deve essere sottoclasse FragmentWithContextMenu. Imposta normalmente la RecylerWindow principale e passa alla sottoclasse Adapter. La sottoclasse Adapter è simile
public class Adapter extends RecyclerView.Adapter<Adapter.ViewHolder> {
private final FragmentWithContextMenu fragment;
Adapter(FragmentWithContextMenu fragment) {
this.fragment = fragment;
}
@Override
public Adapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context)
.inflate(R.layout.child_view, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(final Adapter.ViewHolder holder, int position) {
// Logic needed to bind holder to specific position
// ......
}
@Override
public int getItemCount() {
// Logic to return current item count
// ....
}
public class ViewHolder extends RecyclerView.ViewHolder implements ContextMenuHandler {
ViewHolder(View view) {
super(view);
((ViewWithContextMenu)view).setContextMenuHandler(fragment, this);
view.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// Do stuff to handle simple clicks on child views
// .......
}
});
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
// Logic to set up context menu goes here
// ....
}
@Override
public boolean onContextItemSelected(MenuItem item) {
// Logic to handle context menu item selections goes here
// ....
return true;
}
}
}
Questo è tutto. Sembra che tutto funzioni. Ha messo tutte le classi di utilità in un pacchetto di menu contestuale separato in modo che avrei potuto dare i nomi delle classi che corrispondono alle classi che ci sono sottoclassi, ma ho pensato che sarebbe stato più confuso.
Ok, sulla base della risposta di @ Flexo, metterò mPosition all'ordine ...
protected class ExampleViewHolder extends RecyclerView.ViewHolder implements View.OnCreateContextMenuListener {
int mPosition;
public KWViewHolder(View itemView) {
super(itemView);
itemView.setOnCreateContextMenuListener(this);
}
public void setPosition(int position) {
mPosition = position;
}
@Override
public void onCreateContextMenu(ContextMenu contextMenu, View view, ContextMenu.ContextMenuInfo contextMenuInfo) {
contextMenu.setHeaderTitle(R.string.menu_title_context);
contextMenu.add(0, R.id.menu_delete, mPosition, R.string.delete);
}
}
quindi in onContextItemSelected utilizzo
item.getOrder()
E tutto funziona bene. Ottengo facilmente la posizione dell'array