EditText, enfoque claro en el tacto exterior

100

Mi diseño contiene ListView, SurfaceViewy EditText. Cuando hago clic en EditText, recibe el enfoque y aparece el teclado en pantalla. Cuando hago clic en algún lugar fuera del EditText, todavía tiene el foco (no debería). Supongo que podría configurar OnTouchListener's en las otras vistas en el diseño y borrar manualmente el EditTextfoco'. Pero parece demasiado hackish ...

También tengo la misma situación en la otra vista de lista de diseño con diferentes tipos de elementos, algunos de los cuales tienen EditTextdentro. Actúan como escribí anteriormente.

La tarea consiste en EditTextperder el foco cuando el usuario toca algo fuera de él.

He visto preguntas similares aquí, pero no he encontrado ninguna solución ...

note173
fuente

Respuestas:

66

Probé todas estas soluciones. edc598 era el más cercano a funcionar, pero los eventos táctiles no se activaban en otros Views contenidos en el diseño. En caso de que alguien necesite este comportamiento, esto es lo que terminé haciendo:

Creé un (invisible) FrameLayoutllamado touchInterceptor como el último Viewen el diseño para que se superponga a todo ( editar: también debe usar a RelativeLayoutcomo diseño principal y dar los atributos touchInterceptor fill_parent ). Luego lo usé para interceptar toques y determinar si el toque estaba encima del EditTexto no:

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

Devuelva falso para dejar que la manipulación táctil se caiga.

Es hacky, pero es lo único que funcionó para mí.

Conocido
fuente
20
Puede hacer lo mismo sin agregar un diseño adicional anulando dispatchTouchEvent (MotionEvent ev) en su actividad.
pcans
gracias por la pista clearFocus (), también funcionó para mí en SearchView
Jimmy Ilenloa
1
Me gustaría insinuar la respuesta de @ zMan a continuación. Se basa en esto, pero no necesita una vista stackoverflow.com/a/28939113/969016
Boy
Además, si alguien se encuentra con una situación en la que el teclado no se esconde, pero se limpia el enfoque, primero invoca y hideSoftInputFromWindow()luego limpia el enfoque
Ahmet Gokdayi
230

Sobre la base de la respuesta de Ken, aquí está la solución de copiar y pegar más modular.

No se necesita XML.

Colóquelo en su Actividad y se aplicará a todos los EditTexts, incluidos los que están dentro de los fragmentos dentro de esa actividad.

@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
fuente
12
Se ve bien. Todavía tengo un problema, todo mi texto de edición está dentro de una vista de desplazamiento y el texto de edición superior siempre tiene el cursor visible. Incluso cuando hago clic en el exterior, el enfoque se pierde y el teclado desaparece, pero el cursor sigue visible en el texto de edición superior.
Vaibhav Gupta
1
¡La mejor solución que encontré! Anteriormente estaba usando @Override public boolean onTouch (View v, evento MotionEvent) {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 (); } falso retorno; } falso retorno; }
Pradeep Kumar Kushwaha
¡Simple, conciso, funcional! Seguro que es la mejor solución. Gracias
Aleksandar
13
¡La mejor solución que nunca vi antes! Guardaré este código en mi esencia para pasarlo a mi hijo y a mi nieto.
Fan Applelin
2
Si el usuario hace clic en otro EditText, el teclado se cierra y se vuelve a abrir inmediatamente. Para resolver esto, cambié MotionEvent.ACTION_DOWNa MotionEvent.ACTION_UPsu código. ¡Gran respuesta por cierto!
Thanasis1101
35

Para la vista principal de EditText, deje que los siguientes 3 atributos sean " verdaderos ":
seleccionable , enfocable , enfocableInTouchMode .

Si una vista desea recibir el enfoque, debe cumplir estas 3 condiciones.

Ver android.view :

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

Espero eso ayude.

suber
fuente
2
Para aclarar el enfoque, la otra vista enfocable debe ser padre de la vista enfocada. No funcionará si la vista para enfocar está en otra jerarquía.
AxeEffect
Técnicamente, su declaración es incorrecta. La vista de los padres tiene que ser (clickable) *OR* (focusable *AND* focusableInTouchMode);)
Martin Marconcini
24

Simplemente coloque estas propiedades en la parte superior más primaria.

android:focusableInTouchMode="true"
android:clickable="true"
android:focusable="true" 
chandan
fuente
gracias hombre. Estuve luchando con este problema desde las últimas dos horas. Agregar estas líneas en el padre superior funcionó.
Däñish Shärmà
FYI: Aunque funciona para la mayoría de los casos, no funciona para algunos elementos internos del diseño.
SV Madhava Reddy
17

La respuesta de Ken funciona, pero es engañosa. Como pcans alude en el comentario de la respuesta, se podría hacer lo mismo con dispatchTouchEvent. Esta solución es más limpia ya que evita tener que piratear el XML con un FrameLayout transparente y ficticio. Así es como se ve:

@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
fuente
7

Para mí debajo las cosas funcionaron -

1. Añadiendo android:clickable="true"y android:focusableInTouchMode="true"al parentLayoutde 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. Anulación dispatchTouchEventen la clase de actividad e inserción de hideKeyboard()funciones

@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. Agregar setOnFocusChangeListenerpara EditText

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

