web-dev-qa-db-fra.com

EditText, effacer le focus sur le toucher extérieur

Ma mise en page contient ListView, SurfaceView et 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 (cela ne devrait pas) . Je suppose que je pourrais configurer les OnTouchListener sur les autres vues de la présentation et effacer manuellement le focus de EditText. Mais semble trop hackish ...

J'ai également la même situation dans l'autre affichage de liste de présentation avec différents types d'éléments, dont certains contiennent des éléments EditText. Ils agissent comme je l'ai écrit ci-dessus.

La tâche consiste à faire en sorte que EditText ne se concentre plus lorsque l'utilisateur touche quelque chose en dehors de celle-ci.

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

76
note173

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

J'ai créé une variable (invisible) FrameLayout appelée touchInterceptor en tant que dernière View dans la présentation, de sorte qu'elle recouvre tout ( edit: vous devez également utiliser une RelativeLayout comme disposition parent et donner le touchInterceptorfill_parent attributs). Ensuite, je l'ai utilisé pour intercepter les contacts et déterminer si le contact était au-dessus de EditText ou 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;
    }
});

Renvoie false pour laisser la manipulation tactile échouer.

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

61
Ken

S'appuyant sur la réponse de Ken, voici la solution de copier-coller la plus modulaire.

Pas de XML nécessaire.

Mettez-le dans votre activité et cela s'appliquera à tous les EditTexts, y compris ceux contenus dans des 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 );
}
178
zMan

Pour la vue parent de EditText, laissez les 3 attributs suivants être " true ":
clickable , focusable , focusableInTouchMode .

Si une vue souhaite recevoir le focus, elle doit satisfaire à 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. 

32
suber

La réponse de Ken fonctionne, mais c'est hacky. Comme le dit le commentaire de la réponse, pcans pourrait faire la même chose avec dispatchTouchEvent. Cette solution est plus propre car elle évite de devoir pirater le XML avec un FrameLayout factice transparent. Voici à quoi ça 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);
}
14
Mike Ortiz

Il suffit de mettre ces propriétés dans la partie la plus parent.

Android:focusableInTouchMode="true"
Android:clickable="true"
Android:focusable="true" 
12
chandan

Vous avez probablement déjà trouvé la réponse à ce problème, mais je cherchais un moyen de résoudre ce problème et je ne parviens toujours pas à trouver exactement ce que je cherchais.

Voici ce que j’ai fait (c’est très général, le but est de vous donner une idée de la marche à suivre, copier et coller tout le code ne fonctionnera pas O: D):

Commencez par insérer le texte EditText et les autres vues de votre programme dans une seule vue. Dans mon cas, j'ai utilisé un LinearLayout pour tout emballer.

<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 Touch Listener 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, cela les aide à résoudre leur problème.

5
edc598

Je pense vraiment que c’est un moyen plus robuste d’utiliser getLocationOnScreen que getGlobalVisibleRect. Parce que je rencontre un problème. Il existe une liste qui contient du texte Edittext et ajustpan dans l'activité. Je trouve que getGlobalVisibleRect retourne une valeur qui ressemble à inclure le scrollY, mais 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())));
}
2
Victor Choy

Définissez simplement deux propriétés de parent de cette EditText comme suit:

Android:clickable="true"
Android:focusableInTouchMode="true"

Ainsi, lorsque l'utilisateur touchera en dehors de la zone EditText, la focalisation sera supprimée car elle sera transférée à la vue parent. 

1
Dhruvam Gupta

J'ai une ListView composée de EditText vues. Le scénario dit qu'après avoir édité du texte dans une ou plusieurs rangées, il faut cliquer sur un bouton appelé "terminer". J'ai utilisé onFocusChanged dans la vue EditText à l'intérieur de listView, mais 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();

dans la onClickListener du bouton "terminer" et les données ont été enregistrées avec succès. 

1
Muhannad A.Alhariri

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

1. Ajout de Android:clickable="true" et Android:focusableInTouchMode="true" à la parentLayout de EditTexti.e Android.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. Remplacer dispatchTouchEvent dans la classe d'activité et insérer la fonction hideKeyboard() 

@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. Ajouter setOnFocusChangeListener pour EditText

EmployeeId.setOnFocusChangeListener(new View.OnFocusChangeListener() {
            @Override
            public void onFocusChange(View v, boolean hasFocus) {
                if (!hasFocus) {
                    hideKeyboard(v);
                }
            }
        });
1
Adarsh Gumashta

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

Mais il semble que l'utilisation de focus en mode tactile ne soit pas recommandée . S'il vous plaît jetez un oeil ici: http://Android-developers.blogspot.com/2008/12/touch-mode.html

1
forumercio

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

Ici, nous obtenons les coordonnées tactiles et les comparant 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);
}

De plus, il n'est pas nécessaire de vérifier l'existence et la visibilité des vues si la présentation de votre activité ne change pas pendant l'exécution (par exemple, vous n'ajoutez pas de fragments, ne remplacez pas/ne supprimez pas les vues de la présentation). Toutefois, si vous souhaitez fermer (ou faire quelque chose de similaire) un menu contextuel personnalisé (comme dans Google Play Store lorsque vous utilisez le menu de dépassement de capacité de l'élément), vous devez vérifier l'existence de la vue. Sinon, vous obtiendrez une NullPointerException.

0
Sabre

La meilleure façon est d'utiliser la méthode par défaut clearFocus()

Vous savez comment résoudre les codes dans onTouchListener à droite?

Il suffit d'appeler EditText.clearFocus(). Cela effacera le focus dans last EditText.

0
Huy Tower

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));
0
Andrii