Страницы

Поиск по вопросам

Показаны сообщения с ярлыком popupmenu. Показать все сообщения
Показаны сообщения с ярлыком popupmenu. Показать все сообщения

воскресенье, 22 декабря 2019 г.

Контекстное меню в Android

#java #android #listview #меню #popupmenu


Доброго всем времени суток!

У меня есть ListView, и я хочу к каждому его элементу прикрутить контекстное меню.
Часто видел в приложениях, что контекстное меню для элементов листвью вызывается при
клике на специальную кнопку с тремя квадратами. Не знаю, как называется эта кнопка,
поэтому даже не могу нормально погуглить. Выглядит эта кнопка так: 



Скажите, пожалуйста, что это за кнопка и как наилучшим образом приделать контекстное
меню к каждому элементу listview? Спасибо!
    


Ответы

Ответ 1



Кнопка называется "overflow". Скачать её можно в интернете, на habre был когда-то пост со ссылкой на пак с разноцветными иконками. Меню называется "PopUpMenu". Чтобы его вызвать нужно: 1) Добавить в лэйаут элемента списка саму иконку, найти её в адаптере и далее: @Override public View getView(final int position, View convertView, ViewGroup parent) { //popUp menu holderMain.settings.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { PopupMenu popup = new PopupMenu(context, v); popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { public boolean onMenuItemClick(MenuItem item) { switch (item.getItemId()) { case R.id.mark_as_read: //действие return true; case R.id.share_link: //действие return true; case R.id.show_comments: //действие return true; default: return false; } } }); MenuInflater inflater = popup.getMenuInflater(); inflater.inflate(R.menu.popup_menu, popup.getMenu()); popup.show(); } }); 2) Ну и ещё нужен popup_menu.xml в папке /res/menu

вторник, 10 декабря 2019 г.

PopupMenu выбрасывает WindowLeaked при повороте устройства

#java #android #popupmenu #recyclerview


Реализую overflow-кнопку для элемента списка, кнопка обрабатывается в холдере адаптера:

class CustomAdapter extends RecyclerView.Adapter  {

....

 public static class ItemHolder extends RecyclerView.ViewHolder implements View.OnClickListener{

        ImageButton mMenuItem;
        CardView mCard;

        public ItemHolder(View v) {
            super(v);

            mCard = (CardView) v.findViewById(R.id.card);
            mMenuItem = (ImageButton) v.findViewById(R.id.menu_button);

            mMenuItem.setOnClickListener(this);
            mCard.setOnClickListener(this);
        }

        @Override
        public void onClick(View v) {

            int position = getAdapterPosition();
            if (position != RecyclerView.NO_POSITION) {
                switch (v.getId()) {
                    case R.id.menu_button:
                        PopupMenu popupMenu = new PopupMenu(mMenuItem.getContext(),
mMenuItem);
                        popupMenu.inflate(R.menu.popup_menu);
                        popupMenu.show();
                        break;
                    case R.id.card:
                        //
                        break;
            }
       }
  }
 }


Если меню раскрыто, то при повороте устройства получаю исключение:


  E WindowManager: android.view.WindowLeaked: Activity com.example.android.test.MyActivity


Понятно, что менюшка теряет прошлую активити при пересоздании и, в общем то, это
не приводит к падению приложения и вообще никак не проявляется, если не смотреть logcat,
но хотелось бы решить проблему.
Собственно решение, как таковое, известно - закрывать PopupMenu в методах закрытия
активити, например onPause() - popupmenu.dissmis(). Проблема в том, что это меню создается
в адаптере и ему о жизненном цикле активити ничего не известно. Можно, конечно, уведомить
адаптер, что вызван метод onPause() и закрыть эту менюшку, но получается все довольно
коряво - нужно переносить инициализацию меню в сам адаптер и тп. нежелательные решения.

Посмотрел большое количество примеров реализации, в том числе эталонный iosched,
везде эта проблема просто игнорируется, даже гуглом.

Вопрос такой:  как решить и стоит ли вообще обращать внимание на эту проблему или
какие то альтернативные способы реализации меню в айтеме для RecyclerView.
    


Ответы

Ответ 1



Поскольку вложенный в адаптер класс ItemHolder статический, то для решения проблемы сделал следующее: Объявил PopupMenu статическим полем класса - теперь все экземпляры меню будут "связаны", как будто это один экземпляр: public static class ItemHolder extends RecyclerView.ViewHolder implements View.OnClickListener{ ImageButton mMenuItem; CardView mCard; public static PopupMenu popupMenu; ... @Override public void onClick(View v) { ... if (position != RecyclerView.NO_POSITION) { switch (v.getId()) { case R.id.menu_button: popupMenu = new PopupMenu(mMenuItem.getContext(), mMenuItem); ... } } } В onPause() активити, в которую выводится список, обращаюсь напрямую к этому статическому экземпляру через класс адаптера с предложением закрыться: @Override protected void onPause() { super.onPause(); PopupMenu popupMenu = CustomAdapter.ItemHolder.popupMenu; if (popupMenu != null) popupMenu.dismiss(); popupMenu = null; // защита от утечек памяти } При желании можно написать геттер, чтобы отдавал PopupMenu. В данном случае я не вижу в этом особой надобности. PS: По прежнему хотел бы услышать более оптимальное решение.

Ответ 2



Для решения проблемы нам надо: В нужный момент (при "смерти" активити) закрыть PopupMenu. А для этого надо получить ссылку на объект PopupMenu. А оно определено в RecyclerView.ViewHolder. Значит нам надо получить ссылку на RecyclerView.ViewHolder И в нём-то и будет искомое PopupMenu. Добавим поле в холдер для хранения меню, а также геттер: public static class ItemHolder extends RecyclerView.ViewHolder implements View.OnClickListener { ImageButton mMenuItem; CardView mCard; //сюда будем ссылку на меню сохранять PopupMenu popup; //так будем его из холдера получать public PopupMenu getPopup() { return popup; } public ItemHolder(View v) { super(v); mCard = (CardView) v.findViewById(R.id.card); mMenuItem = (ImageButton) v.findViewById(R.id.menu_button); //устанавливаем тэг одному из элементов разметки //чтобы потом вызовом View.getTag() получить ссылку на экземпляр холдера mMenuItem.setTag(this); mMenuItem.setOnClickListener(this); mCard.setOnClickListener(this); } @Override public void onClick(View v) { int position = getAdapterPosition(); if (position != RecyclerView.NO_POSITION) { switch (v.getId()) { case R.id.menu_button: //инициализируем поле класса popup = new PopupMenu(mMenuItem.getContext(), mMenuItem); popupMenu.inflate(R.menu.popup_menu); popupMenu.show(); break; case R.id.card: // break; } } } Все инструменты готовы, теперь можно в методах жизненного цикла Activity или Fragment, используя лишь ссылку на RecyclerView, находить PopupMenu в элементах списка и вызывать над ними PopupMenu.dismiss(), что устранит вываливание в логи сообщения об ошибке: //можно оптимизировать, но для простоты пробежимся по всем элементам for (int i = 0; i < recyclerView.getLayoutManager().getChildCount(); i++) { View recyclerViewElement = recyclerView.getLayoutManager().findViewByPosition(i); if (recyclerViewElement == null) { continue; } //находим в элементе View, хранящую ссылку на холдер View viewThatHoldsReferenceToHolder = recyclerViewElement.findViewById(R.id.menu_button); if (viewThatHoldsReferenceToHolder == null) { continue; } //получаем ссылку на холдер ItemHolder holder = ((ItemHolder) viewThatHoldsReferenceToHolder .getTag()); if (holder != null) { //Вот оно - наше меню! PopupMenu popup = holder.getPopup(); if (popup != null) { //Закрываем его и задачу на правку бага popup.dismiss(); Log.d(LOG, "Красота, а не ругань системы в логах! =)"); } } } В случае же вызывания PopupMenu из адаптера всё гораздо проще - получаем ссылку на меню из адаптера и... Всё, вопрос решён. Буду считать сей случай доводом в пользу писания всей логики обработки элемента списка в адаптере, а не в классе холдера.

вторник, 13 ноября 2018 г.

Контекстное меню в Android

Доброго всем времени суток!
У меня есть ListView, и я хочу к каждому его элементу прикрутить контекстное меню. Часто видел в приложениях, что контекстное меню для элементов листвью вызывается при клике на специальную кнопку с тремя квадратами. Не знаю, как называется эта кнопка, поэтому даже не могу нормально погуглить. Выглядит эта кнопка так:

Скажите, пожалуйста, что это за кнопка и как наилучшим образом приделать контекстное меню к каждому элементу listview? Спасибо!


Ответ

Кнопка называется "overflow". Скачать её можно в интернете, на habre был когда-то пост со ссылкой на пак с разноцветными иконками. Меню называется "PopUpMenu". Чтобы его вызвать нужно:
1) Добавить в лэйаут элемента списка саму иконку, найти её в адаптере и далее:
@Override public View getView(final int position, View convertView, ViewGroup parent) { //popUp menu holderMain.settings.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { PopupMenu popup = new PopupMenu(context, v); popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { public boolean onMenuItemClick(MenuItem item) { switch (item.getItemId()) { case R.id.mark_as_read: //действие return true; case R.id.share_link: //действие return true; case R.id.show_comments: //действие return true; default: return false; } } }); MenuInflater inflater = popup.getMenuInflater(); inflater.inflate(R.menu.popup_menu, popup.getMenu()); popup.show(); } });
2) Ну и ещё нужен popup_menu.xml в папке /res/menu

вторник, 16 октября 2018 г.

PopupMenu выбрасывает WindowLeaked при повороте устройства

Реализую overflow-кнопку для элемента списка, кнопка обрабатывается в холдере адаптера:
class CustomAdapter extends RecyclerView.Adapter {
....
public static class ItemHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
ImageButton mMenuItem; CardView mCard;
public ItemHolder(View v) { super(v);
mCard = (CardView) v.findViewById(R.id.card); mMenuItem = (ImageButton) v.findViewById(R.id.menu_button);
mMenuItem.setOnClickListener(this); mCard.setOnClickListener(this); }
@Override public void onClick(View v) {
int position = getAdapterPosition(); if (position != RecyclerView.NO_POSITION) { switch (v.getId()) { case R.id.menu_button: PopupMenu popupMenu = new PopupMenu(mMenuItem.getContext(), mMenuItem); popupMenu.inflate(R.menu.popup_menu); popupMenu.show(); break; case R.id.card: // break; } } } }
Если меню раскрыто, то при повороте устройства получаю исключение:
E WindowManager: android.view.WindowLeaked: Activity com.example.android.test.MyActivity
Понятно, что менюшка теряет прошлую активити при пересоздании и, в общем то, это не приводит к падению приложения и вообще никак не проявляется, если не смотреть logcat, но хотелось бы решить проблему. Собственно решение, как таковое, известно - закрывать PopupMenu в методах закрытия активити, например onPause() - popupmenu.dissmis(). Проблема в том, что это меню создается в адаптере и ему о жизненном цикле активити ничего не известно. Можно, конечно, уведомить адаптер, что вызван метод onPause() и закрыть эту менюшку, но получается все довольно коряво - нужно переносить инициализацию меню в сам адаптер и тп. нежелательные решения.
Посмотрел большое количество примеров реализации, в том числе эталонный iosched, везде эта проблема просто игнорируется, даже гуглом.
Вопрос такой: как решить и стоит ли вообще обращать внимание на эту проблему или какие то альтернативные способы реализации меню в айтеме для RecyclerView


Ответ

Поскольку вложенный в адаптер класс ItemHolder статический, то для решения проблемы сделал следующее:
Объявил PopupMenu статическим полем класса - теперь все экземпляры меню будут "связаны", как будто это один экземпляр:
public static class ItemHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
ImageButton mMenuItem; CardView mCard; public static PopupMenu popupMenu; ...
@Override public void onClick(View v) { ... if (position != RecyclerView.NO_POSITION) { switch (v.getId()) { case R.id.menu_button: popupMenu = new PopupMenu(mMenuItem.getContext(), mMenuItem); ... } } } В onPause() активити, в которую выводится список, обращаюсь напрямую к этому статическому экземпляру через класс адаптера с предложением закрыться:
@Override protected void onPause() { super.onPause();
PopupMenu popupMenu = CustomAdapter.ItemHolder.popupMenu; if (popupMenu != null) popupMenu.dismiss(); popupMenu = null; // защита от утечек памяти }
При желании можно написать геттер, чтобы отдавал PopupMenu. В данном случае я не вижу в этом особой надобности.
PS: По прежнему хотел бы услышать более оптимальное решение.