Como ocultar o teclado virtual no android depois de clicar fora do EditText?

354

Ok, todo mundo sabe que, para ocultar um teclado, você precisa implementar:

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

Mas o grande problema aqui é como ocultar o teclado quando o usuário toca ou seleciona qualquer outro lugar que não EditTextseja o ou o softKeyboard?

Tentei usar o onTouchEvent()no meu pai, Activitymas isso só funciona se o usuário tocar fora de qualquer outra visualização e não houver rolagem.

Tentei implementar um toque, clique, focalize o ouvinte sem sucesso.

Até tentei implementar minha própria visualização de rolagem para interceptar eventos de toque, mas só consigo obter as coordenadas do evento e não clicar na visualização.

Existe uma maneira padrão de fazer isso? no iPhone foi muito fácil.

htafoya
fonte
Bem, percebi que o scrollview não era realmente o problema, mas os rótulos existentes. A vista é um layout vertical com algo como: TextView, EditText, TextView, EditText, etc .. e os TextViews não vai deixar o EditText de foco solto e ocultar o teclado
htafoya
Você pode encontrar uma solução para getFields()aqui: stackoverflow.com/questions/7790487/…
Reto
Teclado pode ser fechado pressionando o botão de retorno, então eu diria que é questionável se isso vale a pena o esforço
gerrytan
4
Encontrei esta resposta: stackoverflow.com/a/28939113/2610855 A melhor.
Loenix

Respostas:

575

O seguinte trecho simplesmente oculta o teclado:

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

Você pode colocar isso em uma classe de utilitário ou, se estiver definindo em uma atividade, evite o parâmetro de atividade ou chame hideSoftKeyboard(this).

A parte mais complicada é quando chamá-lo. Você pode escrever um método que itere em todas as Viewsuas atividades e verificar se é um instanceof EditTextse não estiver registrando setOnTouchListenera nesse componente e tudo se encaixará. Caso você esteja se perguntando como fazer isso, é de fato bastante simples. Aqui está o que você faz, você escreve um método recursivo como o seguinte, na verdade, você pode usá-lo para fazer qualquer coisa, como configurar tipos de letra personalizados etc ... Aqui está o 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);
        }
    }
}

Isso é tudo, basta chamar esse método depois de você setContentViewem sua atividade. Caso você esteja se perguntando qual parâmetro você passaria, é o iddo contêiner pai. Atribua um idao seu container pai como

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

e ligar setupUI(findViewById(R.id.parent)), isso é tudo.

Se você deseja usar isso efetivamente, poderá criar um Activitymétodo estendido e colocar esse método, além de fazer com que todas as outras atividades em seu aplicativo estendam essa atividade e chamem- setupUI()no no onCreate()método

Espero que ajude.

Se você usar mais de uma atividade, defina o ID comum para o layout pai, como <RelativeLayout android:id="@+id/main_parent"> ... </RelativeLayout>

Em seguida, estenda uma classe Activitye defina setupUI(findViewById(R.id.main_parent))Dentro dela OnResume()e estenda essa classe em vez de `` Atividadein your program


Aqui está uma versão Kotlin da função acima:

@file:JvmName("KeyboardUtils")

fun Activity.hideSoftKeyboard() {
    currentFocus?.let {
        val inputMethodManager = ContextCompat.getSystemService(this, InputMethodManager::class.java)!!
        inputMethodManager.hideSoftInputFromWindow(it.windowToken, 0)
    }
}
Navneeth G
fonte
Eu não testei a mim mesmo, mas parece que funcionaria e, como tem críticas elevadas, alterarei a resposta aceita.
htafoya
4
Não deve ser muito difícil? Estou fora da programação do Android agora, então me corrija se estiver errado. De alguma forma, você pode rastrear o EditText focado a qualquer momento e solicitar que ele perca o foco durante um OnTouchEvent?
precisa saber é o seguinte
25
Não tenho certeza se alguém já passou por esse problema, mas isso causa uma falha no aplicativo quando você chama hideSoftKeyboard se nada estiver focado. Você pode resolver isso em torno da segunda linha do método comif(activity.getCurrentFocus() != null) {...}
Frank Cangialosi
14
O problema dessa abordagem é que ela assume que todas as outras visualizações nunca precisarão definir uma OnTouchListenerpara elas. Você pode simplesmente definir essa lógica em uma ViewGroup.onInterceptTouchEvent(MotionEvent)para uma visualização raiz.
Alex.F
2
Não funciona quando eu clicar em outros controles mantendo o teclado aberto
mohitum
285

