web-dev-qa-db-fra.com

Comment gérer onContextItemSelected dans une activité multi-fragments?

J'essaie actuellement d'adapter mon application pour utiliser les "Bibliothèques de compatibilité pour Android v4" pour fournir les avantages de l'utilisation de fragments même à Android 1.6 utilisateurs.

L'implémentation d'un menu contextuel semble délicate:

  • L'activité principale de l'application est l'extension de la classe FragmentActivity.
  • Les fragments sont tous basés sur une classe qui étend la classe Fragment.
  • La classe de fragment appelle registerForContextMenu () dans sa méthode onCreateView () et remplace les méthodes onCreateContextMenu () et onContextItemSelected ().

Pour onCreateContextMenu () cela fonctionne plutôt bien. Le menu contextuel est gonflé à partir d'un fichier de ressources et légèrement modifié en fonction de l'élément sélectionné (qui est basé sur une listView ... même si le fragment n'est pas un ListFragment).

Le problème se produit lorsqu'une entrée de menu contextuel est sélectionnée. onContextItemSelected () est appelé pour tous les fragments actuellement existants en commençant par le premier ajouté.

Dans mon cas, les fragments sont utilisés pour montrer le contenu d'une structure de dossiers. Lorsque le menu contextuel d'un fragment de sous-dossier est ouvert et qu'un élément de menu est sélectionné, onContextItemSelected () est d'abord appelé aux niveaux supérieurs (selon le nombre de fragments autorisés/visibles à ce moment).

À l'heure actuelle, j'utilise une solution de contournement par un champ au niveau de l'activité qui contient la balise du dernier fragment appelant son onCreateContextMenu (). De cette façon, je peux appeler "return super.onContextItemSelected (item)" au début de onContextItemSelected () lorsque la balise stockée n'est pas la même que getTag (). Mais cette approche me semble un peu sale.

Pourquoi onContextItemSelected () est appelé sur tous les fragments? et pas seulement celui qui appelait onCreateContextMenu ()?

Quelle est la façon la plus élégante de gérer cela?

67
Lars K.

J'ai trouvé une alternative. Cela ne change rien à mon problème ci-dessus, mais cela le rend inutile.

J'ai supprimé complètement le menu contextuel de mon application. Au lieu de cela, je capture le clic long sur un élément de la liste et modifie les boutons visibles de la barre d'action en ce moment. Du point de vue de l'utilisateur, c'est beaucoup plus comme une tablette comme un menu contextuel.

Dans les applications rétrocompatibles, la barre d'actions n'existe pas. J'ai donc décidé de créer ma propre (sorte de barre d'outils en haut) pour les appareils pré Honeycomb.

Si vous souhaitez rester avec le menu contextuel, je n'ai pas trouvé de meilleure solution que la solution de contournement que j'ai mentionnée ci-dessus.

2
Lars K.

Je posterai une réponse même si vous avez trouvé une solution de contournement car je viens de traiter un problème similaire. Lorsque vous gonflez le menu contextuel pour un fragment spécifique, affectez à chaque élément de menu un groupId unique au fragment. Ensuite, testez le groupId dans "onContextItemSelected". Par exemple:

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

De cette façon, tous vos fragments recevront toujours des appels à "onContextItemSelected", mais seul le bon répondra, évitant ainsi d'avoir à écrire du code au niveau de l'activité. Je suppose qu'une version modifiée de cette technique pourrait fonctionner même si vous n'utilisez pas 'menu.add (...)'

68
Rich Ehmer

Une autre solution:

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

Basé sur ce patch de Jake Wharton.

55
Sergey Glotov

J'ai aimé la solution simple de Sergei G (basée sur le correctif de Jake Wharton), mais inversée car il est plus facile d'ajouter à plusieurs fragments:

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

Après cela, le code est le même qu'avant.

8
azelez

J'ai trouvé une solution très simple. Comme onCreateContextMenu () est appelé à chaque fois que le ContextMenu est créé, j'ai défini une variable booléenne sur 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;
}

La seule autre chose que je dois faire est de demander cette variable 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);
    }
}

C'est ça.

4
Rawpal

Si vous utilisez des adaptateurs avec des vues de liste dans votre fragment, cela peut être utile.

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
user1103538

Dans mon premier fragment, j'ai défini tous mes identifiants de menu> 5000 donc, comme première ligne de code de onContextItemSelected du premier fragment que j'ai

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

et le deuxième fragment sera invoqué.

1
frusso

Changez juste

 @Override
    public boolean onContextItemSelected(MenuItem item) {
    return true;
 }

à

@Override
    public boolean onContextItemSelected(MenuItem item) {
    return super.onContextItemSelected(item); 
 }

et fonctionnera très bien !!!

0
Amit Hooda

À mon humble avis, nous pouvons simplement vérifier si la vue cible est l'enfant d'une liste de fragments. C'est très simple et ça marche bien pour moi. Je viens d'ajouter à tous mes fragments: if (getListView.getPositionForView(info.targetView) == -1) return false lors de la migration à partir d'une ancienne API

Ceci est un exemple d'un de mes fragments parents. Voici Scala, mais j'espère que vous avez une idée.

@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

P.S. Si vous tracez des appels de onContextItemSelected (...), vous pouvez notifier que super.onContextItemSelected(item) retourne toujours false. OnContextItemSelected valide invoqué APRÈS, pas DANS. Donc super.onContextItemSelected(item) est inutile et je l'ai remplacé par false.

0
Ezhik

Dans la méthode modifiée, retournez true; pour retourner super.onContextItemSelected (item); dans ma substitution onContextItemSelected () et tout a commencé à fonctionner.

0
Ali Imran

J'ai trouvé une solution plus simple que celle exposée:

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

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

    switch(item.getItemId()) {
        ...
    }
}
0
Federico Quevedo