¿Cómo ocultar el teclado virtual en Android después de hacer clic fuera de EditText?

354

Ok, todos saben que para ocultar un teclado debes implementar:

InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);

Pero el gran problema aquí es cómo ocultar el teclado cuando el usuario toca o selecciona cualquier otro lugar que no sea un EditTextsoftKeyboard.

Traté de usar el onTouchEvent()en mi padre Activitypero eso solo funciona si el usuario toca fuera de cualquier otra vista y no hay una vista de desplazamiento.

Traté de implementar un oyente de toque, clic y enfoque sin ningún éxito.

Incluso intenté implementar mi propia vista de desplazamiento para interceptar eventos táctiles, pero solo puedo obtener las coordenadas del evento y no hacer clic en la vista.

¿Hay una forma estándar de hacer esto? en iPhone fue realmente fácil.

htafoya
fuente
Bueno, me di cuenta de que la vista de desplazamiento no era realmente el problema, sino las etiquetas que están allí. La vista es un diseño vertical con algo como: TextView, EditText, TextView, EditText, etc. y textViews no permitirá que edittext pierda el foco y oculte el teclado
htafoya
Puede encontrar una solución getFields()aquí: stackoverflow.com/questions/7790487/…
Reto
Teclado se puede cerrar presionando botón de retorno, así que diría que es cuestionable si esto vale la pena el esfuerzo
gerrytan
44
Encontré esta respuesta: stackoverflow.com/a/28939113/2610855 La mejor.
Loenix

Respuestas:

575

El siguiente fragmento simplemente oculta el teclado:

public static void hideSoftKeyboard(Activity activity) {
    InputMethodManager inputMethodManager = 
        (InputMethodManager) activity.getSystemService(
            Activity.INPUT_METHOD_SERVICE);
    inputMethodManager.hideSoftInputFromWindow(
        activity.getCurrentFocus().getWindowToken(), 0);
}

Puede poner esto en una clase de utilidad, o si lo está definiendo dentro de una actividad, evite el parámetro de actividad o llame hideSoftKeyboard(this).

La parte más complicada es cuándo llamarlo. Puede escribir un método que recorra cada Viewuna de las actividades de su actividad y verifique si es un instanceof EditTextregistro setOnTouchListenerde ese componente y todo estará en su lugar. En caso de que se pregunte cómo hacerlo, de hecho es bastante simple. Esto es lo que haces, escribes un método recursivo como el siguiente, de hecho, puedes usar esto para hacer cualquier cosa, como configurar tipos de letra personalizados, etc. Aquí está el método

public void setupUI(View view) {

    // Set up touch listener for non-text box views to hide keyboard.
    if (!(view instanceof EditText)) {
        view.setOnTouchListener(new OnTouchListener() {
            public boolean onTouch(View v, MotionEvent event) {
                hideSoftKeyboard(MyActivity.this);
                return false;
            }
        });
    }

    //If a layout container, iterate over children and seed recursion.
    if (view instanceof ViewGroup) {
        for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
            View innerView = ((ViewGroup) view).getChildAt(i);
            setupUI(innerView);
        }
    }
}

Eso es todo, simplemente llame a este método después setContentViewde su actividad. En caso de que se pregunte qué parámetro pasaría, es el iddel contenedor principal. Asigne un ida su contenedor principal como

<RelativeLayoutPanel android:id="@+id/parent"> ... </RelativeLayout>

y llama setupUI(findViewById(R.id.parent)), eso es todo.

Si desea utilizar de manera eficaz, es posible crear una extendida Activityy poner este método en, y hacer todas las otras actividades en su aplicación se extienden esta actividad y llamar a su setupUI()en el onCreate()método.

Espero eso ayude.

Si usa más de 1 actividad, defina la identificación común para el diseño primario como <RelativeLayout android:id="@+id/main_parent"> ... </RelativeLayout>

Luego extienda una clase desde Activityy defina setupUI(findViewById(R.id.main_parent))Dentro de ella OnResume()y extienda esta clase en lugar de `` Actividadin your program


Aquí hay una versión de Kotlin de la función anterior:

@file:JvmName("KeyboardUtils")

fun Activity.hideSoftKeyboard() {
    currentFocus?.let {
        val inputMethodManager = ContextCompat.getSystemService(this, InputMethodManager::class.java)!!
        inputMethodManager.hideSoftInputFromWindow(it.windowToken, 0)
    }
}
Navneeth G
fuente
No me he probado a mí mismo, pero parece que funcionaría, y como tiene críticas altas, cambiaré la respuesta aceptada.
htafoya
44
¿No debería ser muy difícil? Estoy fuera de la programación de Android en este momento, así que corrígeme si me equivoco. ¿Podría de alguna manera rastrear el EditText enfocado en cualquier momento, y simplemente solicitar que pierda el foco durante un OnTouchEvent?
Navneeth G
25
No estoy seguro de si alguien más se ha encontrado con este problema, pero esto hace que la aplicación se bloquee cuando llama a hideSoftKeyboard si nada está enfocado. Puede resolver esto rodeando la segunda línea del método conif(activity.getCurrentFocus() != null) {...}
Frank Cangialosi
14
El problema con este enfoque es que supone que todas las demás vistas nunca tendrán que establecer una OnTouchListenerpara ellos. Simplemente podría establecer esa lógica en una ViewGroup.onInterceptTouchEvent(MotionEvent)vista raíz.
Alex.F
2
No funciona cuando hago clic en otros controles manteniendo el teclado abierto
mohitum
285

