web-dev-qa-db-fra.com

Focusable EditText dans ListView

J'ai passé environ 6 heures à ce sujet jusqu'à présent et je n'ai rencontré que des barrages routiers. Le principe général est qu'il y a une ligne dans un ListView (qu'elle soit générée par l'adaptateur ou ajoutée en tant qu'en-tête) contenant un widget EditText et un Button. . Tout ce que je veux, c’est pouvoir utiliser le jogball/les flèches pour naviguer dans le sélecteur jusqu’à des éléments individuels comme d'habitude, mais lorsque j'arrive à une rangée particulière - même si je dois identifier explicitement la rangée - elle a un focus enfant, je veux que cet enfant se concentre au lieu d'indiquer la position avec le sélecteur.

J'ai essayé beaucoup de possibilités et je n'ai pas eu de chance jusqu'à présent.

disposition:

<ListView
    Android:id="@Android:id/list" 
    Android:layout_height="fill_parent" 
    Android:layout_width="fill_parent"
    />

Vue d'en-tête:

EditText view = new EditText(this);
listView.addHeaderView(view, null, true);

En supposant que l'adaptateur contient d'autres éléments, utilisez les touches de direction pour déplacer la sélection vers le haut/le bas de la liste, comme prévu. mais lorsque vous accédez à la ligne d'en-tête, il est également affiché avec le sélecteur, et aucun moyen de se concentrer sur le EditText à l'aide du jogball. Remarque: taper sur EditTextvolonté le focalisera à ce moment-là, mais cela repose sur un écran tactile, ce qui ne devrait pas être une exigence.

ListView a apparemment deux modes à cet égard:
1. setItemsCanFocus(true): le sélecteur n'est jamais affiché, mais le EditText peut être activé lorsque les flèches sont utilisées. L'algorithme de recherche de focus est difficile à prédire et il n'y a pas de retour visuel (sur les lignes: avoir des enfants à focaliser ou non) sur l'élément sélectionné, ce qui peut donner à l'utilisateur une expérience inattendue.
2. setItemsCanFocus(false): le sélecteur est toujours tracé en mode non tactile, et EditText ne peut jamais obtenir la mise au point, même si vous appuyez dessus.

Pour aggraver les choses, appeler editTextView.requestFocus() renvoie true, mais ne donne en fait pas le focus EditText.

Ce que je prévois est essentiellement un hybride de 1 & 2, où plutôt que le paramètre de liste si tous les éléments sont activables ou non, je souhaite définir la focalisation pour un nique élément de la liste, de sorte que le sélecteur passe de manière transparente à la sélection de la ligne entière pour les éléments non focalisables et à la navigation dans l’arborescence de focus pour les éléments contenant des enfants activables.

N'importe quels preneurs?

120
Joe

Désolé, a répondu à ma propre question. Ce n'est peut-être pas la solution la plus correcte ni la plus élégante, mais cela fonctionne pour moi et donne une expérience utilisateur plutôt solide. J'ai examiné le code de ListView pour voir pourquoi les deux comportements sont si différents, et c'est ce que ListView.Java a découvert:

    public void setItemsCanFocus(boolean itemsCanFocus) {
        mItemsCanFocus = itemsCanFocus;
        if (!itemsCanFocus) {
            setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
        }
    }

Ainsi, lorsque vous appelez setItemsCanFocus(false), vous définissez également la focalisation descendante de sorte qu'aucun enfant ne puisse obtenir le focus. Ceci explique pourquoi je ne pouvais pas simplement basculer mItemsCanFocus dans OnItemSelectedListener de ListView - parce que ListView bloquait alors le focus sur tous les enfants.

Ce que j'ai maintenant:

<ListView
    Android:id="@Android:id/list" 
    Android:layout_height="match_parent" 
    Android:layout_width="match_parent"
    Android:descendantFocusability="beforeDescendants"
    />

J'utilise beforeDescendants parce que le sélecteur ne sera dessiné que lorsque le ListView lui-même (pas un enfant) a le focus, le comportement par défaut doit donc être que ListView prend le focus en premier et dessine des sélecteurs.

Puis, dans OnItemSelectedListener, puisque je sais quelle vue d’en-tête je veux remplacer le sélecteur (il faudrait plus de travail pour déterminer de manière dynamique si une position donnée contient une vue focalisable), je peux modifier la focalisation descendante et définir le focus sur EditText. Et lorsque je quitte cet en-tête, modifiez-le à nouveau.

public void onItemSelected(AdapterView<?> listView, View view, int position, long id)
{
    if (position == 1)
    {
        // listView.setItemsCanFocus(true);

        // Use afterDescendants, because I don't want the ListView to steal focus
        listView.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        myEditText.requestFocus();
    }
    else
    {
        if (!listView.isFocused())
        {
            // listView.setItemsCanFocus(false);

            // Use beforeDescendants so that the EditText doesn't re-take focus
            listView.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
            listView.requestFocus();
        }
    }
}

public void onNothingSelected(AdapterView<?> listView)
{
    // This happens when you start scrolling, so we need to prevent it from staying
    // in the afterDescendants mode if the EditText was focused 
    listView.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
}

Notez les appels setItemsCanFocus commentés. Avec ces appels, j'ai eu le comportement correct, mais setItemsCanFocus(false) a provoqué le passage du focus de EditText à un autre widget en dehors de ListView, puis de nouveau à ListView et a affiché le sélecteur sur le prochain élément sélectionné. la focalisation sautante était distrayante. La suppression de la modification ItemsCanFocus et le simple basculement de la focalisation descendante m'ont permis d'obtenir le comportement souhaité. Tous les éléments dessinent le sélecteur normalement, mais lorsque vous accédez à la ligne avec EditText, elle se concentre sur le champ de texte. Puis, en sortant de cet EditText, il a recommencé à dessiner le sélecteur.

101
Joe

Cela m'a aidé.
Dans votre manifeste:

<activity Android:name= ".yourActivity" Android:windowSoftInputMode="adjustPan"/>
97
logan

Ma tâche consistait à implémenter ListView qui se développe lorsque vous cliquez dessus. L'espace supplémentaire indique EditText où vous pouvez saisir du texte. L'application devrait être fonctionnelle sur la version 2.2+ (jusqu'à la version 4.2.2 au moment de la rédaction de cette lettre)

J'ai essayé de nombreuses solutions de ce post et d'autres que j'ai pu trouver; les ont testés sur des appareils 2.2 à 4.2.2. Aucune des solutions n'a été satisfaisante sur tous les appareils 2.2+, chaque solution présentant des problèmes différents.

Je voulais partager ma solution finale:

  1. définir listview sur Android:descendantFocusability="afterDescendants"
  2. mettre listview à setItemsCanFocus(true);
  3. définissez votre activité sur Android:windowSoftInputMode="adjustResize" De nombreuses personnes suggèrent que adjustPan mais adjustResize donne de bien meilleurs résultats, mais testez-le dans votre cas. Avec adjustPan, vous obtiendrez par exemple une liste des éléments cachés en bas. Les documents suggèrent que ("Ceci est généralement moins souhaitable que le redimensionnement"). Également sur la version 4.0.4, après que l'utilisateur commence à taper sur un clavier logiciel, l'écran fait défiler vers le haut.
  4. sur 4.2.2 avec adjustResize, le focus EditText pose quelques problèmes. La solution consiste à appliquer la solution rjrjr à partir de ce fil. Cela semble effrayant mais ce n'est pas le cas. Et il fonctionne. Juste l'essayer.

Supplémentaire 5. En raison de l'actualisation de l'adaptateur (en raison du redimensionnement de la vue) lorsque EditText se concentre sur les versions antérieures à HoneyComb, j'ai rencontré un problème avec Vues inversées: obtenir la vue pour l'élément ListView/ordre inverse sous 2.2; fonctionne sous 4.0.

Si vous réalisez des animations, vous voudrez peut-être modifier le comportement en adjustPan pour les versions antérieures à Honeycomb afin que le redimensionnement ne se déclenche pas et que l'adaptateur n'actualise pas les vues. Vous avez juste besoin d'ajouter quelque chose comme ça

if(Android.os.Build.VERSION.SDK_INT < Android.os.Build.VERSION_CODES.HONEYCOMB)
        getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);

Tout cela donne des ux acceptables sur les périphériques 2.2 - 4.2.2. J'espère que cela fera gagner du temps aux gens, car il m'a fallu au moins plusieurs heures pour arriver à cette conclusion.

17
AndroidGecko

Cela m'a sauvé la vie --->

  1. définir cette ligne

    ListView.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);

  2. Ensuite, dans votre manifeste dans le tag d’activité, tapez ceci ->

    <activity Android:windowSoftInputMode="adjustPan">

