Wykrywanie, kiedy użytkownik odrzucił klawiaturę programową

114

W widoku mam widżet EditText. Gdy użytkownik wybierze widżet EditText, wyświetlam kilka instrukcji i pojawia się klawiatura programowa.

Używam OnEditorActionListener, aby wykryć, kiedy użytkownik zakończył wprowadzanie tekstu, i zwalniam klawiaturę, ukrywam instrukcje i wykonuję jakąś akcję.

Mój problem polega na tym, że użytkownik odrzuca klawiaturę, naciskając klawisz BACK. System operacyjny odrzuca klawiaturę, ale moje instrukcje (które muszę ukryć) są nadal widoczne.

Próbowałem zastąpić OnKeyDown, ale to nie wydaje się być wywoływane, gdy przycisk WSTECZ jest używany do zwolnienia klawiatury.

Próbowałem ustawić OnKeyListener na widżecie EditText, ale to też nie wydaje się być wywoływane.

Jak mogę wykryć, że klawiatura programowa jest odrzucana?

deSelby
źródło

Odpowiedzi:

160

Wiem, jak to zrobić. Podklasuj EditText i zaimplementuj:

@Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
  if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
    // Do your thing.
    return true;  // So it is not propagated.
  }
  return super.dispatchKeyEvent(event);
}

Oto link do sposobu korzystania z niestandardowych widoków (w przypadku podklasy EditText): http://developer.android.com/guide/topics/ui/custom-components.html

Sójka
źródło
2
Otrzymuję raporty od użytkowników Androida z klawiaturami sprzętowymi, które w jakiś sposób zakłócają naciskanie klawiszy. W tej chwili nie mam żadnych dodatkowych informacji.
esilver
Szukałem kilku rozwiązań, to zdecydowanie najlepsze!
Friesgaard
11
Poczekaj, zaczekaj, właśnie spojrzałem na to trzeci raz - czy nie powinno być super wezwanie onKeyPreIme? A może jest jakiś szczególny powód, dla którego tak nie jest?
Erhannis
Wygląda na przydatne, z wyjątkiem sytuacji, gdy EditText nie może być podklasą (np. W SearchView). Jest to problem podczas próby ukrycia widoku wyszukiwania, jeśli jest pusty, gdy klawiatura jest odrzucana. Muszę się zastanawiać, dlaczego ludzie z Androidem nie zapewniają tylko fajnych interfejsów API OSK do tego rodzaju rzeczy.
tbm
2
@tbm Aby osiągnąć podobny efekt w SearchView, odwiedź stackoverflow.com/questions/9629313/…
Cheok Yan Cheng
124

Jay, twoje rozwiązanie jest dobre! dzięki :)

public class EditTextBackEvent extends EditText {

    private EditTextImeBackListener mOnImeBack;

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

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

    public EditTextBackEvent(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
        if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && 
            event.getAction() == KeyEvent.ACTION_UP) {
            if (mOnImeBack != null) 
                mOnImeBack.onImeBack(this, this.getText().toString());
        }
        return super.dispatchKeyEvent(event);
    }

    public void setOnEditTextImeBackListener(EditTextImeBackListener listener) {
        mOnImeBack = listener;
    }

}

public interface EditTextImeBackListener {
    public abstract void onImeBack(EditTextBackEvent ctrl, String text);
}
olivier_sdg
źródło
Czy jest jakiś szczególny powód, który również chcemy wykryć KeyEvent.ACTION_UP?
Cheok Yan Cheng
2
@CheokYanCheng dzieje się tak, ponieważ działanie użytkownika powinno normalnie działać po zwolnieniu przycisku, a nie po rozpoczęciu jego naciskania.
jayeffkay
3
Pamiętaj, aby przedłużyć android.support.v7.widget.AppCompatEditTextdo barwienia.
Sanvywell
Przedłużenie: AppCompatEditTextdla androidx
COYG
Wspaniały! Sugerowałbym tylko ulepszenie tylko po to, aby uogólnić Twoje rozwiązanie. Przekazałbym argumenty z onKeyPreIme "tak jak jest" do słuchacza, w ten sposób możesz zaimplementować swoją logikę na różne sposoby, gdzie potrzebujesz.
marcRDZ
17