Puede lograr esto siguiendo los siguientes pasos:

  1. Haga que la vista principal (vista de contenido de su actividad) sea clicable y enfocable agregando los siguientes atributos

        android:clickable="true" 
        android:focusableInTouchMode="true" 
  2. Implemente un método hideKeyboard ()

        public void hideKeyboard(View view) {
            InputMethodManager inputMethodManager =(InputMethodManager)getSystemService(Activity.INPUT_METHOD_SERVICE);
            inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
        }
  3. Por último, configure onFocusChangeListener de su texto de edición.

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

Como se señala en uno de los comentarios a continuación, esto podría no funcionar si la vista principal es una Vista de desplazamiento. Para tal caso, se puede agregar el ClickIn y focusableInTouchMode en la vista directamente debajo de ScrollView.

vida
fuente
67
En mi opinión, esta es la respuesta correcta. Menos código, sin iteraciones innecesarias ...
gattshjoty
14
Me gusta mucho esta respuesta. Una cosa a tener en cuenta es que esto no funcionó para mí al agregar clickabley focusableInTouchModea mi ScrollViewelemento raíz . Tuve que agregar al padre directo de mi EditTextque era un LinearLayout.
Adam Johns
10
Funcionó perfectamente para mí. Sin embargo, si tiene dos widgets de edición de texto, debe asegurarse de manejar correctamente el enfoque en ambos, de lo contrario, alternará la ocultación innecesaria del teclado.
Marka A
1
@MarkaA No tuve problemas con esto. Cuando hago clic en el otro EditText, el teclado se queda, si se hace clic en el fondo se esconde como debería. Tuve algún otro manejo en onFocusChange, simplemente cambiando el fondo del EditText cuando tiene foco, nada valioso.
CularBytes
3
@Shrikant: también vi parpadeos con varios textos de edición. Simplemente configuré onFocusChangeListener en el padre en lugar de en cada texto de edición, y cambié la condición para decir si (hasFocus) {hideKeyboard (v); } No me he dado cuenta de que ya no parpadea cambiando entre textos de edición.
manisha
69

Simplemente anule el siguiente código en Actividad

 @Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (getCurrentFocus() != null) {
        InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
    }
    return super.dispatchTouchEvent(ev);
}
sumit sonawane
fuente
44
Solución simple, agregue actividad y también se encargará del fragmento
Rohit Maurya
1
Otra solución es crear BaseActivity y extenderla en todas las actividades
sumar sonawane el
1
Solución brillante
Ahmed Adel Ismail
1
me salvaste de apagar mi pantalla al cerrar el teclado. ¡Pulgares hacia arriba!
Dimas Mendes
1
Agradable, pero era demasiado bueno para ser verdad: simple, muy corto, y funcionó ... por desgracia, hay un problema: cuando se muestra el teclado, cada vez que tocamos el EditText que pedía el teclado, se baja y arriba automáticamente.
Chrysotribax
60

La respuesta aceptada me parece un poco complicada.

Aquí está mi solución. Agregue una OnTouchListenera su diseño principal, es decir:

findViewById(R.id.mainLayout).setOnTouchListener(this)

y pon el siguiente código en el método onTouch.

InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);

De esta manera, no tiene que iterar sobre todas las vistas.

roepit
fuente
@roepit: estoy obteniendo una claseCastexception por intentar lanzar un diseño a una vista. ¿Me estoy perdiendo de algo?
katzenhut
¿Puedes hacer referencia a tu código en alguna parte? No puedo decir qué está mal cuando no puedo ver su diseño y código de actividad / fragmento, etc.
roepit
La mejor respuesta, aún tratando de entender cómo funciona.
user40797
¿funcionará esto si haces clic en la barra de título de la aplicación?
user1506104
1
¡Esto funciona perfectamente para ocultar el teclado! Tenga en cuenta que esto en realidad no desenfoca el EditText, solo oculta el teclado. Para desenfocar también el EditText, agregue, por ejemplo, android:onClick="stealFocusFromEditTexts"al xml de la vista principal y luego public void stealFocusFromEditTexts(View view) {}a su actividad. El método de clic no necesita hacer nada, solo tiene que existir para que la vista principal sea enfocable / seleccionable, lo cual es necesario para robar el enfoque del niño EditText
Jacob R
40

Tengo una solución más para ocultar el teclado:

InputMethodManager imm = (InputMethodManager) getSystemService(
    Activity.INPUT_METHOD_SERVICE);
imm.toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY, 0);

Aquí pasa HIDE_IMPLICIT_ONLYen la posición de showFlagy 0en la posición de hiddenFlag. Se cerrará con fuerza el teclado blando.

Saurabh Pareek
fuente
3
Gracias, es trabajo ... sobre todo lo que intenté, pero no funciona mientras obtengo valor del cuadro de diálogo texto editext nd closoing dialogbox ...
PankajAndroid
Gracias, esto solo funciona, ¡y es mucho más limpio que el anterior! +1
Edmond Tamas
Funciona como se esperaba, gracias por su solución +1
tryp
Funciona como un encanto para mí. +1 para una solución elegante.
saintjab
2
lo siento, pero este método es alternar, por lo que si el estado del teclado ya está cerrado, mostrará el teclado
HendraWD
16