Você pode conseguir isso executando as seguintes etapas:

  1. Torne a visualização principal (visualização de conteúdo da sua atividade) clicável e focável, adicionando os seguintes atributos

        android:clickable="true" 
        android:focusableInTouchMode="true" 
  2. Implementar um método hideKeyboard ()

        public void hideKeyboard(View view) {
            InputMethodManager inputMethodManager =(InputMethodManager)getSystemService(Activity.INPUT_METHOD_SERVICE);
            inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
        }
  3. Por fim, defina o onFocusChangeListener do seu edittext.

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

Conforme indicado em um dos comentários abaixo, isso pode não funcionar se a exibição pai for um ScrollView. Nesse caso, o Clickable e focusableInTouchMode podem ser adicionados à exibição diretamente sob o ScrollView.

vida
fonte
67
Na minha opinião, esta é a resposta correta. Código menos, há iterações desnecessárias ...
gattshjoty
14
Eu gosto muito dessa resposta. Uma coisa a observar é que isso não funcionou para mim ao adicionar clickablee focusableInTouchModeao meu ScrollViewelemento raiz . Eu tive que adicionar ao pai direto do meu EditTextque era um LinearLayout.
Adam Johns
10
Funcionou perfeitamente para mim. No entanto, se você tiver dois widgets do edittext, precisará se certificar de manipular o foco dos dois corretamente, caso contrário, alternará ocultando desnecessariamente o teclado.
Marka A
11
@MarkaA Não tive problemas com isso. Quando clico no outro EditText, o teclado permanece, se clicado no plano de fundo, ele se esconde como deveria. Eu tive algumas outras manipulações no onFocusChange, apenas alterando o plano de fundo do EditText quando ele tem foco, nada fency.
CularBytes 2/07
3
@ Srikant - Eu estava vendo cintilação também com vários textos de edição. Acabei de definir o onFocusChangeListener no pai, em vez de em cada texto de edição, e mudei a condição para dizer se (hasFocus) {hideKeyboard (v); } Não percebemos mais cintilação ao alternar entre os textos de edição.
manisha
69

Basta substituir o código abaixo na Atividade

 @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
fonte
4
Solução simples, Adicionar em atividade e vai cuidar do fragmento também
Rohit Maurya
11
Outra solução é criar BaseActivity e estendê-lo em todas as atividades
sumit sonawane
11
Solução brilhante
Ahmed Adel Ismail
11
você me salvou de piscar minha tela ao fechar o teclado. Afirmativo!
Dimas Mendes
11
Bom, mas era bom demais para ser verdade: simples, muito curto e funcionou ... infelizmente, há um problema: quando o teclado é exibido, toda vez que tocamos no EditText que solicitou o teclado, ele desce e automaticamente.
Chrysotribax
60

Acho a resposta aceita um pouco complicada.

Aqui está a minha solução. Adicione um OnTouchListenerao seu layout principal, ou seja:

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

e coloque o seguinte código no método onTouch.

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

Dessa forma, você não precisa repetir todas as visualizações.

roepit
fonte
@roepit - estou recebendo uma classCastexception por tentar converter um layout em uma exibição. estou esquecendo de algo?
katzenhut
Você pode fazer referência ao seu código em algum lugar? Não sei dizer o que há de errado quando não consigo olhar para o seu layout e o código de atividade / fragmento etc.
roepit
Melhor resposta lá fora, ainda tentando entender como isso funciona.
User40797
isso funcionará se você clicar na barra de título do aplicativo?
user1506104
11
Isso funciona perfeitamente para esconder o teclado! Observe que isso na verdade não desfoca o EditText, apenas oculta o teclado. Para também desfocar o EditText, adicione, por exemplo, android:onClick="stealFocusFromEditTexts"ao xml da visualização principal e depois public void stealFocusFromEditTexts(View view) {}à sua atividade. O método on-click não precisa fazer nada, apenas deve existir para que a visualização pai seja focalizável / selecionável, o que é necessário para roubar o foco da criança. EditText
Jacob R
40