Dokonałem niewielkiej zmiany w rozwiązaniu Jaya, wywołując super.onKeyPreIme ():

_e = new EditText(inflater.getContext()) {
@Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
    if (event.getKeyCode() == KeyEvent.KEYCODE_BACK){
            cancelTextInput();
        }
        return super.onKeyPreIme(keyCode, event);
    }
};

Wspaniałe rozwiązanie, Jay, +1!

Steelight
źródło
14

Oto mój niestandardowy tekst edycji do wykrywania, czy klawiatura jest wyświetlana, czy nie

/**
 * Created by TheFinestArtist on 9/24/15.
 */
public class KeyboardEditText extends EditText {

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

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

    public KeyboardEditText(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
        super.onFocusChanged(focused, direction, previouslyFocusedRect);
        if (listener != null)
            listener.onStateChanged(this, true);
    }

    @Override
    public boolean onKeyPreIme(int keyCode, @NonNull KeyEvent event) {
        if (event.getKeyCode() == KeyEvent.KEYCODE_BACK
                && event.getAction() == KeyEvent.ACTION_UP) {
            if (listener != null)
                listener.onStateChanged(this, false);
        }
        return super.onKeyPreIme(keyCode, event);
    }

    /**
     * Keyboard Listener
     */
    KeyboardListener listener;

    public void setOnKeyboardListener(KeyboardListener listener) {
        this.listener = listener;
    }

    public interface KeyboardListener {
        void onStateChanged(KeyboardEditText keyboardEditText, boolean showing);
    }
}
Najlepszy artysta
źródło
8

Teraz jest rok 2019 ...
Więc stworzyłem zgrabniejsze rozwiązanie z Kotlinem

1. utwórz funkcję rozszerzenia:

fun Activity.addKeyboardToggleListener(onKeyboardToggleAction: (shown: Boolean) -> Unit): KeyboardToggleListener? {
    val root = findViewById<View>(android.R.id.content)
    val listener = KeyboardToggleListener(root, onKeyboardToggleAction)
    return root?.viewTreeObserver?.run {
        addOnGlobalLayoutListener(listener)
        listener
    }
}

2.Gdzie odbiornik przełączania:

open class KeyboardToggleListener(
        private val root: View?,
        private val onKeyboardToggleAction: (shown: Boolean) -> Unit
) : ViewTreeObserver.OnGlobalLayoutListener {
    private var shown = false
    override fun onGlobalLayout() {
        root?.run {
            val heightDiff = rootView.height - height
            val keyboardShown = heightDiff > dpToPx(200f)
            if (shown != keyboardShown) {
                onKeyboardToggleAction.invoke(keyboardShown)
                shown = keyboardShown
            }
        }
    }
}

fun View.dpToPx(dp: Float) = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, resources.displayMetrics).roundToInt()

3. Użyj go w dowolnej tak prostej aktywności :

addKeyboardToggleListener {shown ->
          // hurray! Now you know when the keyboard is shown and hidden!!
      }
Leo Droidcoder
źródło
Dzięki za rozwiązanie Kotlin. Chociaż kiedy go zaimplementowałem, zauważyłem, że odpala słuchacza wielokrotnie przy jednej zmianie klawiatury, a także przy starcie. Musiałem przechowywać stan otwarty / nie otwarty i wywoływać słuchacze tylko wtedy, gdy wartość była faktycznie inna.
RandomEngy
@RandomEngy naprawił to w KeyboardToggleListener. Dzięki za uwagę
Leo Droidcoder,
4

