Cómo crear un menú contextual para RecyclerView

97

¿Cómo implemento el menú contextual para RecyclerView?aparentemente llamar registerForContextMenu(recyclerView)no funciona? Lo llamo desde un fragmento. ¿Alguien tuvo éxito al implementar esto?

Binoy Babu
fuente
Estoy en el mismo barco. Probé varias aplicaciones de ListView con AdapterContextMenuInfo, pero no puedo obtener la posición de información
RoundSparrow hilltx
Creo que los días del menú contextual se acabaron.
Binoy Babu
4
Oye, lo hice funcionar;) refeerence: stackoverflow.com/questions/2321332/… - ViewHolder para mí es el oyente onClick - también lo hice el OnCreateContextMenuListener. La clave de todo esto es que me di cuenta de que solo se puede abrir UN menú a la vez, por lo que el adaptador solo necesita un int para saber cuál fue el último elemento de la lista RecyclerView en el que se hizo clic en el menú ... entonces el Fragmento / Actividad puede pregunte al adaptador cuando obtenga el clic del elemento de menú real.
RoundSparrow hilltx

Respuestas:

92

No puede implementar directamente estos métodos como onClickListener , OnContextMenuListener , etc. porque RecycleView extiende android.view.ViewGroup . Entonces no podemos usar directamente este método. Podemos implementar estos métodos en la clase de adaptador ViewHolder . Podemos usar el menú contextual en RecycleView de esta manera:

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


    }

Ahora seguimos el mismo procedimiento mientras implementamos el menú contextual.

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

}

Si tiene alguna dificultad, pregunte en un comentario.

Prabhakar
fuente
7
Ok, y luego implementamos un nivel onContextItemSelectedde actividad / fragmento. getTitlefunciona, getItemIdfunciona, pero getMenuInfoentrega nula. Entonces, ¿cómo conseguirlo ViewHolder?
Michael Schmidt
@MichaelSchmidt puede mostrarme su código donde implementa la información del menú contextual.
Prabhakar
7
Después de experimentar un poco, tampoco puedo hacer que esto funcione, getMenuInfo()devuelve nulo en onContextItemSelected(). ¿Quizás las personas que lo hicieron funcionar tienen un onCreateContextMenu()método en una vista más arriba en la jerarquía (el RecyclerViewo Fragment)? Eso puede funcionar, pero luego nos lleva a otras respuestas a esta pregunta.
NeilS
3
Prabhakbar, ¿no mencionaste cómo obtener el elemento seleccionado ni la vista?
Benjamin Stürmer
6
Puede usar menu.add(this.getAdapterPosition(), v.getId(), 0, "Call");y luego en su método de devolución de llamada para item.getGroupId()obtener la posición
JacobPariseau
99

Gracias por la información y los comentarios. Pude lograr ContextMenuartículos en Recyclerview.

Aquí esta lo que hice

en el onViewCreatedmétodo de Fragment o el onCreatemétodo de Activity :

registerForContextMenu(mRecyclerView);

Luego, en Adaptador, agregue

private int position;

public int getPosition() {
    return position;
}

public void setPosition(int position) {
    this.position = position;
}

hacer que la ViewHolderclase implementeOnCreateContextMenuListener

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

onBindViewHolderagregue OnLongClickListenerel método holder.itemView para capturar la posición antes de que se cargue el menú contextual:

holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
    @Override
    public boolean onLongClick(View v) {
        setPosition(holder.getPosition());
        return false;
    }
});

Luego, onViewRecycledelimine el Listener para que no haya problemas de referencia. (puede que no sea necesario).

@Override
public void onViewRecycled(ViewHolder holder) {
    holder.itemView.setOnLongClickListener(null);
    super.onViewRecycled(holder);
}

Finalmente, en el Fragmento / Actividad anule lo onContextItemSelectedsiguiente:

@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);
}
Hardik Shah
fuente
1
position = ((BackupRestoreListAdapter) getAdapter ()). getPosition (); -> obtener error: el método getAdapter () no está definido para el tipo ..., ¿puede mostrar el método
getAdapter
1
Gran sugerencia, gracias. Menor: viewHolder.getPosition () está obsoleto. ¿Cuál es tu consejo para mejorar?
tm1701
10
use viewHolder.getAdapterPosition () en lugar de getPosition ()
Sagar Chavada
2
Para aquellos que obtienen el error getAdapter (), lo resolví guardando una referencia a mi RecyclerView y luego lo uso como: ((BackupRestoreListAdapter) recyclingView.getAdapter ()). GetPosition ();
Kevin Amorim
1
¿Y dónde está exactamente R.id.ctx_menu_remove_backup?
Akubi
29

La respuesta actual no es correcta. Aquí hay una implementación funcional:

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

En su Fragmento (o Actividad):

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

Y finalmente, en su ViewHolder:

class MyViewHolder extends RecyclerView.View.ViewHolder {
    ...
    private void onLongClick() {
        itemView.showContextMenu();
    }
}
Renaud Cerrato
fuente
Por favor, agregue el constructor RecyclerView que tuvo que implementar en su ContextMenuRecyclerView. Y también, getChildPosition()ahora está en desuso. Usé en su getChildAdapterPosition()lugar.
Juan José Melero Gómez
1
Nota: Lo siento, olvidé agregar: getChildPosition()está obsoleto en com.android.support:recyclerview-v7:22.0.0.
Juan José Melero Gómez
1
esto no está funcionando. no puede acceder a getView (). showContextMenu () desde RecyclerView.View.ViewHolder.
Mulgard
4
Esto funciona para mi. Pero no es necesario tener onLongClick () en MyViewHolder, es suficiente establecer itemView.setLongClickable (true) en el constructor, el menú contextual aparecerá cuando OnLongClickListener no esté registrado.
alisos
2
Importante también: al extender RecyclerView a ContextMenuRecyclerView, no olvide AGREGAR LOS CONSTRUCTORES sugeridos por IDE. Específicamente, si no implementa el constructor de dos argumentos que toma Context y AttributeSet, Android no podrá inflar su XML de diseño.
lidkxx
24

Pruebe esto para un Viewartículo en recyclingView

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

Puede usarlo para configurar datos en un ViewHolderelemento

Sergey Bondarenko
fuente
Exactamente lo que necesitaba para el menú contextual en una vista de imagen dentro de una vista reciclada
Themos
2
Para cualquiera que esté viendo esto ahora, esto solo funciona en API 23 y superior.
SWoo el
16

La respuesta de Prabhakar es correcta, pero no explicó cómo obtener datos, relacionados con el elemento presionado, cuando se selecciona un elemento del menú contextual. Podemos usar la onContextItemSelecteddevolución de llamada, pero ContextMenuInfono está disponible ( null) en este caso (si el getContextMenuInfo()método no se anula para una vista presionada). Entonces, la solución más simple es agregar OnMenuItemClickListenerdirectamente al 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;
        }
    };
}
Valeriy Katkov
fuente
1
Explique claramente qué hace su fragmento de código y cómo. Solo un código copiado y pegado no es suficiente, incluso si vale la pena.
peterh - Reincorporar a Monica
Esto parece ser un compromiso razonable si no desea crear subclases personalizadas RecyclerViewsolo para anular getContextMenuInfo, etc., incluso si no es tan eficiente como dejar que el fragmento / actividad maneje los clics. Los oyentes tendrán acceso a los datos en el soporte, por lo que no debería necesitar posición. Y, en teoría, podría almacenar en caché la posición al vincularse en su adaptador de todos modos, y usar la delegación para llamar a su titular si es necesario, aunque usar Contextdesde una de las vistas vinculadas a veces puede ser suficiente.
qix
10