Eu tenho mais uma solução para ocultar o teclado:

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

Aqui passe HIDE_IMPLICIT_ONLYna posição de showFlage 0na posição de hiddenFlag. Fechará com força o teclado virtual.

Saurabh Pareek
fonte
3
Graças é trabalho ..... acima de tudo, eu tinha tentado, mas ele não está funcionando enquanto im recebendo o valor de texto dialogbox EditExt nd closoing dialogbox ...
PankajAndroid
Obrigado, isso está funcionando apenas, e é muito mais limpo que o outro acima! +1
Edmond Tamas
Está funcionando como o esperado, obrigado pela sua solução +1
tryp
Funciona como um encanto para mim. +1 para solução elegante.
saintjab
2
Desculpe, mas este método é de alternância, por isso, se estado do teclado já está fechado, ele irá mostrar o teclado
HendraWD
16

Bem, eu consigo resolver um pouco o problema, substitui o dispatchTouchEvent na minha atividade, lá estou usando o seguinte para ocultar o 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);
}

EDIT: O método getFields () é apenas um método que retorna uma matriz com os campos de texto na exibição. Para evitar criar essa matriz a cada toque, criei uma matriz estática chamada sFields, que é retornada no método getFields (). Essa matriz é inicializada nos métodos onStart (), como:

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


Não é perfeito. O horário do evento de arrastar é baseado apenas em heurísticas; portanto, às vezes, ele não se oculta ao executar cliques longos, e eu também terminei criando um método para obter todos os editTexts por exibição; caso contrário, o teclado ocultaria e mostraria ao clicar em outro EditText.

Ainda assim, soluções mais limpas e mais curtas são bem-vindas

htafoya
fonte
8
Para ajudar outras pessoas no futuro, você consideraria editar o código em sua resposta para incluir seu getFields()método? Não precisa ser exato, apenas um exemplo com talvez apenas alguns comentários indicando que ele retorna uma matriz de EditTextobjetos.
21411 Squonk
14

Use OnFocusChangeListener .

Por exemplo:

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

Atualização : você também pode substituir onTouchEvent()sua atividade e verificar as coordenadas do toque. Se as coordenadas estiverem fora do EditText, oculte o teclado.

Sergey Glotov
fonte
9
O problema é que o edittext não perde o foco quando clico em um Rótulo ou em outras visualizações que não são focalizáveis.
Htafoya
Neste caso, tenho mais uma solução. Eu atualizei a resposta.
Sergey Glotov 12/11/2010
2
onTouchEvent chamado a muitas vezes de modo que este não é uma boa prática, quer
Jesus Dimrix
13

Eu implementei o dispatchTouchEvent na Activity para fazer isso:

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

e eu testei, funciona perfeito!

Jishi Chen
fonte
obras, mas problema nisso é que se tivermos mais de um EditText então precisamos considerar isso também, mas eu gostei sua resposta :-)
Lalit Poptani
getActionMasked (ev) foi descontinuado, agora use: final int action = ev.getActionMasked (); para a primeira linha.
Andrew
12

Uma maneira mais Kotlin e Design de materiais usando TextInputEditText (essa abordagem também é compatível com EditTextView ) ...

1.Faça clicável e focável a visualização pai (visualização de conteúdo de sua atividade / fragmento) adicionando os seguintes atributos

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

2.Crie uma extensão para todo o View (dentro de um arquivo ViewExtension.kt, por exemplo):

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

3.Crie um BaseTextInputEditText que herda de TextInputEditText. Implemente o método onFocusChanged para ocultar o teclado quando a exibição não estiver focada:

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. Basta chamar sua nova exibição personalizada em seu 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> 

Isso é tudo. Não há necessidade de modificar seus controladores (fragmento ou atividade) para lidar com esse caso repetitivo.

Phil
fonte
Sim, mas eu gostaria que houvesse uma maneira mais simples!
devDeejay
11

Substituir público booleano dispatchTouchEvent (evento MotionEvent) em qualquer Atividade (ou estender a classe de Atividade)

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

E é tudo o que você precisa fazer