Bueno, logré resolver el problema de alguna manera, anulé el dispatchTouchEvent en mi actividad, allí estoy usando lo siguiente para ocultar el teclado.

 /**
 * Called to process touch screen events. 
 */
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {

    switch (ev.getAction()){
        case MotionEvent.ACTION_DOWN:
            touchDownTime = SystemClock.elapsedRealtime();
            break;

        case MotionEvent.ACTION_UP:
            //to avoid drag events
            if (SystemClock.elapsedRealtime() - touchDownTime <= 150){  

                EditText[] textFields = this.getFields();
                if(textFields != null && textFields.length > 0){

                    boolean clickIsOutsideEditTexts = true;

                    for(EditText field : textFields){
                        if(isPointInsideView(ev.getRawX(), ev.getRawY(), field)){
                            clickIsOutsideEditTexts = false;
                            break;
                        }
                    }

                    if(clickIsOutsideEditTexts){
                        this.hideSoftKeyboard();
                    }               
                } else {
                    this.hideSoftKeyboard();
                }
            }
            break;
    }

    return super.dispatchTouchEvent(ev);
}

EDITAR: El método getFields () es solo un método que devuelve una matriz con los campos de texto en la vista. Para evitar crear esta matriz en cada toque, creé una matriz estática llamada sFields, que se devuelve en el método getFields (). Esta matriz se inicializa en los métodos onStart () como:

sFields = new EditText[] {mUserField, mPasswordField};


No es perfecto, el tiempo del evento de arrastre solo se basa en la heurística, por lo que a veces no se oculta al realizar clics largos, y también terminé creando un método para obtener todos los editTexts por vista; de lo contrario, el teclado se escondería y mostraría al hacer clic en otro EditText.

Aún así, soluciones más limpias y cortas son bienvenidas

htafoya
fuente
8
Para ayudar a otros en el futuro, ¿consideraría editar el código en su respuesta para incluir su getFields()método? No tiene que ser exacto, solo un ejemplo con quizás solo algunos comentarios que indican que devuelve una matriz de EditTextobjetos.
Squonk
14

Use OnFocusChangeListener .

Por ejemplo:

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

Actualización : también puede anular onTouchEvent()su actividad y verificar las coordenadas del toque. Si las coordenadas están fuera de EditText, oculte el teclado.

Sergey Glotov
fuente
99
El problema es que el texto de edición no pierde el foco cuando hago clic en una Etiqueta u otras vistas que no son enfocables.
htafoya
En este caso tengo una solución más. He actualizado la respuesta.
Sergey Glotov
2
onTouchEvent llamó muchas veces, por lo que tampoco es una buena práctica
Jesus Dimrix
13

Implementé dispatchTouchEvent en Activity para hacer esto:

private EditText mEditText;
private Rect mRect = new Rect();
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    final int action = MotionEventCompat.getActionMasked(ev);

    int[] location = new int[2];
    mEditText.getLocationOnScreen(location);
    mRect.left = location[0];
    mRect.top = location[1];
    mRect.right = location[0] + mEditText.getWidth();
    mRect.bottom = location[1] + mEditText.getHeight();

    int x = (int) ev.getX();
    int y = (int) ev.getY();

    if (action == MotionEvent.ACTION_DOWN && !mRect.contains(x, y)) {
        InputMethodManager input = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
        input.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
    }
    return super.dispatchTouchEvent(ev);
}

y lo probé, funciona perfecto!

Jishi Chen
fuente
funciona, pero el problema es que si tenemos más de un EditText, también debemos tenerlo en cuenta, pero me gustó su respuesta :-)
Lalit Poptani
getActionMasked (ev) está en desuso, así que ahora use: final int action = ev.getActionMasked (); para la primera línea
Andrew
12

Una forma más de Kotlin y diseño de materiales usando TextInputEditText (este enfoque también es compatible con EditTextView ) ...

1.Haga clic y enfoque en la vista principal (vista de contenido de su actividad / fragmento) agregando los siguientes atributos

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

2.Cree una extensión para toda la Vista (por ejemplo, dentro de un archivo ViewExtension.kt):

fun View.hideKeyboard(){
    val inputMethodManager = context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
    inputMethodManager.hideSoftInputFromWindow(this.windowToken, 0)
}

3.Cree un BaseTextInputEditText que herede de TextInputEditText. Implemente el método onFocusChanged para ocultar el teclado cuando la vista no está enfocada:

class BaseTextInputEditText(context: Context?, attrs: AttributeSet?) : TextInputEditText(context, attrs){
    override fun onFocusChanged(focused: Boolean, direction: Int, previouslyFocusedRect: Rect?) {
        super.onFocusChanged(focused, direction, previouslyFocusedRect)
        if (!focused) this.hideKeyboard()
    }
}

4. Simplemente llame a su nueva vista personalizada en su XML:

<android.support.design.widget.TextInputLayout
        android:id="@+id/textInputLayout"
        ...>

        <com.your_package.BaseTextInputEditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            ... />

    </android.support.design.widget.TextInputLayout> 

