Przycisk przechwytywania wstecz z miękkiej klawiatury

84

Mam aktywność z kilkoma polami wejściowymi. Po rozpoczęciu czynności wyświetlana jest klawiatura programowa. Po naciśnięciu przycisku Wstecz miękka klawiatura zamyka się i aby zamknąć czynność, muszę jeszcze raz nacisnąć przycisk Wstecz.

A więc pytanie: czy można przechwycić przycisk Wstecz, aby zamknąć miękką klawiaturę i zakończyć działanie jednym naciśnięciem przycisku Wstecz bez tworzenia niestandardowego InputMethodService?

PS Wiem, jak przechwycić przycisk powrotu w innych przypadkach: onKeyDown()lub onBackPressed()ale to nie działa w tym przypadku: przechwytywane jest tylko drugie naciśnięcie przycisku wstecz.

Sergey Glotov
źródło

Odpowiedzi:

76

Tak, jest całkowicie możliwe, aby pokazać i ukryć klawiaturę oraz przechwycić połączenia do przycisku Wstecz. Jest to trochę dodatkowy wysiłek, ponieważ zostało wspomniane, że nie ma bezpośredniego sposobu, aby to zrobić w API. Kluczem jest zastąpienie boolean dispatchKeyEventPreIme(KeyEvent)w układzie. Tworzymy nasz układ. Wybrałem RelativeLayout, ponieważ był to podstawa mojej aktywności.

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

W ramach naszej aktywności konfigurujemy nasze pola wejściowe i wywołujemy setActivity(...)funkcję.

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

Oczywiście initInputField()funkcja ustawia pole wejściowe. Umożliwia również klawisz Enter do wykonania funkcji (w moim przypadku wyszukiwania).

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

Więc kiedy onBackPressed()wywoływany jest w naszym układzie, możemy zrobić, co chcemy, na przykład ukryć klawiaturę:

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

W każdym razie tutaj jest moje zastąpienie 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);
    }
}

Niestety nie mogę przypisać sobie całej zasługi. Jeśli zaznaczysz źródło Androida w oknie szybkiego wyszukiwania w oknie dialogowym , zobaczysz, skąd wziął się pomysł.

mhradek
źródło
2
To działało bardzo dobrze i nie było zbyt trudne do wdrożenia (mimo że wyglądało trochę przerażająco).
Cesar
Bardzo dziękuję za to, chciałbym tylko mieć możliwość uzyskania czegoś takiego do pracy ze starszymi wersjami Androida.
zidarsk8
3
Kod można uprościć, po prostu rzutując getContext()do Activity. Oczywiście pod warunkiem, że kontekst układu to przedmiotowa czynność. Ale nie wiem, czy to niemożliwe.
mxcl
3
statyczny mSearchActivity - to źle pachnie problemem. Co jeśli masz dwa z nich w układzie? Zamiast tego użyj zmiennej niestatycznej.
Pointer Null
Działa jak marzenie. Ale myślę, że lepiej jest użyć lokalnego statycznego interfejsu i ustawić jego implementację z wewnętrznej aktywności, zamiast używać statycznej aktywności ...
SpiralDev
80

onKeyDown () i onBackPressed () nie działają w tym przypadku. Musisz użyć onKeyPreIme .

Początkowo musisz utworzyć własny tekst do edycji, który rozszerza EditText. Następnie musisz zaimplementować metodę onKeyPreIme, która kontroluje KeyEvent.KEYCODE_BACK . Następnie wystarczy jedno naciśnięcie wstecz, aby rozwiązać problem. To rozwiązanie na mnie działa idealnie.

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

W Twoim XML

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

W Twojej aktywności

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);
   }
}
Alican Temel
źródło
4
Tak bardzo podoba mi się ta odpowiedź. Używając tego również z fragmentem (w pasku wyszukiwania, który nawet nie pojawia się zawsze) i sugerując nadpisanie editText, łatwo ujmuje to zachowanie tylko wtedy, gdy jest to konieczne
Jawad
2
Uwaga dla wszystkich: dodanie do tego szybkiego interfejsu sprawia, że ​​życie jest o wiele lepsze i na szczęście jest to całkiem łatwe
Jawad
jak wywołać metodę w onKeyPreIme (), która znajduje się w mainActivity.java?
Abhigyan
Nie musisz dzwonić do onKeyPreIme podczas aktywności. Jeśli używasz CustomEditText w swoim działaniu, metoda jest uruchamiana po naciśnięciu dowolnego przycisku na klawiaturze.
Alican Temel
1
Myślę, że to najlepsza odpowiedź, ponieważ jest znacznie prostsza do wdrożenia. w rzeczywistości wystarczy skopiować klasę „CustomEditText” (i zmienić nazwę pliku edittext w plikach XML) i zrobić to, co chcesz, w obszarze // TODO. I po prostu dodaj „return true” w obszarze TODO, jeśli nie chcesz kończyć swojej aktywności. Dzięki !
Chrysotribax
10

