web-dev-qa-db-fra.com

Erreur "IllegalArgumentException: le paramètre doit être un descendant de cette vue": prévention

J'ai un ListView avec quelques composants focalisables à l'intérieur (principalement EditTexts). Oui, je sais que ce n'est pas exactement recommandé, mais en général, presque tout fonctionne correctement et l'accent est mis là où il faut aller (avec quelques modifications que j'ai dû coder). Quoi qu’il en soit, mon problème est qu’il existe une condition de concurrence étrange lorsque vous faites défiler la liste avec votre doigt, puis que vous utilisez brusquement la boule de commande lorsque le clavier IME est affiché . Quelque chose doit sortir des limites et être recyclé. A ce stade, la méthode offsetRectBetweenParentAndChild() doit entrer en action et lancer la IllegalArgumentException

Le problème est que cette exception est émise en dehors de tout bloc dans lequel je peux insérer un essai/attraper (autant que je sache). Il y a donc deux solutions valables à cette question, soit: 

  1. Quelqu'un sait pourquoi cette exception étant levée et comment l'empêcher de se produire
  2. Quelqu'un sait comment placer un bloc try/catch quelque part qui permettra au moins à mon application de survivre. Autant que je sache, le problème est celui de la focalisation, il ne devrait donc absolument pas anéantir mon application (ce qui est le cas). J'ai essayé de surcharger les méthodes de la variable ViewGroup mais ces deux méthodes offset* sont marquées comme finales.

Trace de la pile:

08-17 18:23:09.825: ERROR/AndroidRuntime(1608): FATAL EXCEPTION: main
08-17 18:23:09.825: ERROR/AndroidRuntime(1608): Java.lang.IllegalArgumentException: parameter must be a descendant of this view
08-17 18:23:09.825: ERROR/AndroidRuntime(1608):     at Android.view.ViewGroup.offsetRectBetweenParentAndChild(ViewGroup.Java:2633)
08-17 18:23:09.825: ERROR/AndroidRuntime(1608):     at Android.view.ViewGroup.offsetDescendantRectToMyCoords(ViewGroup.Java:2570)
08-17 18:23:09.825: ERROR/AndroidRuntime(1608):     at Android.view.ViewRoot.scrollToRectOrFocus(ViewRoot.Java:1624)
08-17 18:23:09.825: ERROR/AndroidRuntime(1608):     at Android.view.ViewRoot.draw(ViewRoot.Java:1357)
08-17 18:23:09.825: ERROR/AndroidRuntime(1608):     at Android.view.ViewRoot.performTraversals(ViewRoot.Java:1258)
08-17 18:23:09.825: ERROR/AndroidRuntime(1608):     at Android.view.ViewRoot.handleMessage(ViewRoot.Java:1859)
08-17 18:23:09.825: ERROR/AndroidRuntime(1608):     at Android.os.Handler.dispatchMessage(Handler.Java:99)
08-17 18:23:09.825: ERROR/AndroidRuntime(1608):     at Android.os.Looper.loop(Looper.Java:130)
08-17 18:23:09.825: ERROR/AndroidRuntime(1608):     at Android.app.ActivityThread.main(ActivityThread.Java:3683)
08-17 18:23:09.825: ERROR/AndroidRuntime(1608):     at Java.lang.reflect.Method.invokeNative(Native Method)
08-17 18:23:09.825: ERROR/AndroidRuntime(1608):     at Java.lang.reflect.Method.invoke(Method.Java:507)
08-17 18:23:09.825: ERROR/AndroidRuntime(1608):     at com.Android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.Java:839)
08-17 18:23:09.825: ERROR/AndroidRuntime(1608):     at com.Android.internal.os.ZygoteInit.main(ZygoteInit.Java:597)
08-17 18:23:09.825: ERROR/AndroidRuntime(1608):     at dalvik.system.NativeStart.main(Native Method)
48
dmon

Pour ce que ça vaut (ou quiconque trébuche), j'ai abandonné l'approche ListView pour cette activité. Outre les collisions aléatoires, il est presque impossible d'obtenir le comportement de focus correctement sans configurer le windowSoftInputMode="adjustPan", qui ouvre de nombreuses autres boîtes de vers. Au lieu de cela, je suis juste allé pour un "simple" ScrollView et cela a fonctionné très bien.

10
dmon

Je suis désolé de vous dire, j'ai trouvé que ma réponse précédente n'était pas le moyen le plus parfait pour résoudre ce problème.

Alors j'essaye ceci:
Ajouter un ScrollListener à votre activité, lorsque listView commence à défiler, efface le focus actuel.

protected class MyScrollListener implements OnScrollListener {

        @Override
        public void onScroll(AbsListView view, int firstVisibleItem,
                int visibleItemCount, int totalItemCount) {
            // do nothing 
        }

        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {
            if (SCROLL_STATE_TOUCH_SCROLL == scrollState) {
                View currentFocus = getCurrentFocus();
                if (currentFocus != null) {
                    currentFocus.clearFocus();
                }
            }
        }

    }
31
Bruce