Hoang Trinh
fonte
essa foi a maneira mais fácil que encontrei para fazê-lo funcionar. trabalha com vários EditTexts e uma ScrollView
RJH
Testei com vários EditTexts; funciona! A única desvantagem é que, quando você faz um movimento de arrastar, ele também oculta.
RominaV
9

Modifiquei a solução de Andre Luis IM, consegui esta:

Criei um método utilitário para ocultar o teclado virtual da mesma maneira que Andre Luiz IM:

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

Mas, em vez de registrar um OnTouchListener para todas as visualizações, que apresentam um desempenho ruim, registrei o OnTouchListener apenas para a visualização raiz. Como o evento borbulha até ser consumido (o EditText é uma das visualizações que o consome por padrão), se chega à visualização raiz, é porque não foi consumido, por isso fecho o 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
fonte
11
Isso parece ser mais seguro para mim.
Superarts.org
9

Estou ciente de que esse tópico é bastante antigo, a resposta correta parece válida e há muitas soluções de trabalho por aí, mas acho que a abordagem descrita abaixo pode ter um benefício adicional em relação à eficiência e elegância.

Como preciso desse comportamento para todas as minhas atividades, criei uma classe CustomActivity herdando da classe Activity e "liguei" a função dispatchTouchEvent . Existem principalmente duas condições para cuidar:

  1. Se o foco permanecer inalterado e alguém estiver tocando fora do campo de entrada atual, ignore o IME
  2. Se o foco mudou e o próximo elemento focado não for uma instância de qualquer tipo de campo de entrada, então descarte o IME

Este é o meu 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 lateral: Além disso, atribuo esses atributos à visualização raiz, possibilitando limpar o foco em todos os campos de entrada e impedindo que os campos ganhem foco na inicialização da atividade (tornando o conteúdo visualizado como "coletor de foco"):

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

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

    view.setFocusable(true);
    view.setFocusableInTouchMode(true);
}
fje
fonte
Super está funcionando bem obrigado !! Eu coloquei um voto positivo para a sua resposta.
Vijay
2
Eu acho que é a melhor solução para layouts complexos. Mas eu encontrei duas desvantagens até agora: 1. O menu de contexto EditText não é clicável - qualquer clique nele causa perda de foco do EditText 2. Quando nosso EditText está na parte inferior de uma visualização e clicamos por muito tempo (para selecionar a palavra), depois o teclado mostrado como nosso "ponto de clique" está no teclado, não no EditText - então, perdemos o foco novamente: /
sosite
@sosite, acho que resolvi essas limitações na minha resposta; dê uma olhada.
Andy Dennie 24/09
6

Gostei da abordagem de chamada dispatchTouchEventfeita pelo htafoya, mas:

  • Não entendi a parte do temporizador (não sei por que medir o tempo de inatividade deve ser necessário?)
  • Não gosto de registrar / cancelar o registro de todos os EditTexts a cada alteração de exibição (pode haver muitas alterações de exibição e edições em hierarquias complexas)

Então, eu fiz essa solução um pouco mais 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());
}

Há uma desvantagem:

Mudar de um EditTextpara outro EditTextfaz com que o teclado oculte e reapresente - no meu caso, é desejado dessa maneira, porque mostra que você alternou entre dois componentes de entrada.

Christian R.
fonte
Esse método funcionou melhor em termos de capacidade de plug-and-play com meu FragmentActivity.
BillyRayCyrus
Obrigado, é o melhor caminho! Também adicionei a verificação da ação do evento: int action = ev.getActionMasked (); if (action == MotionEvent.ACTION_DOWN || ação == MotionEvent.ACTION_POINTER_DOWN) {...}
sergey.n
Obrigado ! Você economizou meu tempo ... Melhor resposta.
I.d007
6

Fundamento: Reconheço que não tenho influência, mas leve a minha resposta a sério.

Problema: ignore o teclado virtual ao clicar fora do teclado ou edite o texto com o mínimo de código.

Solução: Biblioteca externa conhecida como Butterknife.

Solução de uma linha:

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

Solução mais legível:

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

Explicação: Vincule o OnClick Listener ao ID pai do Layout XML da atividade, para que qualquer clique no layout (não no texto ou teclado de edição) execute esse snippet de código que ocultará o teclado.