Eso es todo. No es necesario modificar sus controladores (fragmento o actividad) para manejar este caso repetitivo.

Phil
fuente
Sí, bueno, pero desearía que hubiera una manera más simple.
devDeejay
11

Anular el envío público booleanoTouchEvent (evento MotionEvent) en cualquier actividad (o ampliar la clase de actividad)

@Override
public boolean dispatchTouchEvent(MotionEvent event) {
    View view = getCurrentFocus();
    boolean ret = super.dispatchTouchEvent(event);

    if (view instanceof EditText) {
        View w = getCurrentFocus();
        int scrcoords[] = new int[2];
        w.getLocationOnScreen(scrcoords);
        float x = event.getRawX() + w.getLeft() - scrcoords[0];
        float y = event.getRawY() + w.getTop() - scrcoords[1];

        if (event.getAction() == MotionEvent.ACTION_UP 
 && (x < w.getLeft() || x >= w.getRight() 
 || y < w.getTop() || y > w.getBottom()) ) { 
            InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
            imm.hideSoftInputFromWindow(getWindow().getCurrentFocus().getWindowToken(), 0);
        }
    }
 return ret;
}

Y eso es todo lo que necesitas hacer

Hoang Trinh
fuente
Esta fue la forma más fácil que encontré para hacerlo funcionar. funciona con múltiples EditTexts y un ScrollView
RJH
Probado con múltiples EditTexts; ¡funciona! La única desventaja es que cuando haces un movimiento de arrastre, también se esconde.
RominaV
9

Modifiqué la solución de Andre Luis IM. Logré esta:

Creé un método de utilidad para ocultar el teclado virtual de la misma manera que lo hizo Andre Luiz IM:

public static void hideSoftKeyboard(Activity activity) {
    InputMethodManager inputMethodManager = (InputMethodManager)  activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
    inputMethodManager.hideSoftInputFromWindow(activity.getCurrentFocus().getWindowToken(), 0);
}

Pero en lugar de registrar un OnTouchListener para cada vista, que da un bajo rendimiento, registré el OnTouchListener solo para la vista raíz. Dado que el evento burbujea hasta que se consume (EditText es una de las vistas que lo consume por defecto), si llega a la vista raíz, es porque no se consumió, así que cierro el teclado virtual.

findViewById(android.R.id.content).setOnTouchListener(new OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        Utils.hideSoftKeyboard(activity);
        return false;
    }
});
Fernando Camargo
fuente
1
Esto parece ser más seguro para mí.
superarts.org
9

Soy consciente de que este hilo es bastante antiguo, la respuesta correcta parece válida y hay muchas soluciones de trabajo, pero creo que el enfoque que se indica a continuación podría tener un beneficio adicional con respecto a la eficiencia y la elegancia.

Necesito este comportamiento para todas mis actividades, por lo que creé una clase CustomActivity heredada de la clase Activity y "enganché" la función dispatchTouchEvent . Hay principalmente dos condiciones para cuidar:

  1. Si el foco no cambia y alguien toca fuera del campo de entrada actual, descarte el IME
  2. Si el foco ha cambiado y el siguiente elemento enfocado no es una instancia de ningún tipo de campo de entrada, descarte el IME

Este es mi resultado:

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    if(ev.getAction() == MotionEvent.ACTION_UP) {
        final View view = getCurrentFocus();

        if(view != null) {
            final boolean consumed = super.dispatchTouchEvent(ev);

            final View viewTmp = getCurrentFocus();
            final View viewNew = viewTmp != null ? viewTmp : view;

            if(viewNew.equals(view)) {
                final Rect rect = new Rect();
                final int[] coordinates = new int[2];

                view.getLocationOnScreen(coordinates);

                rect.set(coordinates[0], coordinates[1], coordinates[0] + view.getWidth(), coordinates[1] + view.getHeight());

                final int x = (int) ev.getX();
                final int y = (int) ev.getY();

                if(rect.contains(x, y)) {
                    return consumed;
                }
            }
            else if(viewNew instanceof EditText || viewNew instanceof CustomEditText) {
                return consumed;
            }

            final InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);

            inputMethodManager.hideSoftInputFromWindow(viewNew.getWindowToken(), 0);

            viewNew.clearFocus();

            return consumed;
        }
    }       

    return super.dispatchTouchEvent(ev);
}

Nota al margen: Además, asigno estos atributos a la vista raíz, lo que permite borrar el foco en cada campo de entrada y evitar que los campos de entrada se centren en el inicio de la actividad (haciendo que la vista de contenido sea el "foco de captura"):

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    final View view = findViewById(R.id.content);

    view.setFocusable(true);
    view.setFocusableInTouchMode(true);
}
fje
fuente
Super está funcionando bien, gracias! He puesto un voto a favor por su respuesta.
vijay
2
Creo que es la mejor solución para diseños complejos. Pero encontré 2 desventajas hasta ahora: 1. El menú contextual EditText no se puede hacer clic: cualquier clic en él causa la pérdida de enfoque de EditText 2. Cuando nuestro EditText está en la parte inferior de una vista y hacemos clic largo en él (para seleccionar la palabra), luego el teclado muestra que nuestro "punto de clic" está en el teclado, no en EditText, por lo que perdemos el foco nuevamente: /
sosite
@sosite, creo que he abordado esas limitaciones en mi respuesta; echar un vistazo.
Andy Dennie
6

