Como criar menu de contexto para RecyclerView

97

Como faço para implementar o menu de contexto para RecyclerView?Aparentemente, a chamada registerForContextMenu(recyclerView)não funciona. Estou chamando de um fragmento. Alguém teve algum sucesso na implementação disso?

Binoy Babu
fonte
Eu estou no mesmo barco. Tentei várias avaliações ListView com AdapterContextMenuInfo - mas não consigo obter o info.position
RoundSparrow hilltx
Acho que os dias de menu de contexto acabaram.
Binoy Babu
4
Ei - comecei a funcionar;) refeerence: stackoverflow.com/questions/2321332/… - o ViewHolder para mim é o ouvinte onClick - eu também o tornei o OnCreateContextMenuListener. A chave para tudo isso é que eu percebi que apenas UM Menu pode ser aberto por vez - então o adaptador só precisa de uma entrada para ser informado qual foi o último item da lista RecyclerView que teve o menu clicado ... então o Fragment / Activity pode pergunte ao adaptador quando ele obtém o clique real do item de menu.
RoundSparrow hilltx

Respostas:

92

Você não pode implementar diretamente esses métodos como onClickListener , OnContextMenuListener etc. porque RecycleView estende android.view.ViewGroup . Portanto, não podemos usar diretamente esses métodos. Podemos implementar esses métodos na classe do adaptador ViewHolder . Podemos usar o menu de contexto no RecycleView desta forma:

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


    }

Agora seguimos o mesmo procedimento ao implementar o menu de contexto.

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

}

Se você tiver alguma dificuldade, pergunte nos comentários.

Prabhakar
fonte
7
Ok, e então implementamos um onContextItemSelectednível de atividade / fragmento. getTitlefunciona, getItemIdfunciona, mas getMenuInfoentrega nulo. Então, como obter o ViewHolder?
Michael Schmidt
@MichaelSchmidt pode me mostrar seu código onde você implementa as informações do menu de contexto.
Prabhakar de
7
Depois de experimentar um pouco, também não consigo fazer isso funcionar, getMenuInfo()retorna nulo em onContextItemSelected(). Talvez as pessoas que o fizeram funcionar tenham um onCreateContextMenu()método em uma visão mais acima na hierarquia (o RecyclerViewou Fragment)? Isso pode funcionar, mas nos leva a outras respostas a essa pergunta.
NeilS de
3
Prabhakbar, você não mencionou como obter o item clicado nem a visualização?
Benjamin Stürmer
6
Você pode usar menu.add(this.getAdapterPosition(), v.getId(), 0, "Call");e, em seguida, em seu teste de método de retorno de chamada item.getGroupId()para obter a posição
JacobPariseau
99

Obrigado pela informação e comentários. Consegui alcançar ContextMenupara itens em Recyclerview.

Aqui está o que eu fiz

no onViewCreatedmétodo do Fragment ou no onCreatemétodo da Activity :

registerForContextMenu(mRecyclerView);

Depois, em Adaptador, adicione

private int position;

public int getPosition() {
    return position;
}

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

fazer a ViewHolderclasse implementarOnCreateContextMenuListener

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

onBindViewHoldermétodo add OnLongClickListenerno holder.itemView para capturar a posição antes de o menu de contexto ser carregado:

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

Em seguida, onViewRecycledremova o Listener para que não haja problemas de referência. (pode não ser necessário).

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

Finalmente, em Fragment / Activity, substitua o onContextItemSelectedcomo em:

@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
fonte
1
posição = ((BackupRestoreListAdapter) getAdapter ()). getPosition (); -> obter erro: O método getAdapter () é indefinido para o tipo ..., você pode mostrar o método
getAdapter
1
Ótima sugestão, obrigado. Menor: o viewHolder.getPosition () está obsoleto. Qual é o seu conselho para melhorar?
tm1701
10
use viewHolder.getAdapterPosition () em vez de getPosition ()
Sagar Chavada
2
Para aqueles que obtêm o erro getAdapter (), resolvi salvando uma referência ao meu RecyclerView e, em seguida, usei-o como: ((BackupRestoreListAdapter) recyclerView.getAdapter ()). GetPosition ();
Kevin Amorim
1
E onde está R.id.ctx_menu_remove_backup exatamente?
Akubi
29

A resposta atual não está correta. Esta é uma implementação 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;
  }
}

