Intercetta il pulsante Indietro dalla tastiera virtuale


84

Ho l'attività con diversi campi di input. All'avvio dell'attività viene visualizzata la tastiera virtuale. Quando si preme il pulsante Indietro, la tastiera virtuale si chiude e per chiudere l'attività è necessario premere il pulsante Indietro ancora una volta.

Quindi la domanda: è possibile intercettare il pulsante Indietro per chiudere la tastiera virtuale e terminare l'attività con una sola pressione del pulsante Indietro senza creare personalizzazioni InputMethodService?

PS So come intercettare il tasto Indietro in altri casi: onKeyDown()oppure onBackPressed()ma in questo caso non funziona: viene intercettata solo la seconda pressione del tasto Indietro.

Risposte:


78

Sì, è completamente possibile mostrare e nascondere la tastiera e intercettare le chiamate al pulsante Indietro. È un piccolo sforzo in più poiché è stato detto che non esiste un modo diretto per farlo nell'API. La chiave è sovrascrivere boolean dispatchKeyEventPreIme(KeyEvent)all'interno di un layout. Quello che facciamo è creare il nostro layout. Ho scelto RelativeLayout poiché era la base della mia attività.

<?xml version="1.0" encoding="utf-8"?>
<com.michaelhradek.superapp.utilities.SearchLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res/com.michaelhradek.superapp"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@color/white">

All'interno della nostra attività impostiamo i nostri campi di input e chiamiamo la setActivity(...)funzione.

private void initInputField() {
    mInputField = (EditText) findViewById(R.id.searchInput);        

    InputMethodManager imm = 
        (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); 
    imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 
            InputMethodManager.HIDE_IMPLICIT_ONLY);

    mInputField.setOnEditorActionListener(new OnEditorActionListener() {

        @Override
        public boolean onEditorAction(TextView v, int actionId,
                KeyEvent event) {
            if (actionId == EditorInfo.IME_ACTION_SEARCH) {
                performSearch();
                return true;
            }

            return false;
        }
    });

    // Let the layout know we are going to be overriding the back button
    SearchLayout.setSearchActivity(this);
}

Ovviamente, la initInputField()funzione imposta il campo di input. Abilita anche il tasto Invio per eseguire la funzionalità (nel mio caso una ricerca).

@Override
public void onBackPressed() {
    // It's expensive, if running turn it off.
    DataHelper.cancelSearch();
    hideKeyboard();
    super.onBackPressed();
}

Quindi, quando onBackPressed()viene chiamato all'interno del nostro layout, possiamo fare quello che vogliamo come nascondere la tastiera:

private void hideKeyboard() {
    InputMethodManager imm = (InputMethodManager) 
        getSystemService(Context.INPUT_METHOD_SERVICE);
    imm.hideSoftInputFromWindow(mInputField.getWindowToken(), 0);
}

Ad ogni modo, ecco il mio override di RelativeLayout.

package com.michaelhradek.superapp.utilities;

import android.app.Activity;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.widget.RelativeLayout;

/**
 * The root element in the search bar layout. This is a custom view just to 
 * override the handling of the back button.
 * 
 */
public class SearchLayout extends RelativeLayout {

    private static final String TAG = "SearchLayout";

    private static Activity mSearchActivity;;

    public SearchLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public SearchLayout(Context context) {
        super(context);
    }

    public static void setSearchActivity(Activity searchActivity) {
        mSearchActivity = searchActivity;
    }

    /**
     * Overrides the handling of the back key to move back to the 
     * previous sources or dismiss the search dialog, instead of 
     * dismissing the input method.
     */
    @Override
    public boolean dispatchKeyEventPreIme(KeyEvent event) {
        Log.d(TAG, "dispatchKeyEventPreIme(" + event + ")");
        if (mSearchActivity != null && 
                    event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
            KeyEvent.DispatcherState state = getKeyDispatcherState();
            if (state != null) {
                if (event.getAction() == KeyEvent.ACTION_DOWN
                        && event.getRepeatCount() == 0) {
                    state.startTracking(event, this);
                    return true;
                } else if (event.getAction() == KeyEvent.ACTION_UP
                        && !event.isCanceled() && state.isTracking(event)) {
                    mSearchActivity.onBackPressed();
                    return true;
                }
            }
        }

        return super.dispatchKeyEventPreIme(event);
    }
}