Exemplo: se o seu arquivo de layout for R.layout.my_layout e seu ID de layout for R.id.my_layout_id, sua chamada de ligação do Butterknife deverá se parecer com:

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

Link da documentação do Butterknife: http://jakewharton.github.io/butterknife/

Plug: Butterknife irá revolucionar o seu desenvolvimento Android. Considere isso.

Nota: O mesmo resultado pode ser alcançado sem o uso da biblioteca externa Butterknife. Basta definir um OnClickListener para o layout pai, conforme descrito acima.

Charles Woodson
fonte
Impressionante, solução perfeita! Obrigado.
Nullforlife
6

No kotlin, podemos fazer o seguinte. Não há necessidade de iterar todas as visualizações. Também funcionará 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
fonte
não funciona a partir de um fragmento
Nurseyit Tursunkulov 12/12/19
isso funciona, mas tem um bug. por exemplo, se eu quiser colar texto na visualização de texto, o teclado oculta e aparece. É um pouco chato.
George Shalvashvili 23/01
Então me diga a melhor solução ..
Sai
4

Aqui está outra variação da resposta do fje que aborda as questões levantadas pelo sosite.

A idéia aqui é lidar com as ações down e up no dispatchTouchEventmétodo da Activity . Na ação descendente, anotamos a visualização atualmente focada (se houver) e se o toque estava dentro dela, salvando essas informações para mais tarde.

Na ação inicial, despachamos primeiro, para permitir que outra visualização potencialmente se concentre. Se depois disso, a visão atualmente focada for a visão originalmente focada e o toque para baixo estiver dentro dessa visão, deixaremos o teclado aberto.

Se a visualização atualmente focada for diferente da visualização originalmente focada e for uma EditText, também deixaremos o teclado aberto.

Caso contrário, fechamos.

Então, para resumir, isso funciona da seguinte maneira:

  • ao tocar dentro de um foco atualmente EditText, o teclado permanece aberto
  • ao passar de um foco EditTextpara outro EditText, o teclado permanece aberto (não fecha / reabre)
  • ao tocar em qualquer lugar fora de um foco atualmente diferente de EditTextoutro EditText, o teclado fecha
  • ao pressionar longamente um EditTextpara exibir a barra de ação contextual (com os botões de recortar / copiar / colar), o teclado permanece aberto, mesmo que a ação UP tenha ocorrido fora do foco EditText(que foi movido para baixo para dar espaço ao CAB) . Observe, porém, que quando você pressiona um botão no CAB, ele fecha o teclado. Isso pode ou não ser desejável; se você deseja cortar / copiar de um campo e colar em outro, seria. Se você deseja colar novamente no mesmo EditText, não seria.
  • quando o foco EditTextestá na parte inferior da tela e você clica prolongadamente em algum texto para selecioná-lo, ele EditTextmantém o foco e, portanto, o teclado abre como você deseja, porque fazemos a verificação "toque está dentro dos limites da vista" na ação para baixo , não a ação up.

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

é muito simples, basta tornar seu layout recente clicável um foco por este código:

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

e, em seguida, escreva um método e um OnClickListner para esse layout, para que quando o layout mais alto for tocado em qualquer lugar em que ele chamará um método no qual você escreverá código para descartar o teclado. a seguir está o código para ambos; // você precisa escrever isso em OnCreate ()

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

método chamado do listner: -

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

Acho que o bit de resposta aceito é complexo para esse requisito simples. Aqui está o que funcionou para mim sem nenhuma falha.

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
fonte
11
Se você usa NestedScrollViewlayouts complexos, consulte a resposta aceita: stackoverflow.com/a/11656129/2914140 . Você deve saber que outros contêineres podem consumir toques.
CoolMind 04/02/19
3

Existe uma abordagem mais simples, baseada no mesmo problema do iPhone. Basta substituir o layout do plano de fundo no evento de toque, onde o texto de edição está contido. Basta usar este código no OnCreate da atividade (login_fondo é o layout raiz):

    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
fonte
11
Como eu disse e lembro, isso só funciona se o formulário não estiver dentro de um ScrollView.
Htafoya
11
Isso não funciona muito bem se o layout de segundo plano contiver outros layouts filhos.
AxeEffect
Obrigado por lembrar que onClick não era a única opção.
Harpreet
3