Dowiedziałem się, że nadpisanie metody dispatchKeyEventPreIme klasy Layout również działa dobrze. Po prostu ustaw swoje główne działanie jako atrybut i uruchom predefiniowaną metodę.

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);
    }
}
Kirill Rakhman
źródło
czy możesz pokazać mi swój xml dla tego układu? Próbuję sprawdzić to podejście i mam widok powierzchni w moim układzie klienta, ale niczego nie przechwytuje
ZZZ
W zasadzie widok niestandardowy to otaczający element w pliku XML. <?xml version="1.0" encoding="utf-8"?> <view class="package.LinearLayoutGradient"...
Kirill Rakhman
działa również, jeśli po prostu rozszerzymy pojedynczy widok i nadpisujemy jego metodę dispatchKeyEventPreIme (). Nie jest wymagane rozszerzenie samego układu.
faizal
7

Odniosłem sukces, zastępując metodę dispatchKeyEvent :

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

Ukrywa klawiaturę i kończy działanie.

jaseelder
źródło
wydaje się, że działa tylko na niektórych urządzeniach, takich jak HTC Desire.
Abhijit
5
nie działa na samsung note 2. Metoda dispatchKeyEvent () nie jest wywoływana, gdy klawiatura programowa jest włączona.
faizal
Cóż, próbowałem tego z emulatorem Nexusa 6 (Lollipop 5.0) -> działało i Note 4 (CM12 Lollipop 5.0.2) -> działało nie tak, nie do końca najlepsze rozwiązanie - ale tak czy inaczej ... Dziękuję :)
Martin Pfeffer
Kliknięcie przycisku jest zużywane podczas zwalniania klawiatury
szczelina
4

Jak pokazujesz miękką klawiaturę?

Jeśli używasz InputMethodManager.showSoftInput(), możesz spróbować przekazać a ResultReceiveri zaimplementować onReceiveResult()do obsługiRESULT_HIDDEN

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

superuser
źródło
Brzmi dobrze, ale showSoftInput()nie pokazuje klawiatury w moim telefonie i emulatorze, więc używamtoggleSoftInput()
Sergey Glotov
1
Nie działa na mnie. onReceiveResult () jest wywoływana z RESULT_SHOWN, ale nigdy później z RESULT_HIDDEN
Yulia Rogovaya
2

Miałem ten sam problem, ale obejść go, przechwytując naciśnięcie klawisza wstecznego. W moim przypadku (HTC Desire, Android 2.2, Application API Level 4) zamyka klawiaturę i natychmiast kończy działanie. Nie wiem, dlaczego to również nie powinno działać dla Ciebie:

@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();
}
whlk
źródło
to nie działa na samsung galaxy note 2. onkeydown () lub onbackpressed () nie są wyzwalane, gdy widoczna jest klawiatura
programowa
1

Użyj onKeyPreIme(int keyCode, KeyEvent event)metody i sprawdź KeyEvent.KEYCODE_BACKwydarzenie. Jest to bardzo proste bez konieczności wymyślnego kodowania.

Umair
źródło
0

Wypróbuj ten kod w swojej implementacji BackPressed ( przycisk Blokuj Wstecz w systemie Android ):

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

Sugeruję, abyś spojrzał @ Zamknij / ukryj klawiaturę Android Soft Keyboard

100rabh
źródło
1
Próbowałem już tego, ale nadal potrzebuję dwóch naciśnięć przycisku Wstecz. Jak rozumiem, pierwsze kliknięcie jest przechwytywane przez miękką klawiaturę, więc onBackPressed()nie działa. I dopiero po drugim tłoczeniu program wpada do onBackPressed().
Sergey Glotov
@Sergey Glotov Wypróbowałem wiele sugestii dotyczących problemu i wreszcie przyszedłem z niezbyt dobrym rozwiązaniem, w którym muszę zaimplementować własny SoftKeyBoard, ale mam nadzieję, że społeczność androidów wkrótce wyjdzie z dużo lepszym rozwiązaniem.
100rabh
0

moja wersja rozwiązania @mhradek:

Układ

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 plik

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

Fragment

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        (view as BazingaLayout).activity = activity
        super.onViewCreated(view, savedInstanceState)
}
tasjapr
źródło