Purtroppo non posso prendermi tutto il merito. Se controlli la fonte Android per la casella di dialogo di ricerca rapida vedrai da dove è nata l'idea.


2
Funzionava molto bene e non era troppo difficile da implementare (nonostante sembrasse un po 'spaventoso).
Cesar

Grazie mille per questo, vorrei solo avere un modo per far funzionare qualcosa del genere con le versioni precedenti di Android.
zidarsk8

3
Il codice può essere semplificato semplicemente trasmettendo getContext()a Activity. A condizione che il contesto del layout sia ovviamente l'attività in questione. Ma non so se non potrebbe essere.
mxcl

3
static mSearchActivity - che ha un odore sbagliato a causa di un problema. E se ne hai due nel layout? Utilizza invece una variabile non statica.
Pointer Null

Funziona come un fascino. Ma penso che sia meglio usare l'interfaccia statica locale e impostarne l'implementazione dall'interno dell'attività invece di utilizzare l'attività statica ...
SpiralDev

81

onKeyDown () e onBackPressed () non funzionano in questo caso. Devi usare onKeyPreIme .

Inizialmente, devi creare un testo di modifica personalizzato che estenda EditText. E poi devi implementare il metodo onKeyPreIme che controlla KeyEvent.KEYCODE_BACK . Dopodiché, premi abbastanza per risolvere il tuo problema. Questa soluzione funziona perfettamente per me.

CustomEditText.java

public class CustomEditText extends EditText {

    public CustomEditText(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            // User has pressed Back key. So hide the keyboard
            InputMethodManager mgr = (InputMethodManager)         

           getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
            mgr.hideSoftInputFromWindow(this.getWindowToken(), 0);
            // TODO: Hide your view as you do it in your activity
        }
        return false;
}

Nel tuo XML

<com.YOURAPP.CustomEditText
     android:id="@+id/CEditText"
     android:layout_height="wrap_content"
     android:layout_width="match_parent"/> 

Nella tua attività

public class MainActivity extends Activity {
   private CustomEditText editText;

   @Override
   public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      editText = (CustomEditText) findViewById(R.id.CEditText);
   }
}

4
Adoro questa risposta così tanto. Usandolo anche con un frammento (all'interno di una barra di ricerca che non viene nemmeno visualizzata sempre) e suggerendo di sovrascrivere editText, questo incapsula facilmente questo comportamento solo quando è necessario
Jawad

2
Nota per tutti: l'aggiunta di un'interfaccia rapida rende la vita molto migliore, ed è fortunatamente abbastanza facile da fare
Jawad

come chiamare un metodo in onKeyPreIme () che è in mainActivity.java?
Abhigyan

Non è necessario chiamare onKeyPreIme in attività. Se utilizzi CustomEditText nella tua attività, il metodo viene attivato quando premi un pulsante qualsiasi sulla tastiera.
Alican Temel

1
Penso che questa sia la risposta migliore, perché è molto più semplice da implementare. infatti devi solo copiare la classe "CustomEditText" (e rinominare il tuo edittext in XML) e fare quello che vuoi nell'area // TODO. E aggiungi semplicemente "return true" nell'area TODO se non vuoi chiudere la tua attività. Grazie !
Chrysotribax

10

Ho scoperto che anche l'override del metodo dispatchKeyEventPreIme della classe Layout funziona bene. Basta impostare la tua attività principale come un attributo e avviare un metodo predefinito.

public class LinearLayoutGradient extends LinearLayout {
    MainActivity a;

    public void setMainActivity(MainActivity a) {
        this.a = a;
    }

    @Override
    public boolean dispatchKeyEventPreIme(KeyEvent event) {
        if (a != null) {
            InputMethodManager imm = (InputMethodManager) a
                .getSystemService(Context.INPUT_METHOD_SERVICE);

            if (imm.isActive() && event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
                a.launchMethod;
            }
        }

        return super.dispatchKeyEventPreIme(event);
    }
}

puoi mostrarmi il tuo xml per questo layout? Sto cercando di controllare questo approccio e ho una vista superficiale nel mio layout personalizzato, ma non sta intercettando nulla
ZZZ

Fondamentalmente la visualizzazione personalizzata è l'elemento circostante in xml. <?xml version="1.0" encoding="utf-8"?> <view class="package.LinearLayoutGradient"...
Kirill Rakhman

funziona anche se estendiamo semplicemente una singola vista e sovrascriviamo il suo dispatchKeyEventPreIme (). Non è necessario estendere il layout stesso.
faizal

7

Ho avuto successo ignorando dispatchKeyEvent :

@Override
public boolean dispatchKeyEvent(KeyEvent event) {
    if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
        finish();
        return true;
    }
    return super.dispatchKeyEvent(event);
}