Me gustó el enfoque de llamadas dispatchTouchEventrealizado por htafoya, pero:

  • No entendí la parte del temporizador (no sé por qué debería ser necesario medir el tiempo de inactividad)
  • No me gusta registrar / cancelar el registro de todos los EditTexts con cada cambio de vista (podría haber muchos cambios de vista y edición de textos en jerarquías complejas)

Entonces, hice esta solución algo más fácil:

@Override
public boolean dispatchTouchEvent(final MotionEvent ev) {
    // all touch events close the keyboard before they are processed except EditText instances.
    // if focus is an EditText we need to check, if the touchevent was inside the focus editTexts
    final View currentFocus = getCurrentFocus();
    if (!(currentFocus instanceof EditText) || !isTouchInsideView(ev, currentFocus)) {
        ((InputMethodManager) getApplicationContext().getSystemService(Context.INPUT_METHOD_SERVICE))
            .hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
    }
    return super.dispatchTouchEvent(ev);
}

/**
 * determine if the given motionevent is inside the given view.
 * 
 * @param ev
 *            the given view
 * @param currentFocus
 *            the motion event.
 * @return if the given motionevent is inside the given view
 */
private boolean isTouchInsideView(final MotionEvent ev, final View currentFocus) {
    final int[] loc = new int[2];
    currentFocus.getLocationOnScreen(loc);
    return ev.getRawX() > loc[0] && ev.getRawY() > loc[1] && ev.getRawX() < (loc[0] + currentFocus.getWidth())
        && ev.getRawY() < (loc[1] + currentFocus.getHeight());
}

Hay una desventaja:

Cambiar de uno EditTexta otro EditTexthace que el teclado se oculte y vuelva a mostrar; en mi caso, se desea de esa manera, porque muestra que cambiaste entre dos componentes de entrada.

Christian R.
fuente
Este método funcionó mejor en términos de poder simplemente enchufar y jugar con mi FragmentActivity.
BillyRayCyrus
Gracias, es la mejor manera! También agregué la comprobación de la acción del evento: int action = ev.getActionMasked (); if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_POINTER_DOWN) {...}
sergey.n
Gracias ! Me ahorraste el tiempo ... La mejor respuesta.
I.d007
6

Súplica: Reconozco que no tengo influencia, pero por favor toma en serio mi respuesta.

Problema: descarte el teclado virtual cuando haga clic fuera del teclado o edite texto con un código mínimo.

Solución: Biblioteca externa conocida como Butterknife.

Solución de una línea:

@OnClick(R.id.activity_signup_layout) public void closeKeyboard() { ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0); }

Solución más legible:

@OnClick(R.id.activity_signup_layout) 
public void closeKeyboard() {
        InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
}

Explicación: enlace OnClick Listener a la ID principal del diseño XML de la actividad, de modo que cualquier clic en el diseño (no en el texto o teclado de edición) ejecutará ese fragmento de código que ocultará el teclado.

Ejemplo: si su archivo de diseño es R.layout.my_layout y su identificación de diseño es R.id.my_layout_id, entonces su llamada de enlace Butterknife debería verse así:

(@OnClick(R.id.my_layout_id) 
public void yourMethod {
    InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
    imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
}

Enlace de documentación de Butterknife: http://jakewharton.github.io/butterknife/

Plug: Butterknife revolucionará tu desarrollo de Android. Considéralo.

Nota: Se puede lograr el mismo resultado sin el uso de la biblioteca externa Butterknife. Simplemente configure un OnClickListener en el diseño principal como se describe anteriormente.

Charles Woodson
fuente
Impresionante, solución perfecta! Gracias.
nullforlife
6

En kotlin, podemos hacer lo siguiente. No es necesario iterar todas las vistas. Funcionará también para fragmentos.

override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
    currentFocus?.let {
        val imm: InputMethodManager = getSystemService(
            Context.INPUT_METHOD_SERVICE
        ) as (InputMethodManager)
        imm.hideSoftInputFromWindow(it.windowToken, 0)
    }
    return super.dispatchTouchEvent(ev)
}
Sai
fuente
no funciona desde el fragmento
Nurseyit Tursunkulov
esto funciona pero tiene un error. por ejemplo, si quiero pegar texto en la vista de texto, el teclado se oculta y luego aparece. Es un poco molesto.
George Shalvashvili
Entonces dime la mejor solución ..
Sai
4

Aquí hay otra variación en la respuesta de fje que aborda los problemas planteados por sosite.

La idea aquí es manejar las acciones hacia abajo y hacia arriba en el dispatchTouchEventmétodo de la Actividad . En la acción hacia abajo, tomamos nota de la vista enfocada actualmente (si la hay) y si el toque estaba dentro de ella, guardando esos dos bits de información para más adelante.

En la acción ascendente, primero despachamos, para permitir que otra vista potencialmente se enfoque. Si después de eso, la vista enfocada actualmente es la vista enfocada originalmente, y el toque hacia abajo estaba dentro de esa vista, entonces dejamos el teclado abierto.

Si la vista enfocada actualmente es diferente de la vista enfocada originalmente y es una EditText, entonces también dejamos el teclado abierto.

De lo contrario lo cerramos.