Em seu fragmento (ou atividade):

@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, finalmente, em seu ViewHolder:

class MyViewHolder extends RecyclerView.View.ViewHolder {
    ...
    private void onLongClick() {
        itemView.showContextMenu();
    }
}
Renaud Cerrato
fonte
Por favor, adicione o construtor RecyclerView que você teve que implementar em seu ContextMenuRecyclerView. E também, getChildPosition()está obsoleto agora. Eu usei em seu getChildAdapterPosition()lugar.
Juan José Melero Gómez
1
Nota: Desculpe, esqueci de adicionar: getChildPosition()está obsoleto em com.android.support:recyclerview-v7:22.0.0.
Juan José Melero Gómez
1
isto não está a funcionar. você não pode acessar getView (). showContextMenu () de RecyclerView.View.ViewHolder.
Mulgard
4
Isso funciona para mim. Mas não há necessidade de ter onLongClick () em MyViewHolder, basta definir itemView.setLongClickable (true) no construtor, o menu de contexto aparecerá quando OnLongClickListener não estiver registrado.
amieiros de
2
Importante também: ao estender o RecyclerView para ContextMenuRecyclerView, não se esqueça de ADICIONAR OS CONSTRUTORES sugeridos pelo IDE. Especificamente, se você não implementar o construtor de dois argumentos que leva Context e AttributeSet, o Android não será capaz de aumentar seu XML de layout.
lidkxx
24

Experimente isso para um Viewitem em 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;
                }
            });
        }
    });

Você pode usá-lo para definir dados para um ViewHolderitem

Sergey Bondarenko
fonte
Exatamente o que eu precisava para o menu de contexto em uma visualização de imagem dentro de uma visualização reciclada
Themos
2
Para quem está olhando para isso agora, isso só funciona na API 23 e superior.
SWoo
16

A resposta de Prabhakar está correta, mas ele não explicou como obter um dado, relacionado ao item pressionado, quando um item do menu de contexto é selecionado. Podemos usar onContextItemSelectedcallback, mas ContextMenuInfonão está disponível ( null) neste caso (se o getContextMenuInfo()método não for sobrescrito para uma visão pressionada). Portanto, a solução mais simples é adicionar OnMenuItemClickListenerdiretamente ao 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
fonte
1
Explique claramente o que seu trecho de código faz e como. Apenas um código copiado e colado não é suficiente, mesmo que valha a pena.
peterh - Reintegrar Monica em
Isso parece ser um meio-termo razoável se você não quiser criar subclasses personalizadas de RecyclerViewapenas para substituir getContextMenuInfoetc., mesmo que não seja tão eficiente quanto permitir que o fragmento / atividade manipule cliques. Os ouvintes terão acesso aos dados no suporte, portanto, você não precisa de posição. E, teoricamente, você poderia armazenar em cache a posição ao vincular em seu adaptador de qualquer maneira e usar a delegação para chamar seu titular, se necessário, embora usar Contextde uma das visualizações vinculadas às vezes possa ser suficiente.
qix
10

A resposta de @Renaud funcionou para mim, mas primeiro exigiu várias correções de código. É como se ele postasse trechos de várias iterações diferentes de seu código. As mudanças que precisam ser feitas são:

  • RecyclerContextMenuInfoe RecyclerViewContextMenuInfosão da mesma classe. Escolha um nome e fique com ele.
  • O ViewHolderdeve implementar View.OnLongClickListenere lembre-se de chamar setOnLongClickListener()o item no construtor.
  • No onLongClick()ouvinte, getView().showContextMenu()está completamente errado. Você deve chamar showContextMenuForChild()seu ContextMenuRecyclerView, caso contrário, ContextMenuInfovocê entrará onCreateContextMenu()e onContextItemSelected()será nulo.

Meu código editado abaixo:

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

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

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

Além disso, certifique-se de substituir RecyclerViewpor ContextMenuRecyclerViewem seu layout!

Josh2112
fonte
1
Obrigado @ josh2112 por apontar erros de digitação, acabei de corrigi-los - MAS, em relação ao seu último ponto, você pode substituir recyclerView.showContextMenuForChild(itemView);por itemView.showContextMenu().
Renaud Cerrato,
1
Importante também: ao estender o RecyclerView para ContextMenuRecyclerView, não se esqueça de ADICIONAR OS CONSTRUTORES sugeridos pelo IDE. Especificamente, se você não implementar o construtor de dois argumentos que leva Context e AttributeSet, o Android não será capaz de aumentar seu XML de layout.
lidkxx
5