La respuesta de @ Renaud funcionó para mí, pero primero requirió varias correcciones de código. Es como si hubiera publicado fragmentos de varias iteraciones diferentes de su código. Los cambios que deben realizarse son:

  • RecyclerContextMenuInfoy RecyclerViewContextMenuInfoson de la misma clase. Elija un nombre y apéguese a él.
  • El ViewHolderdebe poner en práctica View.OnLongClickListener, y recordar a llamar setOnLongClickListener()en el elemento en el constructor.
  • En el onLongClick()oyente, getView().showContextMenu()está completamente equivocado. Debes llamar showContextMenuForChild()a tu ContextMenuRecyclerView, de lo contrario el ContextMenuInfoentrarás onCreateContextMenu()y onContextItemSelected()será nulo.

Mi código editado a continuación:

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

En tu fragmento:

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

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

Además, ¡asegúrese de reemplazar RecyclerViewcon ContextMenuRecyclerViewen su diseño!

josh2112
fuente
1
Gracias @ josh2112 por señalarme errores tipográficos, los acabo de corregir, PERO con respecto a su último punto, puede reemplazar recyclerView.showContextMenuForChild(itemView);por itemView.showContextMenu().
Renaud Cerrato
1
Importante también: al extender RecyclerView a ContextMenuRecyclerView, no olvide AGREGAR LOS CONSTRUCTORES sugeridos por IDE. Específicamente, si no implementa el constructor de dos argumentos que toma Context y AttributeSet, Android no podrá inflar su XML de diseño.
lidkxx
5

Aquí hay una forma limpia de usar el contexto del menú en los elementos de RecyclerView

Primero, necesita una posición de artículo

En clase Adaptador:

 /**
 * Custom on long click item listener.
 */
onLongItemClickListener mOnLongItemClickListener;

public void setOnLongItemClickListener(onLongItemClickListener onLongItemClickListener) {
    mOnLongItemClickListener = onLongItemClickListener;
}

public interface onLongItemClickListener {
    void ItemLongClicked(View v, int position);
}

En onBindViewHolderenganchar al oyente personalizado:

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

En MainActivity (Actividad / Fragmento) cree un campo:

private int mCurrentItemPosition;

En su objeto Adaptador, configure el oyente personalizado:

    mAdapter.setOnLongItemClickListener(new FileAdapter.onLongItemClickListener() {
        @Override
        public void ItemLongClicked(View v, int position) {
            mCurrentItemPosition = position;
        }
    });

Ahora tiene una posición deliciosa para cualquier elemento en el que haya hecho clic durante mucho tiempo 😋

Segundo, crea tu menú

En res -> menú Crear un archivo que contiene su elemento de menú 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>

En MainActivity: Implemente ambos onCreateContextMenuy 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;
}

En tercer lugar, de vuelta a su objeto Adaptador

  1. Registre su menú contextual.
  2. muestra el menú contextual.

    registerForContextMenu(mRecyclerView);
    mAdapter.setOnLongItemClickListener(new FileAdapter.onLongItemClickListener() {
        @Override
        public void ItemLongClicked(View v, int position) {
            mCurrentItemPosition = position;
            v.showContextMenu();
        }
    });

Espero no olvidar nada 🤔

Más información en Documentación de menús

MohammadL
fuente
¡Gracias! ¿Cómo pasar datos al menú contextual? Por ejemplo, identificación del artículo, texto del artículo, etc.
CoolMind
4

Aquí hay una forma más sencilla de hacerlo con Kotlin que funcionó para mí. El mayor desafío es averiguar la posición del elemento que se presionó. Dentro de su adaptador, puede colocar este fragmento de código y podrá capturar la posición del elemento para el que se muestra el menú contextual; eso es todo.

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

} 
Mickey Mouse
fuente
2
Esta es la forma más natural y controlable de hacerlo
nima
3

He combinado mi solución con la solución de @Hardik Shah:

En la actividad tengo:

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

En el Adaptador tengo:

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

En fragmento tengo:

@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);
    }
}
Formación
fuente
2

Quizás llego tarde a la fiesta, pero tengo una solución que funciona . He hecho una esencia para ello.

Agregar menú contextual 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;
    }

}
Gaurav Sharma
fuente
1

