Как обрабатывать onContextItemSelected в многофрагментной деятельности? - PullRequest
67 голосов
/ 14 марта 2011

В настоящее время я пытаюсь адаптировать свое приложение для использования «Библиотеки совместимости для Android v4», чтобы обеспечить преимущества использования фрагментов даже для пользователей Android 1.6.

Реализация контекстного меню кажется сложной:

  • Основная деятельность приложения расширяет FragmentActivity класс.
  • Все фрагменты основаны на одном класс, расширяющий класс Fragment.
  • Фрагмент класса вызывает registerForContextMenu () в своем методе onCreateView () и переопределяет методы onCreateContextMenu () и onContextItemSelected () .

Для onCreateContextMenu () это работает довольно хорошо. Контекстное меню надувается из файла ресурсов и немного изменяется в зависимости от выбранного элемента (который основан на listView ..., даже если фрагмент не является ListFragment).

Проблема возникает при выборе пункта контекстного меню. onContextItemSelected () вызывается для всех существующих на данный момент фрагментов, начиная с первого добавленного.

В моем случае фрагменты используются для отображения содержимого структуры папок. Когда контекстное меню фрагмента подпапки открывается и выбирается элемент меню, onContextItemSelected () сначала вызывается на верхних уровнях (в зависимости от того, сколько фрагментов разрешено / видимо в данный момент).

Прямо сейчас я использую обходной путь для поля на уровне активности, которое содержит тег последнего фрагмента, вызывающего его onCreateContextMenu () . Таким образом, я могу вызвать «return super.onContextItemSelected (item)» в начале onContextItemSelected () , когда сохраненный тег не совпадает с getTag (). Но такой подход мне кажется немного грязным.

Почему onContextItemSelected () вызывается для всех фрагментов? а не только тот, который вызывал onCreateContextMenu () ?

Какой самый элегантный способ справиться с этим?

Ответы [ 11 ]

68 голосов
/ 19 ноября 2011

Я опубликую ответ, даже если вы нашли обходной путь, потому что я только что столкнулся с подобной проблемой.Когда вы раздуваете контекстное меню для определенного фрагмента, присвойте каждому пункту меню идентификатор группы, уникальный для фрагмента.Затем проверьте идентификатор группы в «onContextItemSelected».Например:

public void onCreateContextMenu(ContextMenu menu, View v,ContextMenuInfo menuInfo) {
    menu.add(UNIQUE_FRAGMENT_GROUP_ID, MENU_OPTION_1, 0, R.string.src1);
    menu.add(UNIQUE_FRAGMENT_GROUP_ID, MENU_OPTION_2, 0, R.string.src2);
}
public boolean onContextItemSelected(MenuItem item) {
    //only this fragment's context menus have group ID of -1
    if (item.getGroupId() == UNIQUE_FRAGMENT_GROUP_ID) {
        switch(item.getItemId()) {
        case MENU_OPTION_1: doSomething(); break;
        case MENU_OPTION_2: doSomethingElse(); break;
    }
}

Таким образом, все ваши фрагменты будут по-прежнему получать вызовы onContextItemSelected, но ответит только правильный, таким образом избегая необходимости писать код уровня активности.Я предполагаю, что модифицированная версия этого метода может работать, даже если вы не используете 'menu.add (...)'

55 голосов
/ 12 апреля 2012

Еще одно решение:

@Override
public boolean onContextItemSelected(MenuItem item) {
    if (getUserVisibleHint()) {
        // context menu logic
        return true;
    }
    return false;
}

На основе этого патча от Джейка Уортона.

8 голосов
/ 29 мая 2013

Мне понравилось простое решение от Сергея Г (основанное на исправлении Джейка Уортона), но перевернутое, потому что его легче добавить к нескольким фрагментам:

public boolean onContextItemSelected(android.view.MenuItem item) 
{  
    if( getUserVisibleHint() == false ) 
    {
        return false;
    }

    // The rest of your onConextItemSelect code
    AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
 }

После этого код такой же, как и раньше.

4 голосов
/ 28 декабря 2011

Я нашел очень простое решение. Поскольку onCreateContextMenu () вызывается каждый раз при создании ContextMenu, я устанавливаю логическую переменную в значение true.

public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
    super.onCreateContextMenu(menu, v, menuInfo);
    MenuInflater inflater = getActivity().getMenuInflater();
    inflater.inflate(R.menu.film_menu, menu);
    bMenu=true;
}

