EditText, mise au point claire sur le toucher à l'extérieur

100

Ma mise en page contient ListView, SurfaceViewet EditText. Lorsque je clique sur EditText, il reçoit le focus et le clavier à l'écran apparaît. Lorsque je clique quelque part en dehors de EditText, il a toujours le focus (il ne devrait pas). Je suppose que je pourrais mettre en place OnTouchListenerles autres vues de la mise en page et effacer manuellement le EditTextfocus du. Mais semble trop hackish ...

J'ai également la même situation dans l 'autre vue de mise en page - liste avec différents types d' éléments, dont certains ont à l EditText'intérieur. Ils agissent comme je l'ai écrit ci-dessus.

La tâche est de faire EditTextperdre le focus lorsque l'utilisateur touche quelque chose en dehors.

J'ai vu des questions similaires ici, mais je n'ai trouvé aucune solution ...

note173
la source

Réponses:

66

J'ai essayé toutes ces solutions. edc598 était le plus proche du fonctionnement, mais les événements tactiles ne se sont pas déclenchés sur les autres Viewcontenus dans la mise en page. Au cas où quelqu'un aurait besoin de ce comportement, voici ce que j'ai fini par faire:

J'ai créé un (invisible) FrameLayoutappelé touchInterceptor en dernier Viewdans la mise en page afin qu'il recouvre tout ( modifier: vous devez également utiliser a RelativeLayoutcomme mise en page parent et donner les attributs touchInterceptor fill_parent ). Ensuite, je l'ai utilisé pour intercepter les touches et déterminer si le toucher était au-dessus EditTextou non:

FrameLayout touchInterceptor = (FrameLayout)findViewById(R.id.touchInterceptor);
touchInterceptor.setOnTouchListener(new OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            if (mEditText.isFocused()) {
                Rect outRect = new Rect();
                mEditText.getGlobalVisibleRect(outRect);
                if (!outRect.contains((int)event.getRawX(), (int)event.getRawY())) {
                    mEditText.clearFocus();
                    InputMethodManager imm = (InputMethodManager) v.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 
                    imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
                }
            }
        }
        return false;
    }
});

Renvoyez false pour laisser tomber la manipulation tactile.

C'est hacky, mais c'est la seule chose qui a fonctionné pour moi.

Ken
la source
20
Vous pouvez faire la même chose sans ajouter une mise en page supplémentaire en remplaçant dispatchTouchEvent (MotionEvent ev) dans votre activité.
pcans
merci pour l'indice clearFocus (), cela a également fonctionné pour moi sur SearchView
Jimmy Ilenloa
1
Je voudrais faire allusion à la réponse de @ zMan ci-dessous. Il est basé sur cela, mais n'a pas besoin d'une vue stackoverflow.com/a/28939113/969016
Boy
De plus, si quelqu'un rencontre une situation dans laquelle le clavier ne se cache pas mais hideSoftInputFromWindow()
efface la
231

Sur la base de la réponse de Ken, voici la solution de copier-coller la plus modulaire.

Aucun XML nécessaire.

Mettez-le dans votre activité et il s'appliquera à tous les EditTexts, y compris ceux contenus dans les fragments de cette activité.

@Override
public boolean dispatchTouchEvent(MotionEvent event) {
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
        View v = getCurrentFocus();
        if ( v instanceof EditText) {
            Rect outRect = new Rect();
            v.getGlobalVisibleRect(outRect);
            if (!outRect.contains((int)event.getRawX(), (int)event.getRawY())) {
                v.clearFocus();
                InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
                imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
            }
        }
    }
    return super.dispatchTouchEvent( event );
}
zMan
la source
12
Ça a l'air bien. J'ai toujours un problème, tout mon texte d'édition est dans une vue de défilement et le texte d'édition supérieur a toujours le curseur visible. Même lorsque je clique à l'extérieur, le focus est perdu et le clavier est parti, mais le curseur est toujours visible dans le texte d'édition supérieur.
Vaibhav Gupta
1
Meilleure solution que j'ai rencontrée !! Auparavant, j'utilisais @Override public boolean onTouch (View v, MotionEvent event) {if (v.getId ()! = Search_box.getId ()) {if (search_box.hasFocus ()) {InputMethodManager imm = (InputMethodManager) getSystemService ( Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow (search_box.getWindowToken (), 0); search_box.clearFocus (); } return false; } return false; }
Pradeep Kumar Kushwaha
1
Solution One Shot :)
karthik kolanji
13
La meilleure solution que je n'ai jamais vue auparavant! Je vais enregistrer ce code dans mon essence pour le transmettre à mon fils et petit-fils.
Fan Applelin
2
Si l'utilisateur clique sur un autre EditText, le clavier se ferme et se rouvre immédiatement. Pour résoudre cela , je changé MotionEvent.ACTION_DOWNà MotionEvent.ACTION_UPvotre code. Excellente réponse d'ailleurs!
Thanasis1101 le
35