Esta é uma maneira limpa de usar o contexto do menu em itens RecyclerView

Primeiro, você precisa de uma posição de item

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

No onBindViewHoldergancho do ouvinte 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;
            }
        });

Em MainActivity (Activity / Fragment) crie um campo:

private int mCurrentItemPosition;

No objeto Adaptador, defina o ouvinte personalizado:

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

Agora você tem uma posição deliciosa para qualquer item que clicou por muito tempo 😋

Segundo, crie seu menu

Em res -> menu Crie um arquivo contendo seu item de 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>

Em MainActivity: Implemente 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;
}

Terceiro, de volta ao seu objeto Adaptador

  1. Registre seu menu de contexto.
  2. mostra o menu de contexto.

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

Espero não esquecer de nada 🤔

Mais informações em Menus Documentation

MohammadL
fonte
Obrigado! Como passar dados para o menu de contexto? Por exemplo, id do item, texto do item e assim por diante.
CoolMind
4

Esta é uma maneira mais simples de fazer isso com Kotlin que funcionou para mim. O maior desafio é descobrir a posição do item que foi pressionado. Dentro de seu adaptador, você pode colocar este trecho de código e ele será capaz de capturar a posição do item para o qual o menu de contexto é mostrado; Isso é tudo.

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
fonte
2
Esta é a maneira mais natural e controlável de fazer isso
nima,
3

Combinei minha solução com a solução de @Hardik Shah:

Na atividade eu tenho:

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

No adaptador eu tenho:

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

No fragmento eu tenho:

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

Talvez chegue atrasado para a festa, mas tenho uma solução de trabalho . Eu fiz uma ideia para isso.

Adicionar Menu de Contexto ao 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
fonte
1

Olá pessoal saiu com uma alternativa que funciona para mim. Acabei de registrar meu itemView com registerContextMenu e o Construtor ViewHolder, também defini um onLongClikcListener para o mesmo View. Na implementação onLongClick (View v), eu simplesmente obtenho a posição clicada com getLayoutPosition () e salvo em uma variável de instância (criei uma classe para representar esses dados, assim como o ContextMenuInfo deve funcionar), mas o mais importante é fazer certifique-se de retornar falso neste método. Tudo o que você precisa fazer agora é onContextItemSelected (item MenuItem), ler os dados que você armazena em sua variável de instância e se forem válidos, você prossegue com suas ações. Aqui está um trecho.

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

Eu faço o ViewHolder implementar OnLongClickListener, mas você pode fazer da maneira que preferir.

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

Você também pode definir isso no adaptador ou em outra View que você tenha no ViewHolder (ou seja, uma TextView). O importante é a implementação 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);
}

A melhor parte é que você ainda pode processar o evento LongClick retornando true nos casos que desejar, e o conextMenu não aparecerá.

Este método funciona porque registerForContextView torna o View LongClickable e, quando é hora de processar o ContextMenu, o sistema chama performLongClick, que primeiro chama uma implementação onLongClick e, se retornar false, chama showContextMenu.

Raziel25
fonte
1

Eu uso essa solução há algum tempo e tem funcionado muito bem para mim.

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

Agora, em seu fragmento ou atividade, implemente os seguintes 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, registre-se no contextMenu na tela de reciclagem

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

Isso deve funcionar!

Rakesh Gopathi
fonte
1

Aqui está como você pode implementar o menu de contexto para RecyclerView e obter a posição do item, para o qual o item do menu de contexto foi selecionado:

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
fonte
1

Uma solução para quem deseja obter a identificação do item ao ligar ContextMenu.

Se você tiver um RecyclerViewcom itens como este (contendo clicáveis ImageView):

Lista

então você deve receber callbacks 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()
    }
}

Aviso!

Se você usar um ViewPagerbaseado em um fragmento (todas as páginas são listas semelhantes), você enfrentará um problema. Quando você substituir onContextItemSelectedpara entender qual item de menu foi selecionado, você obterá uma identificação de item de lista na primeira página! Para superar esse problema, consulte Fragmento errado em ViewPager recebe chamada onContextItemSelected .