Votre intention habituelle

10
ravi ranjan

Nous essayons cela sur une courte liste qui ne fait aucune vue de recyclage. Jusqu'ici tout va bien.

XML:

<RitalinLayout
    Android:layout_width="match_parent"
    Android:layout_height="match_parent"
    >
  <ListView
      Android:id="@+id/cart_list"
      Android:layout_width="match_parent"
      Android:layout_height="match_parent"
      Android:scrollbarStyle="outsideOverlay"
      />
</RitalinLayout>

Java:

/**
 * It helps you keep focused.
 *
 * For use as a parent of {@link Android.widget.ListView}s that need to use EditText
 * children for inline editing.
 */
public class RitalinLayout extends FrameLayout {
  View sticky;

  public RitalinLayout(Context context, AttributeSet attrs) {
    super(context, attrs);

    ViewTreeObserver vto = getViewTreeObserver();

    vto.addOnGlobalFocusChangeListener(new ViewTreeObserver.OnGlobalFocusChangeListener() {
      @Override public void onGlobalFocusChanged(View oldFocus, View newFocus) {
        if (newFocus == null) return;

        View baby = getChildAt(0);

        if (newFocus != baby) {
          ViewParent parent = newFocus.getParent();
          while (parent != null && parent != parent.getParent()) {
            if (parent == baby) {
              sticky = newFocus;
              break;
            }
            parent = parent.getParent();
          }
        }
      }
    });

    vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
      @Override public void onGlobalLayout() {
        if (sticky != null) {
          sticky.requestFocus();
        }
      }
    });
  }
}
7
rjrjr

