Come creare un menu contestuale per RecyclerView


97

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?


Sono nella stessa barca. Ho provato varie valutazioni ListView con AdapterContextMenuInfo - ma non riesco a ottenere le informazioni.position
RoundSparrow hilltx

Penso che i giorni del menu contestuale siano finiti.
Binoy Babu

4
Hey - ho ottenuto che funziona;) refeerence: stackoverflow.com/questions/2321332/... - il ViewHolder per me è l'ascoltatore onClick - ho anche fatto l'OnCreateContextMenuListener. La chiave di tutto ciò è che mi sono reso conto che solo UN menu può essere aperto alla volta, quindi l'adattatore ha solo bisogno di un int per dire quale era l'ultimo elemento dell'elenco RecyclerView che aveva fatto clic sul menu ... quindi il frammento / attività può chiedere all'adattatore quando ottiene il clic della voce di menu effettiva.
RoundSparrow hilltx

Risposte:


92

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.


7
Ok, quindi implementiamo un onContextItemSelectedlivello di attività / frammento. getTitlefunziona, getItemIdfunziona, ma getMenuInfofornisce null. Quindi, come ottenere il ViewHolder?
Michael Schmidt

@MichaelSchmidt puoi mostrarmi il tuo codice dove implementi le informazioni del menu contestuale.
Prabhakar

7
Dopo aver sperimentato un po 'non riesco nemmeno a farlo funzionare, 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 RecyclerViewo Fragment)? Può funzionare, ma poi ci porta ad altre risposte a questa domanda.
NeilS

3
Prabhakbar non hai menzionato come ottenere l'elemento cliccato né la vista?
Benjamin Stürmer,

6
È possibile utilizzare menu.add(this.getAdapterPosition(), v.getId(), 0, "Call");e quindi nel test del metodo di richiamata per item.getGroupId()ottenere la posizione
JacobPariseau,

99

Grazie per le informazioni e i commenti. Sono stato in grado di ottenere ContextMenuper articoli in Recyclerview.

Ecco cosa ho fatto

nel onViewCreatedmetodo 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 ViewHolderimplementare 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);
    }
}

onBindViewHoldermetodo aggiungi OnLongClickListenersu 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 onViewRecycledrimuovere 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 onContextItemSelectedcome 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);
}

1
position = ((BackupRestoreListAdapter) getAdapter ()). getPosition (); -> ottieni errore: il metodo getAdapter () non è definito per il tipo ..., puoi mostrare il metodo
getAdapter

1
Ottimo suggerimento, grazie. Minore: viewHolder.getPosition () è deprecato. Qual è il tuo consiglio per migliorare?
tm1701

10
usa viewHolder.getAdapterPosition () invece di getPosition ()
Sagar Chavada

2
Per quelli che ottengono l'errore getAdapter (), l'ho risolto salvando un riferimento al mio RecyclerView e quindi usandolo come: ((BackupRestoreListAdapter) recyclerView.getAdapter ()). GetPosition ();
Kevin Amorim

1
E dov'è esattamente R.id.ctx_menu_remove_backup?
akubi

29

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();
    }
}

Per favore, aggiungi il costruttore RecyclerView che dovevi implementare su ContextMenuRecyclerView. Inoltre, getChildPosition()ora è deprecato. Ho usato getChildAdapterPosition()invece.
Juan José Melero Gómez

1
Nota: mi spiace, ho dimenticato di aggiungere: getChildPosition()è deprecato in com.android.support:recyclerview-v7:22.0.0.
Juan José Melero Gómez

1
Questo non funziona. non è possibile accedere a getView (). showContextMenu () da RecyclerView.View.ViewHolder.
Mulgard

4
Questo funziona per me. Ma non è necessario avere onLongClick () in MyViewHolder, è sufficiente impostare itemView.setLongClickable (true) nel costruttore, il menu contestuale apparirà quando OnLongClickListener non è registrato.
ontani