Método para mostrar / ocultar o 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 tenham sido úteis

lalosoft
fonte
3

Esta é a solução mais fácil para mim (e elaborada por mim).

Este é o método para ocultar o teclado.

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

agora defina o atributo onclick do layout pai da atividade como o método acima hideKeyboard, na visualização Design do seu arquivo XML ou escrevendo o código abaixo na visualização Texto do seu arquivo XML.

android:onClick="hideKeyboard"
NullByte08
fonte
2

Refinei o método, coloque o código a seguir em alguma classe de utilitário da interface do usuário (de preferência, não necessariamente) para que ele possa ser acessado de todas as suas classes Activity ou Fragment para servir a seu 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);
}

Então diga, por exemplo, que você precisa chamá-lo de atividade, chame-o da seguinte maneira;

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

Aviso prévio

findViewById (android.R.id.content)

Isso nos fornece a visualização raiz do grupo atual (você não deve ter definido o ID na visualização raiz).

Felicidades :)

Uzair
fonte
2

Atividade

 @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);
 }
icebail
fonte
3
Esse código é simples, mas tem um problema óbvio: fecha o teclado quando qualquer lugar é tocado. Ou seja, se você tocar em um local diferente do EditText para mover o cursor de entrada, ele oculta o teclado e o teclado aparece novamente pelo sistema.
Malditos legumes
2

Basta adicionar este código na classe @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
fonte
3
Embora isso possa responder à pergunta, é melhor explicar as partes essenciais da resposta e, possivelmente, qual foi o problema com o código do OP.
Pirho
sim @pirho Eu também concordo com você Haseeb precisa se concentrar em dar uma resposta adequada.
Dilip
2
@Dilip Você sabia que também pode votar com antecedência? Isso é apenas para manter a seção de comentários limpa, para que não haja muitos comentários com o mesmo argumento.
Pirho
2

Em vez de iterar por todas as visualizações ou substituir o dispatchTouchEvent.

Por que não apenas substituir o onUserInteraction () da Atividade, isso garantirá que o teclado seja descartado sempre que o usuário tocar fora do EditText.

Funcionará mesmo quando o EditText estiver dentro do scrollView.

@Override
public void onUserInteraction() {
    if (getCurrentFocus() != null) {
        InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
    }
}
SKG
fonte
esta é a melhor resposta
ovluca
Sim! Essa é uma resposta super limpa. E, se você criar uma AbstractActivity que todas as suas outras atividades estendem, poderá incorporá-la como o comportamento padrão em todo o aplicativo.
wildcat12
1

Consegui trabalhar com uma ligeira variante da solução de Fernando Camarago. No meu método onCreate, anexo um único onTouchListener à visualização raiz, mas a envio, em vez da atividade, como argumento.

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

Em uma classe Utils separada é ...

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

Isso pode ser antigo, mas eu consegui isso implementando uma classe 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);
}

a melhor prática aqui é criar uma classe Helper e todos os layouts relativos / lineares de contêineres devem implementar isso.

**** Observe que apenas o contêiner principal deve implementar esta classe (para otimização) ****

e implementá-lo assim:

Parent.setOnClickListener( new DismissKeyboardListener(this) ); 

a palavra-chave é para Atividade. então, se você estiver no fragmento, use como getActivity ();

--- polegar para cima, se isso ajudá-lo ... --- aplaude Ralph ---

ralphgabb
fonte
1

Esta é uma versão ligeiramente modificada da resposta de fje que funcionou perfeitamente na maioria das vezes.

Esta versão usa ACTION_DOWN, portanto, executar uma ação de rolagem também fecha o teclado. Também não propaga o evento, a menos que você clique em outro EditText. Isso significa que clicar em qualquer lugar fora do seu EditText, mesmo em outro clicável, simplesmente fecha o 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
fonte
Algo não parece bem aqui; você está atribuindo ambos viewe viewTmppara getCurrentFocus(), para que eles sempre tenham o mesmo valor.
Andy Dennie 24/09
1

Eu fiz assim:

@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 do teclado :

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

Feito

Hiren Patel
fonte