Tandis que la réponse de Bruce résout le problème, il le fait de manière très brutale, ce qui nuit à l'UX, car cela effacera le focus de chaque vue une fois que nous aurons fait défiler.

Il traite du symptôme du problème mais ne résout pas la cause réelle.

comment reproduire le problème:

Votre EditText a le focus et le clavier est ouvert, vous faites ensuite défiler l'écran jusqu'à ce que le EditText soit hors de l'écran et il n'a pas été recyclé dans un nouveau EditText qui est maintenant affiché.

Comprenons d'abord pourquoi ce problème se produit}

ListView recycle ses vues et les utilise à nouveau, comme vous le savez tous, mais il n'est parfois pas nécessaire d'utiliser une vue qui est sortie de l'écran immédiatement afin de la conserver pour une utilisation ultérieure, et parce qu'elle n'a plus besoin d'être affichée. le détachera, entraînant la valeur null de view.mParent. Cependant, le clavier doit savoir comment passer l’entrée et le fait en choisissant la vue focalisée, ou EditText pour être précis.

Le problème est donc que nous avons un EditText qui a le focus, mais n'a soudainement pas de parent, nous obtenons donc un paramètre "doit être un descendant de cette vue".

En utilisant le listener de défilement, nous causons plus de problèmes.

La solution:

Nous devons écouter un événement qui nous dira quand une vue est partie du tas et n'est plus attachée. Heureusement, ListView expose cet événement.

listView.setRecyclerListener(new AbsListView.RecyclerListener() {
        @Override
        public void onMovedToScrapHeap(View view) {
            if ( view.hasFocus()){
                view.clearFocus(); //we can put it inside the second if as well, but it makes sense to do it to all scraped views
                //Optional: also hide keyboard in that case
                if ( view instanceof EditText) {
                    InputMethodManager imm = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
                    imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
                }
            }
        }
    });
23
ndori

essaye ça

 @Override
public View getView(int position, View convertView, ViewGroup parent) {
    //abandon current focus
    View currentFocus = ((Activity)mContext).getCurrentFocus();
    if (currentFocus != null) {
        currentFocus.clearFocus();
    }

    // other code
}

MODIFIER:

Voir aussi: Meilleure solution

23
Bruce

J'ai rencontré le même problème et découvert cette solution - dans OnGroupCollapseListener/OnGroupExpandListener et OnScrollListener pour ExpandableListView, j'efface le focus et masque le clavier forcé. N'oubliez pas également de définir manifest pour votre activité windowSoftInputMode="adjustPan":

    expListView.setOnGroupCollapseListener(new OnGroupCollapseListener() {

        @Override
        public void onGroupCollapse(int groupPosition) {
            InputMethodManager inputManager = (InputMethodManager) getApplicationContext().getSystemService(Context.INPUT_METHOD_SERVICE);
            if (getWindow().getCurrentFocus() != null) {
                inputManager.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
                getCurrentFocus().clearFocus();
            }
        }
    });

    expListView.setOnGroupExpandListener(new OnGroupExpandListener() {

        @Override
        public void onGroupExpand(int groupPosition) {
            InputMethodManager inputManager = (InputMethodManager) getApplicationContext().getSystemService(Context.INPUT_METHOD_SERVICE);
            if (getWindow().getCurrentFocus() != null) {
                inputManager.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
                getCurrentFocus().clearFocus();
            }
        }
    });

    expListView.setOnScrollListener(new OnScrollListener() {

        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {
            InputMethodManager inputManager = (InputMethodManager) getApplicationContext().getSystemService(Context.INPUT_METHOD_SERVICE);
            if (getCurrentFocus() != null) {
                inputManager.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
                getCurrentFocus().clearFocus();
            }
        }

        @Override
        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {}

    });

Je ne sais pas exactement si OnGroupExpandListener est nécessaire ou non, cela pourrait être inutile.

6
validcat

J'ai utilisé la réponse de Bruce avec un léger ajustement. 

J'avais besoin de adjustResize dans mon activité au lieu de adjustpan mais lorsque j'ai essayé, l'erreur s'est produite à nouveau.
J'ai remplacé ScrollView par <Android.support.v4.widget.NestedScrollView et tout fonctionne correctement maintenant. J'espère que cela aide quelqu'un!

5
Hadi

J'ai également rencontré ce problème et la solution de validcat a fonctionné pour moi, mais j'ai dû appeler getWindow().getCurrentFocus().clearFocus().

3
Bassel Shmali

J'ai rencontré le même problème lorsque j'utilisais EditText dans Recyclerview. Après beaucoup de difficultés et d'essais de différentes options, j'ai découvert qu'après la suppression de la ligne lorsque mon clavier est ouvert, ce problème se produit. Je l'ai résolu en forçant la fermeture de mon clavier et en changeant notifyItemRemoved(position) avec notifyDataSetChanged().

2
M.Waqas Pervez

Si aucune des solutions suggérées ici ne vous concerne ...