Pour la vue parente de EditText, laissez les 3 attributs suivants être " vrais ":
cliquable , focusable , focusableInTouchMode .

Si une vue souhaite recevoir le focus, elle doit remplir ces 3 conditions.

Voir android.view :

public boolean onTouchEvent(MotionEvent event) {
    ...
    if (((viewFlags & CLICKABLE) == CLICKABLE || 
        (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
        ...
        if (isFocusable() && isFocusableInTouchMode()
            && !isFocused()) {
                focusTaken = requestFocus();
        }
        ...
    }
    ...
}

J'espère que ça aide.

suber
la source
2
Pour effacer le focus, l'autre vue focalisable doit être le parent de la vue focalisée. Cela ne fonctionnera pas si la vue sur laquelle se concentrer est sur une autre hiérarchie.
AxeEffect
Techniquement, votre déclaration est incorrecte. Le point de vue parent doit être (clickable) *OR* (focusable *AND* focusableInTouchMode);)
Martin Marconcini
25

Mettez simplement ces propriétés dans le premier parent le plus élevé.

android:focusableInTouchMode="true"
android:clickable="true"
android:focusable="true" 
Chandan
la source
Merci mec. Je luttais contre ce problème depuis deux heures. L'ajout de ces lignes dans le parent supérieur a fonctionné.
Däñish Shärmà
FYI: Bien que cela fonctionne pour la plupart des cas, cela ne fonctionne pas pour la mise en page de certains éléments internes.
SV Madhava Reddy
17

La réponse de Ken fonctionne, mais elle est piratée. Comme pcans fait allusion dans le commentaire de la réponse, la même chose pourrait être faite avec dispatchTouchEvent. Cette solution est plus propre car elle évite d'avoir à pirater le XML avec un FrameLayout transparent et factice. Voici à quoi cela ressemble:

@Override
public boolean dispatchTouchEvent(MotionEvent event) {
    EditText mEditText = findViewById(R.id.mEditText);
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
        View v = getCurrentFocus();
        if (mEditText.isFocused()) {
            Rect outRect = new Rect();
            mEditText.getGlobalVisibleRect(outRect);
            if (!outRect.contains((int)event.getRawX(), (int)event.getRawY())) {
                mEditText.clearFocus();
                //
                // Hide keyboard
                //
                InputMethodManager imm = (InputMethodManager) v.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 
                imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
            }
        }
    }
    return super.dispatchTouchEvent(event);
}
Mike Ortiz
la source
7

Pour moi, ci-dessous, les choses ont fonctionné -

1. Ajout android:clickable="true"et android:focusableInTouchMode="true"au parentLayoutof EditTextieandroid.support.design.widget.TextInputLayout

<android.support.design.widget.TextInputLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:clickable="true"
    android:focusableInTouchMode="true">
<EditText
    android:id="@+id/employeeID"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:ems="10"
    android:inputType="number"
    android:hint="Employee ID"
    tools:layout_editor_absoluteX="-62dp"
    tools:layout_editor_absoluteY="16dp"
    android:layout_marginTop="42dp"
    android:layout_alignParentTop="true"
    android:layout_alignParentRight="true"
    android:layout_alignParentEnd="true"
    android:layout_marginRight="36dp"
    android:layout_marginEnd="36dp" />
    </android.support.design.widget.TextInputLayout>