Probablemente ya haya encontrado la respuesta a este problema, pero he estado buscando cómo resolverlo y todavía no puedo encontrar exactamente lo que estaba buscando, así que pensé que lo publicaría aquí.

Lo que hice fue lo siguiente (esto es muy generalizado, el propósito es darte una idea de cómo proceder, copiar y pegar todo el código no funcionará O: D):

Primero tenga EditText y cualquier otra vista que desee en su programa envuelta en una sola vista. En mi caso, usé LinearLayout para envolver todo.

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

Luego, en su código, debe configurar un Touch Listener en su 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;
        }
    });

Espero que esto ayude a algunas personas. O al menos les ayuda a empezar a resolver su problema.

edc598
fuente
Sí, también he llegado a una solución similar. Luego porté un subconjunto del sistema de vista de Android a c ++ en opengl y construí esta función en él)
nota173
Este es realmente un ejemplo muy claro. Estaba buscando un código como este, pero todo lo que pude encontrar fueron soluciones complejas. Usé tanto la coordenada XY para ser más preciso, ya que también tenía pocos EditText de varias líneas. ¡Gracias!
Sumitk
3

Simplemente defina dos propiedades del padre de eso EditTextcomo:

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

Entonces, cuando el usuario toque fuera del EditTextárea, el enfoque se eliminará porque el enfoque se transferirá a la vista principal.

Dhruvam Gupta
fuente
2

Tengo un ListViewcompuesto de EditTextpuntos de vista. El escenario dice que después de editar el texto en una o más filas, debemos hacer clic en un botón llamado "finalizar". Utilicé onFocusChangeden la EditTextvista interior, listViewpero después de hacer clic en finalizar, los datos no se guardan. El problema se resolvió agregando

listView.clearFocus();

dentro del onClickListenerpara el botón "finalizar" y los datos se guardaron correctamente.

Muhannad A. Alhariri
fuente
Gracias. Ahorraste mi tiempo, tuve este escenario en la vista de reciclaje y estaba perdiendo el último valor editText después de hacer clic en el botón, porque el último enfoque editText no cambió y así en FocusChanged no se llamó. Me gustaría encontrar una solución que haga que el último editText pierda el foco cuando esté fuera de él, haga clic en el mismo botón ... y encuentre su solución. ¡Funcionó muy bien!
Reyhane Farshbaf
2

Realmente creo que es una forma más sólida de usar getLocationOnScreenque getGlobalVisibleRect. Porque me encuentro con un problema. Hay una vista de lista que contiene un texto de edición y un conjunto ajustpanen la actividad. Encuentro que getGlobalVisibleRectdevuelve un valor que parece incluir scrollY en él, pero event.getRawY siempre está junto a la pantalla. El siguiente código funciona 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
fuente
¡Muchas gracias! Funcionó para todos los escenarios de diseño como RecyclerView, etc., etc. Probé otra solución, ¡pero esta sirve para todos! :) ¡Gran trabajo!
Woppi
1

Para perder el enfoque cuando se toca otra vista, ambas vistas deben establecerse como view.focusableInTouchMode (verdadero).

Pero parece que no se recomienda el uso de focos en modo táctil. Eche un vistazo aquí: http://android-developers.blogspot.com/2008/12/touch-mode.html

forumercio
fuente
Sí, he leído esto, pero no es exactamente lo que quiero. No necesito la otra vista para enfocarme. Aunque no veo ninguna forma alternativa en este momento ... ¿Quizás, de alguna manera hacer que la vista raíz capture el foco cuando el usuario hace clic en un niño no enfocable?
nota173
Hasta donde yo sé, el foco no puede ser administrado por una clase, es administrado por Android ... no hay ideas :(
forumercio
0

La mejor manera es usar el método predeterminado clearFocus()

¿Sabes cómo resolver códigos en onTouchListenerverdad?

Solo llama EditText.clearFocus(). Se aclarará el enfoque en last EditText.

Torre Huy
fuente
0

Como sugirió @pcans, puede hacer esta anulación dispatchTouchEvent(MotionEvent event) en su actividad.

Aquí obtenemos las coordenadas táctiles y las comparamos para ver los límites. Si el toque se realiza fuera de una vista, haga algo.

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

Además, no es necesario verificar la existencia y visibilidad de la vista si el diseño de su actividad no cambia durante el tiempo de ejecución (por ejemplo, no agrega fragmentos o reemplaza / elimina vistas del diseño). Pero si desea cerrar (o hacer algo similar) el menú contextual personalizado (como en Google Play Store cuando usa el menú de desbordamiento del elemento) es necesario verificar la existencia de la vista. De lo contrario, obtendrá un NullPointerException.

Sable
fuente
0

Este simple fragmento de código hace lo que quieres

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
fuente
0

En Kotlin

hidekeyboard () es una extensión de Kotlin

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

En actividad agregar dispatchTouchEvent

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

Agregue estas propiedades en la parte superior más primaria

android:focusableInTouchMode="true"
android:focusable="true"
Webserveis
fuente
0

Esta es mi versión basada en el código de zMan. No ocultará el teclado si la siguiente vista también es un texto de edición. Tampoco ocultará el teclado si el usuario simplemente se desplaza por la pantalla.

@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.
fuente