En resumen, esto funciona de la siguiente manera:

  • Al tocar dentro de un foco actualmente EditText, el teclado permanece abierto
  • Al pasar de un foco EditTexta otro EditText, el teclado permanece abierto (no se cierra / vuelve a abrir)
  • Al tocar cualquier lugar fuera de un foco actualmente EditTextque no sea otro EditText, el teclado se cierra
  • Al presionar prolongadamente una tecla EditTextpara que aparezca la barra de acción contextual (con los botones cortar / copiar / pegar), el teclado permanece abierto, aunque la acción ARRIBA tuvo lugar fuera del foco EditText(que se movió hacia abajo para dejar espacio para el CAB) . Sin embargo, tenga en cuenta que cuando toca un botón en el CAB, cerrará el teclado. Eso puede o no ser deseable; si desea cortar / copiar de un campo y pegar en otro, sería. Si quieres volver a pegar en el mismoEditText , no lo sería.
  • cuando el foco EditTextestá en la parte inferior de la pantalla y hace clic largo en algún texto para seleccionarlo, el EditTextfoco se mantiene y, por lo tanto, el teclado se abre como lo desea, porque hacemos la verificación "tocar está dentro de los límites de la vista" en la acción hacia abajo , no la acción de arriba.

    private View focusedViewOnActionDown;
    private boolean touchWasInsideFocusedView;
    
    
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                focusedViewOnActionDown = getCurrentFocus();
                if (focusedViewOnActionDown != null) {
                    final Rect rect = new Rect();
                    final int[] coordinates = new int[2];
    
                    focusedViewOnActionDown.getLocationOnScreen(coordinates);
    
                    rect.set(coordinates[0], coordinates[1],
                            coordinates[0] + focusedViewOnActionDown.getWidth(),
                            coordinates[1] + focusedViewOnActionDown.getHeight());
    
                    final int x = (int) ev.getX();
                    final int y = (int) ev.getY();
    
                    touchWasInsideFocusedView = rect.contains(x, y);
                }
                break;
    
            case MotionEvent.ACTION_UP:
    
                if (focusedViewOnActionDown != null) {
                    // dispatch to allow new view to (potentially) take focus
                    final boolean consumed = super.dispatchTouchEvent(ev);
    
                    final View currentFocus = getCurrentFocus();
    
                    // if the focus is still on the original view and the touch was inside that view,
                    // leave the keyboard open.  Otherwise, if the focus is now on another view and that view
                    // is an EditText, also leave the keyboard open.
                    if (currentFocus.equals(focusedViewOnActionDown)) {
                        if (touchWasInsideFocusedView) {
                            return consumed;
                        }
                    } else if (currentFocus instanceof EditText) {
                        return consumed;
                    }
    
                    // the touch was outside the originally focused view and not inside another EditText,
                    // so close the keyboard
                    InputMethodManager inputMethodManager =
                            (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
                    inputMethodManager.hideSoftInputFromWindow(
                        focusedViewOnActionDown.getWindowToken(), 0);
                    focusedViewOnActionDown.clearFocus();
    
                    return consumed;
                }
                break;
        }
    
        return super.dispatchTouchEvent(ev);
    }
Andy Dennie
fuente
4

es demasiado simple, solo haga que su diseño reciente se pueda hacer clic y se pueda enfocar con este código:

android:id="@+id/loginParentLayout"
android:clickable="true"
android:focusableInTouchMode="true"

y luego escriba un método y un OnClickListner para ese diseño, de modo que cuando se toque el diseño superior se llame a un método en el que escribirá código para descartar el teclado. el siguiente es el código para ambos; // tienes que escribir esto en OnCreate ()

 yourLayout.setOnClickListener(new View.OnClickListener(){
                @Override
                public void onClick(View view) {
                    hideKeyboard(view);
                }
            });

método llamado desde listner: -

 public void hideKeyboard(View view) {
     InputMethodManager imm =(InputMethodManager)getSystemService(Activity.INPUT_METHOD_SERVICE);
        imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
    }
swarnim dixit
fuente
4

La respuesta aceptada me parece un poco compleja para este simple requisito. Esto es lo que funcionó para mí sin ningún problema técnico.

findViewById(R.id.mainLayout).setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View view, MotionEvent motionEvent) {
            InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
            imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
            return false;
        }
    });
Joohay
fuente
1
Si usa NestedScrollViewdiseños complejos o complejos, consulte la respuesta aceptada: stackoverflow.com/a/11656129/2914140 . Debe saber que otros recipientes pueden consumir toques.
CoolMind
3

Hay un enfoque más simple, basado en el mismo problema del iPhone. Simplemente anule el diseño del fondo en el evento táctil, donde está contenido el texto de edición. Simplemente use este código en OnCreate de la actividad (login_fondo es el diseño raíz):

    final LinearLayout llLogin = (LinearLayout)findViewById(R.id.login_fondo);
    llLogin.setOnTouchListener(
            new OnTouchListener()
            {
                @Override
                public boolean onTouch(View view, MotionEvent ev) {
                    InputMethodManager imm = (InputMethodManager) mActivity.getSystemService(
                            android.content.Context.INPUT_METHOD_SERVICE);
                    imm.hideSoftInputFromWindow(mActivity.getCurrentFocus().getWindowToken(), 0);
                    return false;
                }
            });