2. Remplacement dispatchTouchEventdans la classe d'activité et hideKeyboard()fonction d' insertion

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            View view = getCurrentFocus();
            if (view != null && view instanceof EditText) {
                Rect r = new Rect();
                view.getGlobalVisibleRect(r);
                int rawX = (int)ev.getRawX();
                int rawY = (int)ev.getRawY();
                if (!r.contains(rawX, rawY)) {
                    view.clearFocus();
                }
            }
        }
        return super.dispatchTouchEvent(ev);
    }

    public void hideKeyboard(View view) {
        InputMethodManager inputMethodManager =(InputMethodManager)getSystemService(Activity.INPUT_METHOD_SERVICE);
        inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
    }

3. Ajout setOnFocusChangeListenerpour EditText

EmployeeId.setOnFocusChangeListener(new View.OnFocusChangeListener() {
            @Override
            public void onFocusChange(View v, boolean hasFocus) {
                if (!hasFocus) {
                    hideKeyboard(v);
                }
            }
        });
Adi
la source
5

Vous avez probablement déjà trouvé la réponse à ce problème, mais j'ai cherché comment le résoudre et je ne peux toujours pas vraiment trouver exactement ce que je cherchais, alors j'ai pensé que je le publierais ici.

Ce que j'ai fait était ce qui suit (c'est très général, le but est de vous donner une idée de la façon de procéder, copier et coller tout le code ne fonctionnera pas O: D):

Tout d'abord, le EditText et toutes les autres vues que vous souhaitez dans votre programme sont enveloppés par une seule vue. Dans mon cas, j'ai utilisé un LinearLayout pour tout envelopper.

<LinearLayout 
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/mainLinearLayout">
 <EditText
  android:id="@+id/editText"/>
 <ImageView
  android:id="@+id/imageView"/>
 <TextView
  android:id="@+id/textView"/>
 </LinearLayout>

Ensuite, dans votre code, vous devez définir un écouteur tactile sur votre LinearLayout principal.

final EditText searchEditText = (EditText) findViewById(R.id.editText);
mainLinearLayout.setOnTouchListener(new View.OnTouchListener() {

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            // TODO Auto-generated method stub
            if(searchEditText.isFocused()){
                if(event.getY() >= 72){
                    //Will only enter this if the EditText already has focus
                    //And if a touch event happens outside of the EditText
                    //Which in my case is at the top of my layout
                    //and 72 pixels long
                    searchEditText.clearFocus();
                    InputMethodManager imm = (InputMethodManager) v.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
                    imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
                }
            }
            Toast.makeText(getBaseContext(), "Clicked", Toast.LENGTH_SHORT).show();
            return false;
        }
    });

J'espère que cela aidera certaines personnes. Ou du moins les aide à commencer à résoudre leur problème.

edc598
la source
Oui, j'en suis venu à une solution similaire. Ensuite, j'ai porté un sous-ensemble du système de vue Android en C ++ sur opengl et y ai construit cette fonctionnalité)
note173
C'est vraiment un très bon exemple. Je cherchais un code comme celui-ci mais tout ce que je pouvais trouver était des solutions complexes. J'ai utilisé à la fois la coordonnée XY pour être plus précis car j'avais également peu d'EditText multiligne. Merci!
Sumitk
3

Définissez simplement deux propriétés du parent de celui-ci EditTextcomme suit:

android:clickable="true"
android:focusableInTouchMode="true"

Ainsi, lorsque l'utilisateur touchera en dehors de la EditTextzone, le focus sera supprimé car le focus sera transféré vers la vue parent.

Dhruvam Gupta
la source
2

J'ai un ListViewcomposé de EditTextpoints de vue. Le scénario dit qu'après avoir édité du texte dans une ou plusieurs lignes, nous devrions cliquer sur un bouton appelé «terminer». J'ai utilisé onFocusChangedla EditTextvue à l'intérieur de listViewmais après avoir cliqué sur Terminer, les données ne sont pas enregistrées. Le problème a été résolu en ajoutant

listView.clearFocus();

à l'intérieur du onClickListenerbouton "Terminer" et les données ont été enregistrées avec succès.