Hola chicos, salí con una alternativa que funciona para mí. Acabo de registrar mi itemView con registerContextMenu y ViewHolder Constructor, también configuro un onLongClikcListener en la misma vista. En la implementación de onLongClick (View v), simplemente obtengo la posición en la que se hizo clic con getLayoutPosition () y la guardo en una variable de instancia (creé una clase para representar estos datos, al igual que se espera que funcione ContextMenuInfo), pero lo más importante es hacer asegúrese de devolver falso en este método. Todo lo que tienes que hacer ahora es estar en onContextItemSelected (elemento MenuItem), leer los datos que almacenas en tu variable de instancia y si es válido, continúas con tus acciones. Aquí es un fragmento.

  public MyViewHolder(View itemView){
super(itemView);
registerForContextMenu(itemView);
itemView.setOnLongClickListener(this);
}

Hago que ViewHolder implemente OnLongClickListener, pero puedes hacerlo de la forma que prefieras.

@Override
public boolean onLongClick(View v){
    mCurrentLongItem = new ListItemInfo(v.getId(), getLayoutPosition());
    return false; // REMEMBER TO RETURN FALSE.
  }

También puede configurar esto en el adaptador o en otra Vista que tenga en ViewHolder (es decir, una TextView). Lo importante es la implementación de 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 mejor parte es que aún puede procesar el evento LongClick devolviendo verdadero en los casos que desee, y el conextMenu no aparecerá.

Este método funciona porque registerForContextView hace que View LongClickable, y cuando llega el momento de procesar el ContextMenu, el sistema llama a performLongClick, que primero llama a una implementación onLongClick, y si devuelve falso, llama a showContextMenu.

Raziel25
fuente
1

He estado usando esta solución durante algún tiempo y me ha funcionado bastante bien.

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

Ahora, en su fragmento o actividad, implemente los siguientes métodos.

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

Finalmente, regístrese para el contextMenu en la vista de reciclaje

   //for showing a popup on LongClick of items in recycler.
    registerForContextMenu(recyclerView);

¡Eso debería funcionar!

Rakesh Gopathi
fuente
1

A continuación, se muestra cómo puede implementar el menú contextual para RecyclerView y obtener la posición del elemento, para el que se ha seleccionado el elemento del menú contextual:

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

}

B-GangsteR
fuente
1

Una solución para aquellos que desean obtener la identificación del artículo al llamar ContextMenu.

Si tiene un RecyclerViewelemento con elementos como este (que contiene clics ImageView):

lista

entonces debería recibir devoluciones de llamada de onClickListener.

Adaptador

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

Fragmento

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

¡Advertencia!

Si usa un ViewPagerbasado en un fragmento (todas las páginas son listas similares), enfrentará un problema. Cuando anule onContextItemSelectedpara comprender qué elemento de menú se seleccionó, obtendrá una identificación de elemento de lista desde la primera página. Para superar este problema, consulte El fragmento incorrecto en ViewPager recibe la llamada onContextItemSelected .

CoolMind
fuente
0

He estado luchando con esto porque Android no maneja esto bien para mí en RecyclerView, que estaba funcionando muy bien para ListView.

La pieza más difícil es que la pieza ContextMenuInfo está incrustada dentro de una Vista, que no se puede adjuntar fácilmente más que anular la Vista.

Por lo tanto, necesitará un contenedor que lo ayude a entregar la información de la posición a la Actividad.

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

}

Luego, en su RecyclerAdapter, cuando crea ViewHolders, necesita configurar el Wrapper como la vista raíz y registrar contextMenu en cada 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);
    }

}

Y por último, en tu Actividad podrás hacer lo que haces habitualmente:

@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
Zoro
fuente
0

Lo mejor fue usar el menú contextual con la vista de reciclador si getContextMenuInfo()crea una vista de reciclador personalizada y anula el método y devuelve su propia instancia del objeto de información del menú contextual para que pueda buscar posiciones cuando se creó y cuando se hace clic en el menú:

@Override
protected ContextMenu.ContextMenuInfo getContextMenuInfo() {
    return mContextMenuInfo;
}

Eche un vistazo a esta esencia que he creado:

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

ReeSen
fuente
0

Ampliando un poco algunas de las respuestas anteriores, si desea evitar definir manualmente el menú en su código en el Adaptador / ViewHolder, puede usar un PopupMenu e inflar las opciones del menú desde un archivo de recursos estándar menu.xml.

El siguiente ejemplo muestra esto, incluida la capacidad de pasar un oyente que puede implementar en su Fragmento / Actividad para responder a los clics del menú contextual.

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

Resumen completo aquí https://gist.github.com/brettwold/45039b7f02ce752ae0d32522a8e2ad9c

Brett Cherrington
fuente
0

Puede pasar OnCreateContextMenuListener a ViewHolder en el enlace. Este oyente puede crear un menú personalizado para cada elemento de datos. Simplemente agregue setOnCreateContextMenuListener en su ViewHolder y llámelo durante el enlace.

     public static class ItemViewHolder extends RecyclerView.ViewHolder
     {


        public ItemViewHolder(View itemView) {
            super(itemView);


        }
        void setOnCreateContextMenuListener(View.OnCreateContextMenuListener listener) {
            itemView.setOnCreateContextMenuListener(listener);
        }

}

En adaptador:

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

                        }
                    }

                });
     }
Alexey Usmanov
fuente
0

En mi caso, tuve que usar datos de mi fragmento en el onContextItemSelected()método. La solución con la que terminé fue pasar una instancia del fragmento a mi adaptador y registrar el elemento de vista en el soporte de vista:

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

Luego onCreateContextMenu(), puede guardar el índice en una variable local:

selectedViewIndex = (int)v.getTag();

y recuperarlo en onContextItemSelected()

Eric B.
fuente
0

La primera vez que encontré este problema con los adaptadores normales, terminé creando mi propia subclase de vista personalizada y almacenando las cosas que necesitaba en ella. Realmente no me gustó esa solución y pasé mucho tiempo mirando las grandes ideas que la gente había propuesto, y decidí que no me gustaban más. Así que puse todo junto, lo sacudí por un tiempo y salí con algo nuevo que me gusta.

Comenzamos con un par de clases de servicios públicos. ContextMenuHandler es una interfaz para cualquier objeto que vaya a manejar el menú contextual. En la práctica, esta será una subclase de ViewHolder, pero en teoría podría ser casi cualquier 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);
}

A continuación, se muestra una interfaz que debe implementar cualquier vista que se vaya a utilizar como hijo inmediato de un RecyclerView.

public interface ViewWithContextMenu {
  public void setContextMenuHandler(FragmentWithContextMenu fragment, ContextMenuHandler handler);

  public ContextMenuHandler getContextMenuHandler();
}

A continuación, cualquier vista que vaya a crear un menú contextual como elemento secundario de un RecylcerView debe implementar ViewWIthContextMenu. En mi caso, solo necesitaba una subclase de 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;
  }
}

Y finalmente, necesitamos una clase Fragmento mejorada para interceptar las llamadas del menú contextual y redirigirlas al controlador apropiado.

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 todo esto en su lugar, la implementación final es bastante simple. El fragmento principal tiene que subclase FragmentWithContextMenu. Configura la RecylerWindow principal normalmente y se pasa a la subclase Adaptador. La subclase Adaptador se parece a

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

Eso es todo. Todo parece estar funcionando. Puso todas las clases de utilidad en un paquete de menú contextual separado para que pudiera haber dado los nombres de las clases que coincidían con las clases que hay en las subclases, pero pensé que sería más confuso.

Kencorbin
fuente
-1

Ok, basado en la respuesta de @ Flexo, pondré mPosition en orden ...

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

luego en onContextItemSelected utilizo

item.getOrder() 

Y todo funciona bien, obtengo la posición de la matriz fácilmente

windX
fuente