Alex RR
fuente
1
Como dije y recuerdo, esto solo funciona si el formulario no está dentro de un ScrollView.
htafoya
1
Esto no funciona muy bien si el diseño de fondo contiene otros diseños secundarios.
AxeEffect
Gracias por recordar que onClick no era la única opción.
Harpreet
3

Método para mostrar / ocultar teclado virtual

InputMethodManager inputMethodManager = (InputMethodManager) currentActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
    if (isShow) {
        if (currentActivity.getCurrentFocus() == null) {
            inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
        } else {
            inputMethodManager.showSoftInput(currentActivity.getCurrentFocus(), InputMethodManager.SHOW_FORCED);    
        }

    } else {
        if (currentActivity.getCurrentFocus() == null) {
            inputMethodManager.toggleSoftInput(InputMethodManager.HIDE_NOT_ALWAYS, 0);
        } else {
            inputMethodManager.hideSoftInputFromInputMethod(currentActivity.getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);    
        }

    }

Espero que te hayan sido útiles

lalosoft
fuente
3

Esta es la solución más fácil para mí (y elaborada por mí).

Este es el método para ocultar el teclado.

public void hideKeyboard(View view){
        if(!(view instanceof EditText)){
            InputMethodManager inputMethodManager=(InputMethodManager)getSystemService(INPUT_METHOD_SERVICE);
            inputMethodManager.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(),0);
        }
    }

ahora establezca el atributo onclick del diseño principal de la actividad en el método anterior, hideKeyboardya sea desde la vista Diseño de su archivo XML o escribiendo el código siguiente en la vista de Texto de su archivo XML.

android:onClick="hideKeyboard"
NullByte08
fuente
2

He refinado el método, coloque el siguiente código en alguna clase de utilidad de IU (preferiblemente, no necesariamente) para que se pueda acceder desde todas sus clases de Actividad o Fragmento para cumplir su propósito.

public static void serachAndHideSoftKeybordFromView(View view, final Activity act) {
    if(!(view instanceof EditText)) {
        view.setOnTouchListener(new View.OnTouchListener() {
            public boolean onTouch(View v, MotionEvent event) {
                hideSoftKeyboard(act);
                return false;
            }
        });
    }
    if (view instanceof ViewGroup) {
        for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
            View nextViewInHierarchy = ((ViewGroup) view).getChildAt(i);
            serachAndHideSoftKeybordFromView(nextViewInHierarchy, act);
        }
    }
}
public static void hideSoftKeyboard (Activity activity) {
    InputMethodManager inputMethodManager = (InputMethodManager) activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
    inputMethodManager.hideSoftInputFromWindow(activity.getCurrentFocus().getWindowToken(), 0);
}

Luego, por ejemplo, diga que necesita llamarlo desde la actividad, llámelo de la siguiente manera;

UIutils.serachAndHideSoftKeybordFromView(findViewById(android.R.id.content), YourActivityName.this);

aviso

findViewById (android.R.id.content)

Esto nos da la vista raíz del grupo actual (no debe haber configurado la identificación en la vista raíz).

Salud :)

Uzair
fuente
2

Actividad

 @Override
 public boolean dispatchTouchEvent(MotionEvent ev) {
     ScreenUtils.hideKeyboard(this, findViewById(android.R.id.content).getWindowToken());
     return super.dispatchTouchEvent(ev);
 }

ScreenUtils

 public static void hideKeyboard(Context context, IBinder windowToken) {
     InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
     imm.hideSoftInputFromWindow(windowToken, InputMethodManager.HIDE_NOT_ALWAYS);
 }
hielo
fuente
3
Este código es simple, pero tiene un problema obvio: cierra el teclado cuando se toca en cualquier lugar . Es decir, si toca una ubicación diferente del EditText para mover el cursor de entrada, oculta el teclado y el sistema vuelve a abrir el teclado.
Malditas verduras
2

Simplemente agregue este código en la clase @Overide