Muhannad A. Alhariri
la source
Merci. Vous avez économisé mon temps, j'avais ce scénario dans recyclerview et perdais la dernière valeur editText après avoir cliqué sur le bouton, parce que le focus du dernier editText n'a pas changé et ainsi de suite Je voudrais trouver une solution qui ferait que le dernier editText perdrait le focus en dehors, le même clic sur le bouton ... et trouverait votre solution. Cela a très bien fonctionné!
Reyhane Farshbaf
2

Je pense vraiment que c'est une manière plus robuste d'utiliser getLocationOnScreenque getGlobalVisibleRect. Parce que je rencontre un problème. Il y a un Listview qui contient quelques Edittext et et défini ajustpandans l'activité. Je trouve getGlobalVisibleRectreturn une valeur qui ressemble à y compris le scrollY, mais le event.getRawY est toujours à l'écran. Le code ci-dessous fonctionne bien.

public boolean dispatchTouchEvent(MotionEvent event) {
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
        View v = getCurrentFocus();
        if ( v instanceof EditText) {
            if (!isPointInsideView(event.getRawX(), event.getRawY(), v)) {
                Log.i(TAG, "!isPointInsideView");

                Log.i(TAG, "dispatchTouchEvent clearFocus");
                v.clearFocus();
                InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
                imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
            }
        }
    }
    return super.dispatchTouchEvent( event );
}

/**
 * Determines if given points are inside view
 * @param x - x coordinate of point
 * @param y - y coordinate of point
 * @param view - view object to compare
 * @return true if the points are within view bounds, false otherwise
 */
private boolean isPointInsideView(float x, float y, View view) {
    int location[] = new int[2];
    view.getLocationOnScreen(location);
    int viewX = location[0];
    int viewY = location[1];

    Log.i(TAG, "location x: " + location[0] + ", y: " + location[1]);

    Log.i(TAG, "location xWidth: " + (viewX + view.getWidth()) + ", yHeight: " + (viewY + view.getHeight()));

    // point is inside view bounds
    return ((x > viewX && x < (viewX + view.getWidth())) &&
            (y > viewY && y < (viewY + view.getHeight())));
}
Victor Choy
la source
Merci beaucoup! Cela a fonctionné pour tous les scénarios de mise en page comme RecyclerView, etc. etc. J'ai essayé une autre solution mais celle-ci convient à tous! :) Bon travail!
Woppi
1

Pour perdre le focus lorsque vous touchez une autre vue, les deux vues doivent être définies comme view.focusableInTouchMode (true).

Mais il semble que l'utilisation de la mise au point en mode tactile ne soit pas recommandée. Veuillez jeter un œil ici: http://android-developers.blogspot.com/2008/12/touch-mode.html

forumercio
la source
Oui, j'ai lu ceci, mais ce n'est pas exactement ce que je veux. Je n'ai pas besoin de l'autre vue pour me concentrer. Bien que je ne vois pas d'autre moyen pour le moment ... Peut-être, d'une manière ou d'une autre, faire en sorte que la vue racine capture le focus lorsque l'utilisateur clique sur un enfant non focalisable?
note173 du
Tant que je sais, le focus ne peut pas être géré par une classe, il est géré par Android ... pas d'idées :(
forumercio
0

Le meilleur moyen est d'utiliser la méthode par défaut clearFocus()

Vous savez résoudre les codes à onTouchListenerdroite?

Appelez EditText.clearFocus(). Il mettra clairement l'accent last EditText.

Tour Huy
la source
0

Comme @pcans l'a suggéré, vous pouvez effectuer cette substitution dispatchTouchEvent(MotionEvent event)dans votre activité.

Ici, nous obtenons les coordonnées tactiles et les comparons pour afficher les limites. Si le toucher est effectué en dehors d'une vue, faites quelque chose.