Nasconde la tastiera e termina l'attività.


questo sembra funzionare solo su alcuni dispositivi come HTC Desire.
Abhijit

5
non funziona su Samsung Note 2. Il dispatchKeyEvent () non viene chiamato quando la tastiera virtuale è attiva.
faizal

Bene, l'ho provato con l'emulatore Nexus 6 (Lollipop 5.0) -> ha funzionato e Note 4 (CM12 Lollipop 5.0.2) -> ha funzionato non così, non proprio la soluzione migliore - ma comunque .. Grazie :)
Martin Pfeffer

Il clic del pulsante viene consumato quando si chiude la tastiera
slott

4

Come stai mostrando la tastiera morbida?

Se stai usando InputMethodManager.showSoftInput(), puoi provare a passare a ResultReceivere implementare onReceiveResult()per gestireRESULT_HIDDEN

http://developer.android.com/reference/android/view/inputmethod/InputMethodManager.html


Suona bene, ma showSoftInput()non mostra la tastiera sul telefono e sull'emulatore, quindi usotoggleSoftInput()
Sergey Glotov

1
Non funziona per me. onReceiveResult () viene chiamato con RESULT_SHOWN, ma mai più tardi con RESULT_HIDDEN
Yulia Rogovaya

2

Ho avuto lo stesso problema ma l'ho aggirato intercettando la pressione del tasto Indietro. Nel mio caso (HTC Desire, Android 2.2, Application API Level 4) chiude la tastiera e termina immediatamente l'attività. Non so perché questo non dovrebbe funzionare anche per te:

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_BACK) {
        return true;
    }
    return super.onKeyDown(keyCode, event);
}

@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_BACK) {
        onBackPressed();
        return true;
    }
    return super.onKeyUp(keyCode, event);
}

/**
 * Called when the activity has detected the user's press of the back key
 */
private void onBackPressed() {
    Log.e(TAG, "back pressed");
    finish();
}

questo non funziona su samsung galaxy note 2. onkeydown () o onbackpressed () non vengono attivati ​​quando la tastiera
virtuale

1

Usa il onKeyPreIme(int keyCode, KeyEvent event)metodo e controlla l' KeyEvent.KEYCODE_BACKevento. È molto semplice senza fare alcuna codifica di fantasia.


0

Prova questo codice nella tua implementazione BackPressed ( pulsante Block Back in Android ):

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

Ti suggerisco di dare un'occhiata a @ Chiudi / nascondi la Soft Keyboard di Android


1
L'ho già provato, ma sono ancora necessarie due pressioni sul pulsante Indietro. A quanto ho capito, il primo clic viene intercettato dalla tastiera morbida, quindi onBackPressed()non funziona. E solo dopo che il programma della seconda pressa cade nel file onBackPressed().
Sergey Glotov

@Sergey Glotov Ho provato molti suggerimenti per il problema e alla fine sono arrivato con una soluzione non così buona in cui devo implementare il proprio SoftKeyBoard, ma spero che la comunità Android esca presto con una soluzione molto migliore.
100rabh

0

la mia versione della soluzione @mhradek:

disposizione

class BazingaLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
    ConstraintLayout(context, attrs, defStyleAttr) {

var activity: Activity? = null

override fun dispatchKeyEventPreIme(event: KeyEvent): Boolean {
    activity?.let {
        if (event.keyCode == KeyEvent.KEYCODE_BACK) {
            val state = keyDispatcherState
            if (state != null) {
                if (event.action == KeyEvent.ACTION_DOWN
                    && event.repeatCount == 0) {
                    state.startTracking(event, this)
                    return true
                } else if (event.action == KeyEvent.ACTION_UP && !event.isCanceled && state.isTracking(event)) {
                    it.onBackPressed()
                    return true
                }
            }
        }
    }
    return super.dispatchKeyEventPreIme(event)
}

}

xml file

<com... BazingaLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/grey">
 </com... BazingaLayout>

Frammento

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        (view as BazingaLayout).activity = activity
        super.onViewCreated(view, savedInstanceState)
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.