Po prostu stwórz klasę, która rozszerza Edittext i użyj tego edittext w swoim kodzie, powinieneś po prostu zastąpić następującą metodę w niestandardowym edytorze:

@Override
 public boolean onKeyPreIme(int keyCode, KeyEvent event) {
 if (keyCode == KeyEvent.KEYCODE_BACK) {

    //Here it catch all back keys
    //Now you can do what you want.

} else if (keyCode == KeyEvent.KEYCODE_MENU) {
    // Eat the event
    return true;
}
return false;}
farhad.kargaran
źródło
Czy istnieje sposób na wykrycie otwarcia klawiatury?
proszek 366
3

Oto rozwiązanie z kluczem nasłuchującym. Nie mam pojęcia, dlaczego to działa, ale OnKeyListener działa, jeśli po prostu zastąpisz onKeyPreIme w niestandardowym EditText.

SomeClass.java

customEditText.setOnKeyListener((v, keyCode, event) -> {
            if(event.getAction() == KeyEvent.ACTION_DOWN) {
                switch (keyCode) {
                    case KeyEvent.KEYCODE_BACK:
                        getPresenter().onBackPressed();
                        break;
                }
            }
            return false;
        }); 

CustomEditText.java

@Override
    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
        return super.dispatchKeyEvent(event);
    }
Rubin Yoo
źródło
3

Używając odpowiedzi @ olivier_sdg, ale przekonwertowanej na Kotlin:

class KeyboardEditText : AppCompatEditText {

    var listener: Listener? = null
  
    constructor(context: Context) : super(context)
    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
  
    override fun onKeyPreIme(keyCode: Int, event: KeyEvent): Boolean {
        if (event.keyCode == KeyEvent.KEYCODE_BACK && event.action == KeyEvent.ACTION_UP) {
            listener?.onImeBack(this)
        }
        return super.dispatchKeyEvent(event)
    }
  
    interface Listener {
        fun onImeBack(editText: KeyboardEditText)
    }

}

Stosowanie:

keyboardEditText.listener = object : KeyboardEditText.Listener {
    override fun onImeBack(editText: KeyboardEditText) {
        //Back detected
    }
}
bmjohns
źródło
2

Dla każdego, kto chce zrobić to samo w Xamarinie, przetłumaczyłem niektóre z najważniejszych odpowiedzi, ponieważ są one nieco inne. Stworzyłem tutaj sedno , ale podsumowując, tworzysz niestandardowy EditText i nadpisujesz w następujący OnKeyPreImesposób:

public class CustomEditText : EditText
{
    public event EventHandler BackPressed;

    // ...

    public override bool OnKeyPreIme([GeneratedEnum] Keycode keyCode, KeyEvent e)
    {
        if (e.KeyCode == Keycode.Back && e.Action == KeyEventActions.Up)
        {
            BackPressed?.Invoke(this, new EventArgs());
        }

        return base.OnKeyPreIme(keyCode, e);
    }
}

... a potem w widoku ...

editText = FindViewById<CustomEditText>(Resource.Id.MyEditText);
editText.BackPressed += (s, e) => 
{
    // <insert code here>
};
Steven Evers
źródło
Chociaż to tylko prosty przykład, odradzałbym stosowanie anonimowych metod w programach obsługi zdarzeń, ponieważ powodują one wycieki pamięci, a wiele osób korzysta z przykładów znalezionych tutaj i działa z nimi, nie zdając sobie z tego sprawy. Źródło: docs.microsoft.com/en-us/xamarin/cross-platform/deploy-test/…
Jared
0

hideSoftInputFromWindow zwraca wartość true, gdy klawiatura zamyka się, użyj jej wartości, aby wykryć zamknięcie klawiatury w systemie Android

InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);

        if (imm.hideSoftInputFromWindow(findFocus().getWindowToken(),
                InputMethodManager.HIDE_NOT_ALWAYS)) {
            //keyboard is closed now do what you need here
        }
Bali
źródło