@Override
public boolean dispatchTouchEvent(MotionEvent event) {
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
        View yourView = (View) findViewById(R.id.view_id);
        if (yourView != null && yourView.getVisibility() == View.VISIBLE) {
            // touch coordinates
            int touchX = (int) event.getX();
            int touchY = (int) event.getY();
            // get your view coordinates
            final int[] viewLocation = new int[2];
            yourView.getLocationOnScreen(viewLocation);

            // The left coordinate of the view
            int viewX1 = viewLocation[0];
            // The right coordinate of the view
            int viewX2 = viewLocation[0] + yourView.getWidth();
            // The top coordinate of the view
            int viewY1 = viewLocation[1];
            // The bottom coordinate of the view
            int viewY2 = viewLocation[1] + yourView.getHeight();

            if (!((touchX >= viewX1 && touchX <= viewX2) && (touchY >= viewY1 && touchY <= viewY2))) {

                Do what you want...

                // If you don't want allow touch outside (for example, only hide keyboard or dismiss popup) 
                return false;
            }
        }
    }
    return super.dispatchTouchEvent(event);
}

Il n'est pas non plus nécessaire de vérifier l'existence et la visibilité de la vue si la mise en page de votre activité ne change pas pendant l'exécution (par exemple, vous n'ajoutez pas de fragments ou ne remplacez / supprimez pas les vues de la mise en page). Mais si vous souhaitez fermer (ou faire quelque chose de similaire) le menu contextuel personnalisé (comme dans le Google Play Store lorsque vous utilisez le menu de débordement de l'élément), il est nécessaire de vérifier l'existence de la vue. Sinon, vous obtiendrez un fichier NullPointerException.

Sabre
la source
0

Ce simple extrait de code fait ce que vous voulez

GestureDetector gestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
            @Override
            public boolean onSingleTapConfirmed(MotionEvent e) {
                KeyboardUtil.hideKeyboard(getActivity());
                return true;
            }
        });
mScrollView.setOnTouchListener((v, e) -> gestureDetector.onTouchEvent(e));
Andrii
la source
0

À Kotlin

hidekeyboard () est une extension Kotlin

fun Activity.hideKeyboard() {
    hideKeyboard(currentFocus ?: View(this))
}

En activité, ajouter une expédition

override fun dispatchTouchEvent(event: MotionEvent): Boolean {
    if (event.action == MotionEvent.ACTION_DOWN) {
        val v: View? = currentFocus
        if (v is EditText) {
            val outRect = Rect()
            v.getGlobalVisibleRect(outRect)
            if (!outRect.contains(event.rawX.toInt(), event.rawY.toInt())) {
                v.clearFocus()
                hideKeyboard()
            }
        }
    }
    return super.dispatchTouchEvent(event)
}

Ajoutez ces propriétés dans le parent le plus haut

android:focusableInTouchMode="true"
android:focusable="true"
Webserveis
la source
0

Ceci est ma version basée sur le code de zMan. Il ne masquera pas le clavier si la vue suivante est également un texte d'édition. Il ne masquera pas non plus le clavier si l'utilisateur fait simplement défiler l'écran.

@Override
public boolean dispatchTouchEvent(MotionEvent event) {
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
        downX = (int) event.getRawX();
    }

    if (event.getAction() == MotionEvent.ACTION_UP) {
        View v = getCurrentFocus();
        if (v instanceof EditText) {
            int x = (int) event.getRawX();
            int y = (int) event.getRawY();
            //Was it a scroll - If skip all
            if (Math.abs(downX - x) > 5) {
                return super.dispatchTouchEvent(event);
            }
            final int reducePx = 25;
            Rect outRect = new Rect();
            v.getGlobalVisibleRect(outRect);
            //Bounding box is to big, reduce it just a little bit
            outRect.inset(reducePx, reducePx);
            if (!outRect.contains(x, y)) {
                v.clearFocus();
                boolean touchTargetIsEditText = false;
                //Check if another editText has been touched
                for (View vi : v.getRootView().getTouchables()) {
                    if (vi instanceof EditText) {
                        Rect clickedViewRect = new Rect();
                        vi.getGlobalVisibleRect(clickedViewRect);
                        //Bounding box is to big, reduce it just a little bit
                        clickedViewRect.inset(reducePx, reducePx);
                        if (clickedViewRect.contains(x, y)) {
                            touchTargetIsEditText = true;
                            break;
                        }
                    }
                }
                if (!touchTargetIsEditText) {
                    InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
                    imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
                }
            }
        }
    }
    return super.dispatchTouchEvent(event);
}
Dennis H.
la source