Единственное, что мне нужно сделать, это запросить эту переменную OnContextItemSelected ()

public boolean onContextItemSelected(MenuItem item) {
    if (bMenu) {
        bMenu=false;
        if (item.getItemId() == R.id.filmProperties) {
            ///Your code
            return true;
        } else {
            return super.onContextItemSelected(item);
        }
    } else {
        return super.onContextItemSelected(item);
    }
}

Вот и все.

2 голосов
/ 30 мая 2011

Я нашел альтернативу. Это ничего не меняет в моей проблеме выше, но делает это бессмысленным.

Я полностью удалил контекстное меню из своего приложения. Вместо этого я фиксирую лонгклик на элементе списка и меняю видимые кнопки на панели действий в данный момент. С точки зрения пользователя, это гораздо больше похоже на планшет как контекстное меню.

В обратно совместимых приложениях панель действий не существует. Поэтому я решил создать свой собственный (своего рода панель инструментов сверху) для устройств до Honeycomb.

Если вы хотите остаться с контекстным меню, я не нашел лучшего решения в качестве обходного пути, о котором я упоминал выше.

1 голос
/ 22 июля 2014

Если вы используете адаптеры со списками в вашем фрагменте, это может помочь.

public boolean onContextItemSelected(final MenuItem item) {
    final AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();

    //Check if the context menu call came from the list in this fragment (needed for support for multiple fragments in one screen)
    if (info.targetView.getParent() != getView().findViewById(android.R.id.list))
        return super.onContextItemSelected(item);

    //Handle context menu item call
    switch (item.getItemId()) {
        ...
    }
}
1 голос
/ 12 ноября 2011

В моем первом фрагменте я установил все свои идентификаторы меню> 5000, поэтому в качестве первой строки кода onContextItemSelected первого фрагмента у меня есть

if (item.getItemId() < 5000) return false;

и будет задействован второй фрагмент.

0 голосов
/ 30 мая 2015

В методе изменено возвращение true; вернуть super.onContextItemSelected (item); в моем onContextItemSelected () переопределить, и все начало работать.

0 голосов
/ 29 мая 2015

Я нашел более простое решение, чем разоблаченный:

public boolean onContextItemSelected(MenuItem item) {
    ListView yourList = (ListView) (ListView) getView().findViewById(R.id.yourList);

    if (!yourList.hasFocus())
        return false;

    switch(item.getItemId()) {
        ...
    }
}
0 голосов
/ 01 августа 2012

ИМХО, мы можем просто проверить, является ли целевое представление дочерним по отношению к списку фрагментов.Это очень просто и хорошо работает для меня.Я просто добавил ко всем своим фрагментам: if (getListView.getPositionForView(info.targetView) == -1) return false при переходе со старого API

Это пример одного из моих родительских фрагментов.Это Scala, но я надеюсь, что у вас есть идея.

@Loggable
override def onContextItemSelected(menuItem: MenuItem): Boolean = {
  for {
    filterBlock <- TabContent.filterBlock
    optionBlock <- TabContent.optionBlock
    environmentBlock <- TabContent.environmentBlock
    componentBlock <- TabContent.componentBlock
  } yield menuItem.getMenuInfo match {
  case info: AdapterContextMenuInfo =>
    if (getListView.getPositionForView(info.targetView) == -1)
      return false
    TabContent.adapter.getItem(info.position) match {
      case item: FilterBlock.Item =>
        filterBlock.onContextItemSelected(menuItem, item)
      case item: OptionBlock.Item =>
        optionBlock.onContextItemSelected(menuItem, item)
      case item: EnvironmentBlock.Item =>
        environmentBlock.onContextItemSelected(menuItem, item)
      case item: ComponentBlock.Item =>
        componentBlock.onContextItemSelected(menuItem, item)
      case item =>
        log.debug("skip unknown context menu item " + info.targetView)
        false
    }
  case info =>
    log.fatal("unsupported menu info " + info)
    false
  }
} getOrElse false

PS Если вы отслеживаете вызовы onContextItemSelected (...), вы можете уведомить, что super.onContextItemSelected(item) возвращает всегда false.Допустимый onContextItemSelected вызван ПОСЛЕ, а НЕ В ТЕЧЕНИЕ .Так что super.onContextItemSelected(item) бесполезен, и я заменил его на false.

...