2
Importante anche: mentre estendi RecyclerView a ContextMenuRecyclerView, non dimenticare di AGGIUNGERE I COSTRUTTORI suggeriti da IDE. In particolare, se non si implementa il costruttore a due argomenti che accetta Context e AttributeSet, Android non sarà in grado di aumentare l'XML del layout.
lidkxx

24

Prova questo per un Viewelemento 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 ViewHolderelemento


Esattamente quello di cui avevo bisogno per il menu di scelta rapida su una visualizzazione immagine all'interno di una visualizzazione riciclata
Themos

2
Per chiunque lo guardi ora, funziona solo su API 23 e versioni successive.
SWoo

16

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 onContextItemSelectedcallback, ma ContextMenuInfonon è disponibile ( null) in questo caso (se il getContextMenuInfo()metodo non è sovrascritto per una vista premuta). Quindi, la soluzione più semplice è aggiungere OnMenuItemClickListenerdirettamente 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;
        }
    };
}

1
Spiega chiaramente cosa fa lo snippet di codice e come. Solo un codice incollato non è sufficiente, anche se è utile.
peterh - Ripristina Monica il

Questo sembra essere un ragionevole compromesso se non si desidera creare sottoclassi personalizzate RecyclerViewsolo per sovrascrivere getContextMenuInfoecc., 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 Contextdi una delle viste associate a volte possa essere sufficiente.
qix

10

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:

  • RecyclerContextMenuInfoe RecyclerViewContextMenuInfosono della stessa classe. Scegli un nome e mantienilo.
  • Il ViewHolderdeve implementare View.OnLongClickListenere ricordare di richiamare setOnLongClickListener()l'elemento nel costruttore.
  • Nel onLongClick()ascoltatore, getView().showContextMenu()è completamente sbagliato. Devi chiamare il showContextMenuForChild()tuo ContextMenuRecyclerView, altrimenti ContextMenuInfoentri 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 RecyclerViewcon ContextMenuRecyclerViewnel tuo layout!


1
Grazie @ josh2112 per avermi segnalato errori di battitura, li ho appena corretti, MA per quanto riguarda il tuo ultimo punto, puoi sostituirlo recyclerView.showContextMenuForChild(itemView);con itemView.showContextMenu().
Renaud Cerrato

1
Importante anche: mentre estendi RecyclerView a ContextMenuRecyclerView, non dimenticare di AGGIUNGERE I COSTRUTTORI suggeriti da IDE. In particolare, se non si implementa il costruttore a due argomenti che accetta Context e AttributeSet, Android non sarà in grado di aumentare l'XML del layout.
lidkxx

5

Ecco un modo pulito per utilizzare il contesto del menu sugli elementi RecyclerView

Innanzitutto, hai bisogno di una posizione dell'oggetto

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 onBindViewHolderhook 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 😋

Secondo, crea il tuo menu

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 onCreateContextMenue 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;
}

Terzo, torniamo all'oggetto Adapter

  1. Registra il tuo menu contestuale.
  2. 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


Grazie! Come passare i dati al menu contestuale? Ad esempio, ID articolo, testo articolo e così via.
CoolMind

4

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    
            }       
    }       

} 

2
Questo è il modo più naturale e controllabile per farlo
nima

3

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);
    }
}

2

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;
    }

}

1

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.


1

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!


1

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;
    }
}

}


1

Una soluzione per coloro che desiderano ottenere l'ID oggetto durante la chiamata ContextMenu.

Se hai un RecyclerViewcon elementi come questo (contenente cliccabile ImageView):

elenco

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 ViewPagerbasato su un frammento (tutte le pagine sono elenchi simili), dovrai affrontare un problema. Quando esegui l'override onContextItemSelectedper 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 .


0

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

0

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:

https://gist.github.com/resengupta/2b2e26c949b28f8973e5


0

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


0

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);   

                        }
                    }

                });
     }

0

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()


0

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.


-1

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.