CoolMind
fonte
0

Tenho lutado com isso porque o Android não lida bem com isso para mim no RecyclerView, que estava funcionando muito bem para o ListView.

A parte mais difícil é que a parte ContextMenuInfo está embutida dentro de uma View, que você não pode anexar facilmente a não ser sobrescrever a View.

Portanto, você precisará de um invólucro que o ajude a entregar as informações de posição para a Activity.

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

}

Então, em seu RecyclerAdapter, ao criar ViewHolders, você precisa definir o Wrapper como a visualização raiz e registrar contextMenu em cada visualização.

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

}

E, por último, em sua Atividade, você poderá fazer o que costuma fazer:

@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
fonte
0

O melhor era usar o menu de contexto com a visão recicladora se você criar uma visão recicladora customizada e substituir o getContextMenuInfo()método e retornar sua própria instância do objeto de informações do menu de contexto para que você possa buscar posições quando ele foi criado e quando o menu é clicado:

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

Dê uma olhada nesta essência que criei:

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

ReeSen
fonte
0

Expandindo um pouco algumas das respostas acima, se quiser evitar definir manualmente o menu em seu código no Adapter / ViewHolder, você pode usar um PopupMenu e aumentar as opções de menu a partir de um arquivo de recurso menu.xml padrão.

O exemplo abaixo mostra isso, incluindo a capacidade de passar um ouvinte que você pode implementar em seu Fragmento / Atividade para responder a cliques no menu de contexto.

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

Síntese completa aqui https://gist.github.com/brettwold/45039b7f02ce752ae0d32522a8e2ad9c

Brett Cherrington
fonte
0

Você pode passar OnCreateContextMenuListener para ViewHolder na ligação. Este ouvinte pode criar um menu personalizado para cada item de dados. Basta adicionar setOnCreateContextMenuListener em seu ViewHolder e chamá-lo durante a vinculação.

     public static class ItemViewHolder extends RecyclerView.ViewHolder
     {


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


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

}

No 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
fonte
0

No meu caso, tive que usar dados do meu fragmento no onContextItemSelected()método. A solução que acabei escolhendo foi passar uma instância do fragmento para meu adaptador e registrar o item de visualização no portador de visualização:

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

Em seguida, onCreateContextMenu()você pode salvar o índice em uma variável local:

selectedViewIndex = (int)v.getTag();

e recuperá-lo em onContextItemSelected()

Eric B.
fonte
0

A primeira vez que tive esse problema com adaptadores normais, acabei criando minha própria subclasse de Visualização personalizada e armazenando o material de que precisava. Eu realmente não gostei dessa solução e passei muito tempo olhando as grandes ideias que as pessoas propuseram e decidi que não gostava mais delas. Então eu meio que juntei tudo, balancei por um tempo e criei algo novo que eu gosto.

Começamos com algumas classes de utilitários. ContextMenuHandler é uma interface para qualquer objeto que manipulará o menu de contexto. Na prática, esta será uma subclasse ViewHolder, mas em teoria pode ser qualquer coisa

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

Em seguida, está uma Interface que deve ser implementada por qualquer View que será usada como filho imediato de um RecyclerView.

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

  public ContextMenuHandler getContextMenuHandler();
}

Em seguida, qualquer visualização que criará um menu de contexto como filho de um RecylcerView deve implementar ViewWIthContextMenu. No meu caso, eu só precisava de uma subclasse 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;
  }
}

E, finalmente, precisamos de uma classe Fragment aprimorada para interceptar as chamadas do menu de contexto e redirecioná-las para o manipulador apropriado.

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

Com tudo isso instalado, a implementação final é muito simples. O fragmento principal deve ser subclasse FragmentWithContextMenu. Ele configura a RecylerWindow principal normalmente e passa para a subclasse Adapter. A subclasse Adapter parece

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

É sobre isso. Tudo parece estar funcionando. Ele colocou todas as classes de utilitários em um pacote de menu de contexto separado para que eu pudesse dar nomes às classes que correspondiam às classes que existem como subclasses, mas achei que seria mais confuso.

kencorbin
fonte
-1

Ok, com base na resposta de @Flexo, irei colocar o mPosition em ordem ...

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

então em onContextItemSelected eu uso

item.getOrder() 

E tudo funciona bem, eu obtenho a posição do array facilmente

windX
fonte