cet article correspondait exactement à mes mots-clés. J'ai un en-tête ListView avec une recherche EditText et un bouton de recherche.

Afin de donner le focus à EditText après avoir perdu le focus initial, le seul HACK que j'ai trouvé est:

    searchText.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View arg0) {
            // LOTS OF HACKS TO MAKE THIS WORK.. UFF...
            searchButton.requestFocusFromTouch();
            searchText.requestFocus();
        }
    });

Vous avez perdu beaucoup d'heures et ce n'est pas vraiment une solution. J'espère que ça aide quelqu'un de dur.

4
Rafael Sanches

Si la liste est dynamique et contient des widgets activables, l'option appropriée consiste à utiliser RecyclerView au lieu de ListView IMO.

Les solutions de contournement qui définissent adjustPan, FOCUS_AFTER_DESCENDANTS, ou souvenez-vous manuellement de la position focalisée, ne sont en fait que des solutions de contournement. Ils ont des cas de coin (problèmes de défilement + clavier logiciel, changement de position du curseur dans EditText). Ils ne changent pas le fait que ListView crée/détruit des vues en masse pendant notifyDataSetChanged.

Avec RecyclerView, vous informez des insertions, mises à jour et suppressions individuelles. La vue ciblée n'étant pas recréée, aucun problème de perte de focus des contrôles de formulaire. En prime, RecyclerView anime les insertions et suppressions d’éléments de la liste.

Voici un exemple de documentation officielle expliquant comment démarrer avec RecyclerView: Guide du développeur - Créer une liste avec RecyclerView

2
Pēteris Caune

quelques fois quand vous utilisez Android:windowSoftInputMode="stateAlwaysHidden"en activité manifeste ou xml, à ce moment-là, le focus clavier est perdu. Commencez donc par vérifier cette propriété dans votre fichier xml et votre manifeste. Si elle existe, supprimez-la simplement. Après avoir ajouté ces options au fichier manifeste dans l’activité parallèle Android:windowSoftInputMode="adjustPan"et ajouter cette propriété à listview en XML Android:descendantFocusability="beforeDescendants"

1
user4715375

Je viens de trouver une autre solution. Je crois que c'est plus un bidouillage qu'une solution, mais cela fonctionne sur Android 2.3.7 et Android 4.3 (j'ai même testé ce bon vieux D-pad )

commencez votre webview comme d'habitude et ajoutez ceci: (merci Michael Bierman)

listView.setItemsCanFocus(true);

Pendant l'appel getView:

editText.setOnFocusChangeListener(
    new OnFocusChangeListener(View view,boolean hasFocus){
        view.post(new Runnable() {
            @Override
            public void run() {
                view.requestFocus();
                view.requestFocusFromTouch();
            }
     });
0
alaeri

Juste essayer ceci

Android:windowSoftInputMode="adjustNothing"

dans le

activité

section de votre manifeste. Oui, cela ajuste les rien, ce qui signifie que editText restera à sa position d'origine lors de l'ouverture de l'IME. Mais c’est juste un petit inconvénient qui résout complètement le problème de la perte de concentration.

0
Tor_Gash

La partie la plus importante est de faire en sorte que focus fonctionne pour la cellule de liste. Ceci est essentiel pour la liste sur Google TV:

setItemsCanFocus la méthode de la vue liste fait l'affaire:

...
mPuzzleList = (ListView) mGameprogressView.findViewById(R.id.gameprogress_puzzlelist);
mPuzzleList.setItemsCanFocus(true);
mPuzzleList.setAdapter(new PuzzleListAdapter(ctx,PuzzleGenerator.getPuzzles(ctx, getResources(), version_lite)));
...

Ma liste de cellules xml commence comme suit:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
             Android:id="@+id/puzzleDetailFrame"
             Android:focusable="true"
             Android:nextFocusLeft="@+id/gameprogress_lessDetails"
             Android:nextFocusRight="@+id/gameprogress_reset"
...

nextFocusLeft/Right sont également importants pour la navigation par D-Pad.

Pour plus de détails, consultez les autres bonnes réponses.

0
Michael Biermann

Une autre solution simple consiste à définir votre onClickListener, dans la méthode getView (..), de votre ListAdapter.

public View getView(final int position, View convertView, ViewGroup parent){
    //initialise your view
    ...
    View row = context.getLayoutInflater().inflate(R.layout.list_item, null);
    ...

    //define your listener on inner items

    //define your global listener
    row.setOnClickListener(new OnClickListener(){
        public void onClick(View v) {
            doSomethingWithViewAndPosition(v,position);
        }
    });

    return row;

De cette façon, vos rangées sont cliquables et votre vue intérieure aussi :)

0
Weber Antoine