J'ai rencontré une erreur similaire et remarqué qu'elle avait été signalée par les appareils de mes utilisateurs (après un crash) sans aucune explication claire sur sa cause (comme dans le journal indiqué sur la question). Plus précisément, le problème ne concernait que Samsung. Galaxy (y compris S6) (mais pas sur les périphériques Nexus ou autres, c’est pourquoi mes tests n’avaient initialement pas révélé le problème) ... Donc, d’abord, il est utile de vérifier si le problème est spécifique à un périphérique.

Ce que j’ai découvert par la suite, c’est que lorsqu’on appuyait sur le bouton de retour alors qu’un clavier virtuel Samsung était affiché sur un champ de texte, l’application se bloquait, renvoyant cette erreur - mais pas toujours!

En effet, le champ de texte à l’origine du blocage s’affiche également dans une vue de défilement avec fillViewPort = "true" activé.

Ce que j’ai trouvé, c’est que le fait de supprimer l’option fillViewPort de la vue de défilement ne serait pas en conflit avec le clavier Samsung affiché/masqué . Je suppose que le problème est en partie dû au fait que les claviers Samsung sont des claviers virtuels différents des claviers Nexus standard, C’est pourquoi seul un sous-groupe de mes utilisateurs rencontrait le problème et il se bloquerait uniquement sur leurs appareils.

En règle générale, et si aucune des solutions suggérées ne vous concerne, je vérifierai si le problème est spécifique à un appareil, et tenterai aussi de simplifier la vue sur laquelle je travaille jusqu'à ce que je puisse trouver le "composant coupable" (composant J'ajouterais que cela n’a pas été signalé dans les journaux des collisions - je ne suis donc tombé que sur la vue spécifique à l’origine du problème par hasard!).

Désolé de ne pas pouvoir être plus spécifique, mais j'espère que cela vous donnera des pistes pour une enquête plus approfondie si quelqu'un rencontre un problème similaire mais inexpliqué.

1
Pelpotronic

Dans mon cas, il était lié à windowSoftInputMode="adjustPan", listView et editText sur l'élément list (vue d'en-tête).

Pour résoudre ce problème, j’appelle la méthode de masquage du clavier virtuel avant la fin de l’activité.

public void hideKeyboard(Activity activity) {
    InputMethodManager inputMethodManager = (InputMethodManager) activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
    View focusView = activity.getCurrentFocus();
    if (focusView != null) {
        inputMethodManager.hideSoftInputFromWindow(focusView.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
    }
}
1
fabiozo

En cas d'affichage en liste extensible, si vos éléments enfants ont du texte modifié, vous devez modifier la mise au point avant les descendants pour l'affichage en liste extensible

expandableListView.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
1
Fenil

Ma réponse est liée à la plupart des réponses ici, mais je voulais simplement ajouter que dans mon cas, cet incident est dû à la suppression d'une ligne avec un texte de modification qui avait actuellement le focus.

Donc, tout ce que j'ai fait est de remplacer la méthode de suppression de l'adaptateur et de demander si la ligne supprimée contient l'édition en cours du focus et, le cas échéant, efface le focus.

Cela l'a résolu pour moi.

0
Ido Sofi

J'ai la solution la plus simple mais pas bonne. Il suffit d'étendre la méthode NestedScrollView et de remplacer la méthode onSizeChanged, d'ajouter un bloc try catch.

public class FixFocusErrorNestedScrollView extends NestedScrollView {
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    try {
        super.onSizeChanged(w, h, oldw, oldh);
    } catch (Exception e) {
        e.printStackTrace();
    }
}}

Dans mon cas, j'ai la vue de la couche de remorquage, la couche supérieure est listView, la dernière est NestedScrollView. L'erreur est survenue lorsque je change de couche. La mise au point se fera par élément ListeView (bouton).

Donc, je ne peux pas faire perdre le focus au bouton. La meilleure solution consiste alors à étendre NestedScrollView.

0
Snow Albert

Basé sur @Bruce answer, peut résoudre une erreur avec recyclerview comme ceci:

@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        View currentFocus = ((Activity)context).getCurrentFocus();
        if (currentFocus != null) {
            currentFocus.clearFocus();
        }
}
0
SANAT

J'utilise RecyclerView et aucune des solutions présentées n'a fonctionné ... J'ai eu l'erreur de supprimer des éléments.

Ce qui a bien fonctionné a été de surcharger le paramètre "onItemDismiss (int position)" de l'adaptateur, de sorte qu'il effectue d'abord un "notifyDataSetChanged ()" avant de supprimer l'élément, puis "notifyItemRemoved (position)" après le supprimer. Comme ça:

// Adapter code
@Override
public void onItemDismiss(int position) {
    if (position >= 0 && getTheList() != null && getTheList().size() > position) {
        notifyDataSetChanged();  // <--- this fixed it.
        getTheList().remove(position);
        scrollToPosition(position);
        notifyItemRemoved(position);
    }
}

Faites également un remplacement de 'removeAt (int position)' dans TabFragment pour appeler le nouveau code de nettoyage, comme ceci:

// TabFragment code
@Override
public void removeAt(int position) {
    mAdapter.onItemDismiss(position);
    mAdapter.notifyItemRemoved(position); // <--- I put an extra notify here too
}
0
Lee Hounshell