public boolean dispatchTouchEvent(MotionEvent ev) {
    View view = getCurrentFocus();
    if (view != null && (ev.getAction() == MotionEvent.ACTION_UP || ev.getAction() == MotionEvent.ACTION_MOVE) && view instanceof EditText && !view.getClass().getName().startsWith("android.webkit.")) {
        int scrcoords[] = new int[2];
        view.getLocationOnScreen(scrcoords);
        float x = ev.getRawX() + view.getLeft() - scrcoords[0];
        float y = ev.getRawY() + view.getTop() - scrcoords[1];
        if (x < view.getLeft() || x > view.getRight() || y < view.getTop() || y > view.getBottom())
            ((InputMethodManager)this.getSystemService(Context.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow((this.getWindow().getDecorView().getApplicationWindowToken()), 0);
    }
    return super.dispatchTouchEvent(ev);
}
Haseeb Javed
fuente
3
Si bien esto puede responder a la pregunta, es mejor explicar las partes esenciales de la respuesta y posiblemente cuál fue el problema con el código OP.
pirho
sí @pirho También estoy de acuerdo con usted. Haseeb necesita concentrarse en dar la respuesta adecuada.
Dilip
2
@Dilip ¿Sabía que también puede votar los comentarios que está de acuerdo? Esto es solo para mantener limpia la sección de comentarios para no tener muchos comentarios que realmente tengan el mismo punto.
pirho
2

En lugar de iterar a través de todas las vistas o anular dispatchTouchEvent.

¿Por qué no simplemente anular onUserInteraction () de la Actividad, esto asegurará que el teclado se cierre cada vez que el usuario toque fuera de EditText.

Funcionará incluso cuando EditText esté dentro de scrollView.

@Override
public void onUserInteraction() {
    if (getCurrentFocus() != null) {
        InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
    }
}
SKG
fuente
esta es la mejor respuesta
ovluca
¡Si! Esa es una respuesta súper limpia. Y si crea una actividad abstracta que todas sus otras actividades extienden, puede incluirla como el comportamiento predeterminado en toda su aplicación.
wildcat12
1

Obtuve esto trabajando con una ligera variante en la solución de Fernando Camarago. En mi método onCreate, adjunto un único onTouchListener a la vista raíz, pero envío la vista en lugar de la actividad como argumento.

        findViewById(android.R.id.content).setOnTouchListener(new OnTouchListener() {           
        public boolean onTouch(View v, MotionEvent event) {
            Utils.hideSoftKeyboard(v);
            return false;
        }
    });

En una clase de Utils separada es ...

    public static void hideSoftKeyboard(View v) {
    InputMethodManager imm = (InputMethodManager) v.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 
    imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
}
AndyMc
fuente
1

Esto puede ser antiguo, pero conseguí que funcione implementando una clase personalizada

public class DismissKeyboardListener implements OnClickListener {

    Activity mAct;

    public DismissKeyboardListener(Activity act) {
        this.mAct = act;
    }

    @Override
    public void onClick(View v) {
        if ( v instanceof ViewGroup ) {
            hideSoftKeyboard( this.mAct );
        }
    }       
}

public void hideSoftKeyboard(Activity activity) {
        InputMethodManager imm = (InputMethodManager)
        getSystemService(Activity.INPUT_METHOD_SERVICE);
        imm.toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY, 0);
}

la mejor práctica aquí es crear una clase Helper y cada contenedor de diseños relativos / lineales debe implementar esto.

**** Tome nota que solo el contenedor principal debe implementar esta clase (para optimización) ****

e implementarlo así:

Parent.setOnClickListener( new DismissKeyboardListener(this) ); 

la palabra clave esto es para Actividad. así que si estás en un fragmento, usas como getActivity ();

--- aprobado si te ayuda ... --- aplaude Ralph ---

ralphgabb
fuente
1

Esta es una versión ligeramente modificada de la respuesta de fje que en su mayoría funcionó perfectamente.

Esta versión usa ACTION_DOWN, por lo que realizar una acción de desplazamiento también cierra el teclado. Tampoco propaga el evento a menos que haga clic en otro EditText. Esto significa que al hacer clic en cualquier lugar fuera de EditText, incluso en otro clic, simplemente cierra el teclado.

@Override
public boolean dispatchTouchEvent(MotionEvent ev)
{
    if(ev.getAction() == MotionEvent.ACTION_DOWN)
    {
        final View view = getCurrentFocus();

        if(view != null)
        {
            final View viewTmp = getCurrentFocus();
            final View viewNew = viewTmp != null ? viewTmp : view;

            if(viewNew.equals(view))
            {
                final Rect rect = new Rect();
                final int[] coordinates = new int[2];

                view.getLocationOnScreen(coordinates);

                rect.set(coordinates[0], coordinates[1], coordinates[0] + view.getWidth(), coordinates[1] + view.getHeight());

                final int x = (int) ev.getX();
                final int y = (int) ev.getY();

                if(rect.contains(x, y))
                {
                    super.dispatchTouchEvent(ev);
                    return true;
                }
            }
            else if(viewNew instanceof EditText || viewNew instanceof CustomEditText)
            {
                super.dispatchTouchEvent(ev);
                return true;
            }

            final InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);

            inputMethodManager.hideSoftInputFromWindow(viewNew.getWindowToken(), 0);

            viewNew.clearFocus();

            return true;
        }
    }
    return super.dispatchTouchEvent(ev);
}
Cadmonkey33
fuente
Algo no se ve bien aquí; estás asignando ambos viewy viewTmpa getCurrentFocus(), por lo que siempre tendrán el mismo valor.
Andy Dennie
1

He hecho de esta manera:

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
   View view = getCurrentFocus();
   if (view != null && (ev.getAction() == MotionEvent.ACTION_UP || ev.getAction() == MotionEvent.ACTION_MOVE) && view instanceof EditText && !view.getClass().getName().startsWith("android.webkit.")) {
            int scrcoords[] = new int[2];
            view.getLocationOnScreen(scrcoords);
            float x = ev.getRawX() + view.getLeft() - scrcoords[0];
            float y = ev.getRawY() + view.getTop() - scrcoords[1];
            if (x < view.getLeft() || x > view.getRight() || y < view.getTop() || y > view.getBottom())
                hideKeyboard(this);
        }
    return super.dispatchTouchEvent(ev);
}

Ocultar código del teclado :

public static void hideKeyboard(Activity act) {
    if(act!=null)
      ((InputMethodManager)act.getSystemService(Context.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow((act.getWindow().getDecorView().getApplicationWindowToken()), 0);
  }

Hecho

Hiren Patel
fuente