Jak ukryć miękką klawiaturę na Androidzie po kliknięciu poza EditText?


354

Ok wszyscy wiedzą, że aby ukryć klawiaturę, musisz zaimplementować:

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

Ale najważniejsze jest to, jak ukryć klawiaturę, gdy użytkownik dotyka lub wybiera inne miejsce, które nie jest klawiaturą lub klawiaturą EditText?

Próbowałem użyć onTouchEvent()na moim rodzicu, Activityale to działa tylko wtedy, gdy użytkownik dotyka poza jakimkolwiek innym widokiem i nie ma widoku przewijania.

Próbowałem zaimplementować nasłuchiwanie dotykowe, kliknięcie i skupienie bez powodzenia.

Próbowałem nawet wdrożyć własny widok przewijania, aby przechwytywać zdarzenia dotykowe, ale mogę uzyskać tylko współrzędne zdarzenia, a nie kliknięcie widoku.

Czy istnieje standardowy sposób to zrobić? w iPhonie było to naprawdę łatwe.


Cóż, zdałem sobie sprawę, że przewijanie nie było tak naprawdę problemem, ale etykietami, które tam są. Widok jest układem pionowym z czymś takim jak: TextView, EditText, TextView, EditText itp., A textViews nie pozwala edittextowi stracić ostrości i ukryć klawiaturę
htafoya

Możesz znaleźć rozwiązanie getFields()tutaj: stackoverflow.com/questions/7790487/…
Reto

Klawiaturę można zamknąć, naciskając przycisk powrotu, więc powiedziałbym, że wątpliwe, czy jest to warte wysiłku
gerrytan

4
Znalazłem tę odpowiedź: stackoverflow.com/a/28939113/2610855 Najlepsza.
Loenix,

Odpowiedzi:


575

Poniższy fragment kodu po prostu ukrywa klawiaturę:

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

Możesz umieścić to w klasie użyteczności lub, jeśli definiujesz to w ramach działania, unikaj parametru aktywności lub wywołania hideSoftKeyboard(this).

Najtrudniejsze jest, kiedy to nazwać. Możesz napisać metodę, która będzie iterować przez wszystkie Viewdziałania, i sprawdź, czy jest to metoda, instanceof EditTextjeśli nie jest ona zarejestrowana w setOnTouchListenertym komponencie i wszystko się ułoży. Jeśli zastanawiasz się, jak to zrobić, jest to dość proste. Oto, co robisz, piszesz metodę rekurencyjną, taką jak poniżej, w rzeczywistości możesz tego użyć, aby zrobić wszystko, na przykład skonfigurować niestandardowe kroje pisma itp. Oto metoda

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

To wszystko, po prostu wywołaj tę metodę po tobie setContentVieww swojej aktywności. Jeśli zastanawiasz się, jaki parametr chcesz przekazać, jest to idkontener nadrzędny. Przypisz iddo swojego nadrzędnego kontenera jak

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

i zadzwoń setupUI(findViewById(R.id.parent)), to wszystko.

Jeśli chcesz efektywnie z tego korzystać, możesz utworzyć metodę rozszerzoną Activityi wprowadzić tę metodę, a wszystkie inne działania w aplikacji rozszerzyć tę aktywność i wywołać ją setupUI()w onCreate()metodzie.

Mam nadzieję, że to pomoże.

Jeśli używasz więcej niż 1 działania, zdefiniuj wspólny identyfikator układu nadrzędnego, np <RelativeLayout android:id="@+id/main_parent"> ... </RelativeLayout>

Następnie rozszerz klasę Activityi zdefiniuj w setupUI(findViewById(R.id.main_parent))jej obrębie OnResume()i rozszerz tę klasę zamiast `` Aktywnośćin your program


Oto wersja powyższej funkcji Kotlin:

@file:JvmName("KeyboardUtils")

fun Activity.hideSoftKeyboard() {
    currentFocus?.let {
        val inputMethodManager = ContextCompat.getSystemService(this, InputMethodManager::class.java)!!
        inputMethodManager.hideSoftInputFromWindow(it.windowToken, 0)
    }
}

Nie testowałem się, ale wygląda na to, że to zadziała, a ponieważ ma wysokie recenzje, zmienię przyjętą odpowiedź na to.
htafoya

4
Nie powinno być bardzo trudne? W tej chwili nie mam programowania na Androida, więc popraw mnie, jeśli się mylę. W dowolnym momencie możesz w jakiś sposób śledzić skoncentrowany tekst EditText i po prostu poprosić go o utratę ostrości podczas OnTouchEvent?
Navneeth G

25
Nie jestem pewien, czy ktoś napotkał ten problem, ale powoduje to awarię aplikacji po wywołaniu hideSoftKeyboard, jeśli nic nie jest skoncentrowane. Możesz rozwiązać ten problem, otaczając drugą linię metodyif(activity.getCurrentFocus() != null) {...}
Frank Cangialosi,

14
Problem z tym podejściem polega na tym, że zakłada on, że wszystkie inne widoki nigdy nie będą musiały OnTouchListenerdla nich ustawiać . Możesz po prostu ustawić tę logikę w ViewGroup.onInterceptTouchEvent(MotionEvent)widoku głównym.
Alex.F,

2
Nie działa, gdy klikam inne elementy sterujące przy otwartej klawiaturze
mohitum

285

Możesz to osiągnąć, wykonując następujące czynności:

  1. Ustaw widok nadrzędny (widok zawartości Twojej aktywności), na który można kliknąć, i można go ustawić, dodając następujące atrybuty

        android:clickable="true" 
        android:focusableInTouchMode="true" 
  2. Zaimplementuj metodę hideKeyboard ()

        public void hideKeyboard(View view) {
            InputMethodManager inputMethodManager =(InputMethodManager)getSystemService(Activity.INPUT_METHOD_SERVICE);
            inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
        }
  3. Na koniec ustaw onFocusChangeListener swojego tekstu edytującego.

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

Jak wskazano w jednym z poniższych komentarzy, może to nie działać, jeśli widok nadrzędny jest ScrollView. W takim przypadku klikalny i możliwy do ustawienia obiektInTouchMode można dodać w widoku bezpośrednio pod ScrollView.


67
Moim zdaniem jest to poprawna odpowiedź. Mniej kodu, bez zbędnych iteracji ...
gattshjoty

14
Bardzo podoba mi się ta odpowiedź. Należy zauważyć, że nie działało to dla mnie podczas dodawania clickablei focusableInTouchModedo mojego ScrollViewelementu głównego . Musiałem dodać do bezpośredniego rodzica mojego, EditTextktóry był LinearLayout.
Adam Johns

10
Działa idealnie dla mnie. Chociaż, jeśli masz dwa widżety edycyjne, musisz upewnić się, że poprawnie obsługujesz oba przyciski onfocus, w przeciwnym razie niepotrzebnie będziesz przełączał klawiaturę.
Marka A

1
@MarkaA Nie miałem z tym problemu. Po kliknięciu drugiego przycisku EditText klawiatura pozostaje, kliknięcie tła powoduje ukrycie go tak, jak powinno. Miałem trochę innej obsługi w onFocusChange, po prostu zmieniłem tło EditText, gdy jest on skupiony, nic złego.
CularBytes

3
@Shrikant - Widziałem też migotanie przy wielu tekstach do edycji. Po prostu ustawiłem onFocusChangeListener na obiekcie nadrzędnym zamiast na każdym edytowanym tekście i zmieniłem warunek, aby powiedzieć if (hasFocus) {hideKeyboard (v); } Nie zauważyłem już migotania podczas przełączania między edytowaniem tekstów.
manisha

69

Po prostu zastąp kod poniżej w Aktywności

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

4
Proste rozwiązanie, dodaj aktywność, a także zajmie się fragmentem
Rohit Maurya

1
Innym rozwiązaniem jest utworzenie BaseActivity i rozszerzenie go na wszystkie działania
sumit sonawane

1
Genialne rozwiązanie
Ahmed Adel Ismail

1
uratowałeś mnie przed migotaniem ekranu po zamknięciu klawiatury. Kciuki w górę!
Dimas Mendes

1
Fajnie, ale było zbyt piękne, aby mogło być prawdziwe: proste, bardzo krótkie i działało ... niestety jest problem: kiedy klawiatura jest wyświetlana, za każdym razem, gdy dotykamy EditText, który pyta o klawiaturę, idzie w dół i w górę automatycznie.
Chrysotribax

60

Uważam, że zaakceptowana odpowiedź jest nieco skomplikowana.

Oto moje rozwiązanie. Dodaj OnTouchListenerdo głównego układu, tj .:

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

i umieść następujący kod w metodzie onTouch.

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

W ten sposób nie musisz powtarzać wszystkich widoków.


@roepit - otrzymuję classCastexception za próbę rzutowania układu na widok. czy coś mi brakuje?
katzenhut

Czy możesz gdzieś odwołać się do swojego kodu? Nie mogę powiedzieć, co jest nie tak, gdy nie mogę spojrzeć na twój układ i kod / aktywność itp.
roepit

Najlepsza odpowiedź, wciąż próbując owinąć głowę, jak to działa.
user40797

czy to zadziała, jeśli klikniesz pasek tytułowy aplikacji?
user1506104

1
Działa to idealnie do ukrywania klawiatury! Zauważ, że tak naprawdę to nie dezaktywuje EditText, tylko ukrywa klawiaturę. Aby również cofnąć fokus EditText, dodaj np. android:onClick="stealFocusFromEditTexts"Do xml widoku rodzica, a następnie public void stealFocusFromEditTexts(View view) {}do jego aktywności. Metoda „kliknięcie” nie musi nic robić, musi istnieć, aby widok nadrzędny mógł być aktywny / możliwy do wybrania, co jest konieczne do kradzieży fokusu z dziecka EditText
Jacob R

40

Mam jeszcze jedno rozwiązanie, aby ukryć klawiaturę:

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

Tutaj przechodzą HIDE_IMPLICIT_ONLYw pozycji showFlagi 0w pozycji hiddenFlag. Mocno zamknie miękką klawiaturę.


3
Dzięki, że działa ... przede wszystkim próbowałem, ale nie działa, gdy otrzymuję wartość z okna dialogowego editext text i closoing dialog box ...
PankajAndroid

Dzięki, to działa tylko i jest znacznie czystsze niż inne powyżej! +1
Edmond Tamas

Działa zgodnie z oczekiwaniami, dziękuję za rozwiązanie +1
tryp

Dla mnie działa jak urok. +1 za eleganckie rozwiązanie.
saintjab

2
przepraszam, ale ta metoda jest przełączana, więc jeśli stan klawiatury jest już zamknięty, pokaże klawiaturę
HendraWD,

16

Cóż, udaje mi się nieco rozwiązać problem, przesłoniłem dispatchTouchEvent w mojej działalności, tam używam następujących poleceń, aby ukryć klawiaturę.

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

EDYCJA: Metoda getFields () jest po prostu metodą zwracającą tablicę z polami tekstowymi w widoku. Aby uniknąć tworzenia tej tablicy za każdym dotknięciem, stworzyłem tablicę statyczną o nazwie sFields, która jest zwracana przy użyciu metody getFields (). Ta tablica jest inicjowana w metodach onStart (), takich jak:

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


To nie jest idealne, czas zdarzenia przeciągania jest oparty tylko na heurystyce, więc czasami nie ukrywa się podczas wykonywania długich kliknięć, a także zakończyłem, tworząc metodę, aby uzyskać wszystkie editTexts na widok; w przeciwnym razie klawiatura ukryłaby się i pokazała po kliknięciu innego przycisku EditText.

Mimo to mile widziane są czystsze i krótsze rozwiązania


8
Aby pomóc innym w przyszłości, czy zastanowiłbyś się nad edytowaniem kodu w swojej odpowiedzi, aby uwzględnić swoją getFields()metodę? Nie musi to być dokładne, tylko przykład z być może tylko komentarzami wskazującymi, że zwraca tablicę EditTextobiektów.
Squonk

14

Użyj OnFocusChangeListener .

Na przykład:

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

Aktualizacja : możesz również zastąpić onTouchEvent()swoją aktywność i sprawdzić współrzędne dotyku. Jeśli współrzędne znajdują się poza EditText, ukryj klawiaturę.


9
Problem polega na tym, że tekst edytujący nie traci ostrości po kliknięciu etykiety lub innych widoków, których nie można ustawić.
htafoya,

W tym przypadku mam jeszcze jedno rozwiązanie. Zaktualizowałem odpowiedź.
Sergey Glotov

2
onTouchEvent dzwonił wiele razy, więc nie jest to również dobra praktyka
Jesus Dimrix

13

Zaimplementowałem dispatchTouchEvent w działaniu, aby to zrobić:

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

i przetestowałem to, działa idealnie!


działa, ale problem polega na tym, że jeśli mamy więcej niż jeden EditText, musimy również wziąć to pod uwagę, ale podobała mi się twoja odpowiedź :-)
Lalit Poptani

getActionMasked (ev) jest przestarzałe, więc teraz użyj: final int action = ev.getActionMasked (); dla pierwszej linii.
Andrew

12

Bardziej Kotlin i materiał do projektowania materiałów za pomocą TextInputEditText (to podejście jest również kompatybilne z EditTextView ) ...

1. Spraw, aby widok nadrzędny (widok zawartości Twojej aktywności / fragmentu) był klikalny i można go ustawić, dodając następujące atrybuty

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

2. Utwórz rozszerzenie dla wszystkich widoków (na przykład w pliku ViewExtension.kt):

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

3. Utwórz BaseTextInputEditText, który odziedziczy TextInputEditText. Zaimplementuj metodę onFocusChanged, aby ukryć klawiaturę, gdy widok nie jest skupiony:

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. Po prostu nazwij swój nowy niestandardowy widok w swoim 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> 

To wszystko. Nie trzeba modyfikować kontrolerów (fragmentu lub działania), aby obsłużyć ten powtarzalny przypadek.


Tak, ale chciałbym, żeby istniał prostszy sposób!
devDeejay

11

Zastąp publiczny logiczny dispatchTouchEvent (zdarzenie MotionEvent) w dowolnym działaniu (lub rozszerz klasę działania)

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

I to wszystko, co musisz zrobić


to był najprostszy sposób, w jaki udało mi się go uruchomić. współpracuje z wieloma EditTexts i
ScrollView

Przetestowałem to z wieloma EditTexts; to działa! Jedyną wadą jest to, że podczas wykonywania ruchu przeciągania ukrywa się również.
RominaV,

9

Zmodyfikowałem rozwiązanie Andre Luis IM. Osiągnąłem to:

Stworzyłem metodę narzędzia do ukrywania miękkiej klawiatury w taki sam sposób, jak Andre Luiz IM:

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

Ale zamiast rejestrować OnTouchListener dla każdego widoku, który daje słabą wydajność, zarejestrowałem OnTouchListener tylko dla widoku głównego. Ponieważ zdarzenie bąbelkuje, dopóki nie zostanie zużyte (EditText jest jednym z widoków, który zużywa go domyślnie), jeśli dojdzie do widoku głównego, dzieje się tak, ponieważ nie został zużyty, więc zamykam miękką klawiaturę.

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

1
Wydaje mi się to bezpieczniejsze.
superarts.org

9

Zdaję sobie sprawę, że ten wątek jest dość stary, poprawna odpowiedź wydaje się ważna i istnieje wiele działających rozwiązań, ale myślę, że podane poniżej podejście może mieć dodatkową korzyść w zakresie wydajności i elegancji.

Potrzebuję tego zachowania do wszystkich moich działań, więc utworzyłem klasę CustomActivity dziedziczącą z klasy Activity i „zaczepiłem” funkcję dispatchTouchEvent . Należy przede wszystkim spełnić dwa warunki:

  1. Jeśli fokus pozostaje niezmieniony, a ktoś stuknie poza bieżące pole wejściowe, zamknij edytor IME
  2. Jeśli fokus się zmienił, a następny fokusowany element nie jest instancją jakiegokolwiek pola wejściowego, należy zamknąć edytor IME

Oto mój wynik:

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

Uwaga dodatkowa: Dodatkowo przypisuję te atrybuty do widoku głównego, dzięki czemu można wyczyścić fokus na każdym polu wejściowym i zapobiec skupianiu się pól wejściowych na uruchamianiu aktywności (dzięki czemu widok zawartości jest „łapaczem fokusu”):

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

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

    view.setFocusable(true);
    view.setFocusableInTouchMode(true);
}

Super działa dobrze dzięki !! umieściłem jeden głos za odpowiedź.
vijay,

2
Myślę, że to najlepsze rozwiązanie dla złożonych układów. Ale do tej pory znalazłem 2 wady: 1. Menu kontekstowe EditText jest niemożliwe do kliknięcia - każde kliknięcie powoduje utratę ostrości z EditText 2. Gdy nasz EditText znajduje się na dole widoku i długo go klikamy (aby wybrać słowo), a następnie klawiatura pokazała, że ​​nasz „punkt kliknięcia” znajduje się na klawiaturze, a nie w EditText - więc ponownie tracimy ostrość: /
podobny

@sosite, myślę, że rozwiązałem te ograniczenia w mojej odpowiedzi; Spójrz.
Andy Dennie,

6

Podobało mi się podejście do dzwonienia dispatchTouchEventwykonane przez htafoya, ale:

  • Nie zrozumiałem części dotyczącej timera (nie wiem, dlaczego pomiar przestoju powinien być konieczny?)
  • Nie lubię rejestrować / wyrejestrowywać wszystkich EditTextów przy każdej zmianie widoku (może to być całkiem sporo zmian i edycji tekstów w złożonych hierarchiach)

Uczyniłem to nieco łatwiejszym rozwiązaniem:

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

Jest jedna wada:

Przejście z jednego EditTextdo drugiego EditTextczyni skórę klawiatury i reshow - w moim przypadku jest to pożądane w ten sposób, ponieważ pokazuje, że przełączane pomiędzy dwoma składnikami wejściowych.


Ta metoda działała najlepiej pod względem możliwości plug-and-play z moją FragmentActivity.
BillyRayCyrus

Dzięki, to najlepszy sposób! Właśnie dodałem sprawdzanie akcji zdarzenia: int action = ev.getActionMasked (); if (akcja == MotionEvent.ACTION_DOWN || akcja == MotionEvent.ACTION_POINTER_DOWN) {...}
sergey.n

Dzięki ! Zaoszczędziłeś mój czas ... Najlepsza odpowiedź.
I.d007,

6

Zarzut: Rozumiem, że nie mam siły przebicia, ale proszę, potraktuj poważnie moją odpowiedź.

Problem: Wyłącz klawiaturę miękką, gdy klikasz poza klawiaturę lub edytujesz tekst z minimalnym kodem.

Rozwiązanie: zewnętrzna biblioteka znana jako Butterknife.

Rozwiązanie jednoliniowe:

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

Bardziej czytelne rozwiązanie:

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

Objaśnienie: Powiąż odbiornik OnClick z identyfikatorem nadrzędnym układu XML działania, aby każde kliknięcie układu (nie dotyczy tekstu edycji ani klawiatury) spowodowało uruchomienie tego fragmentu kodu, który ukryje klawiaturę.

Przykład: jeśli plik układu to R.layout.my_layout, a identyfikator układu to R.id.my_layout_id, wówczas wywołanie powiązania Butterknife powinno wyglądać następująco:

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

Link do dokumentacji Butterknife: http://jakewharton.github.io/butterknife/

Wtyczka: Butterknife zrewolucjonizuje Twój rozwój Androida. Rozważ to.

Uwaga: Ten sam wynik można osiągnąć bez użycia zewnętrznej biblioteki Butterknife. Wystarczy ustawić OnClickListener na układ nadrzędny, jak opisano powyżej.


Niesamowite, idealne rozwiązanie! Dziękuję Ci.
nullforlife

6

W kotlin możemy wykonać następujące czynności. Nie ma potrzeby powtarzania wszystkich widoków. Będzie działać również w przypadku fragmentów.

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

nie działa z fragmentu
Nurseyit Tursunkulov,

to działa, ale ma błąd. na przykład, jeśli chcę wkleić tekst w widoku tekstu, klawiatura ukrywa się, a następnie pokazuje. To trochę denerwujące.
George Shalvashvili

Więc powiedz mi najlepsze rozwiązanie ..
Sai

4

Oto kolejna odmiana odpowiedzi Fje, która dotyczy problemów podniesionych przez sosite.

Chodzi o to, aby poradzić sobie zarówno z działaniami w dół, jak i w górę w działaniu dispatchTouchEvent metodzie działania. Podczas akcji „w dół” odnotowujemy aktualnie skupiony widok (jeśli istnieje) i to, czy był w nim dotyk, zapisując oba te fragmenty informacji na później.

Jeśli chodzi o akcję up, najpierw wysyłamy, aby umożliwić inny widok potencjalnie skupić się. Jeśli po tym, aktualnie skupiony widok jest pierwotnie skupionym widokiem, a dolny dotyk był w tym widoku, to zostawiamy klawiaturę otwartą.

Jeśli aktualnie ustawiony widok jest inny niż pierwotnie ustawiony widok i jest to EditText, pozostawiamy również klawiaturę otwartą.

W przeciwnym razie zamykamy to.

Podsumowując, działa to w następujący sposób:

  • po dotknięciu wewnątrz aktualnie ustawionej EditTextklawiatury pozostaje otwarta
  • podczas przejścia z fokusa EditTextna inny EditTextklawiatura pozostaje otwarta (nie zamyka się / nie otwiera ponownie)
  • po dotknięciu dowolnego miejsca poza aktualnie skupionym, EditTextktóry nie jest inny EditText, klawiatura zamyka się
  • długie naciśnięcie w, EditTextaby wyświetlić kontekstowy pasek akcji (z przyciskami wycinania / kopiowania / wklejania), klawiatura pozostaje otwarta, nawet jeśli akcja UP miała miejsce poza aktywnym EditText(który przesunął się w dół, aby zrobić miejsce dla CAB) . Pamiętaj jednak, że naciśnięcie przycisku w CAB spowoduje zamknięcie klawiatury. To może, ale nie musi być pożądane; jeśli chcesz wyciąć / skopiować z jednego pola i wkleić do innego, byłoby to możliwe. Jeśli chcesz wkleić z powrotem w to samo EditText, nie byłoby.
  • gdy fokus EditTextznajduje się na dole ekranu i długo klikasz tekst, aby go zaznaczyć, EditTextutrzymuje fokus, a zatem klawiatura otwiera się tak, jak chcesz, ponieważ wykonujemy sprawdzanie „dotyk jest w granicach widoku” podczas akcji w dół , a nie wzrost akcji.

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

4

jest to zbyt proste, po prostu ustaw swój układ, który można kliknąć, aby można go było ustawić za pomocą tego kodu:

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

a następnie napisz metodę i OnClickListner dla tego układu, aby po dotknięciu najwyższego układu w dowolnym miejscu wywołał metodę, w której napiszesz kod, aby zamknąć klawiaturę. następuje kod obu; // musisz to napisać w OnCreate ()

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

metoda wywoływana z listnera: -

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

4

Uważam, że zaakceptowana bitowa odpowiedź jest złożona dla tego prostego wymagania. Oto, co zadziałało dla mnie bez żadnych problemów.

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

1
Jeśli korzystasz z NestedScrollViewlub złożonych układów, zapoznaj się z przyjętą odpowiedzią: stackoverflow.com/a/11656129/2914140 . Powinieneś wiedzieć, że inne pojemniki mogą powodować uszkodzenia.
CoolMind

3

Istnieje prostsze podejście, oparte na tym samym problemie iPhone'a. Wystarczy zastąpić układ tła na zdarzeniu dotykowym, w którym znajduje się edytowany tekst. Wystarczy użyć tego kodu w OnCreate działania (login_fondo to układ główny):

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

1
Jak powiedziałem i pamiętam, działa to tylko wtedy, gdy formularz nie znajduje się w ScrollView.
htafoya

1
Nie działa to zbyt dobrze, jeśli układ tła zawiera inne układy potomne.
AxeEffect,

Dziękujemy za przypomnienie, że onClick nie był jedyną opcją.
Harpreet,

3

Metoda pokazywania / ukrywania miękkiej klawiatury

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

    }

Mam nadzieję, że były przydatne


3

To jest dla mnie najłatwiejsze rozwiązanie (i opracowane przeze mnie).

Jest to metoda na ukrycie klawiatury.

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

teraz ustaw atrybut onclick nadrzędnego układu działania na powyższą metodę hideKeyboardalbo z widoku Projekt pliku XML lub pisząc poniższy kod w widoku Tekst pliku XML.

android:onClick="hideKeyboard"

2

Udoskonaliłem metodę, umieściłem następujący kod w pewnej klasie narzędzi interfejsu użytkownika (najlepiej niekoniecznie), aby można było uzyskać do niego dostęp ze wszystkich klas Activity lub Fragment, aby spełniał swoje zadanie.

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

Powiedz na przykład, że musisz wywołać to z aktywności, nazwij to w następujący sposób;

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

Ogłoszenie

findViewById (android.R.id.content)

To daje nam widok główny bieżącej grupy (nie możesz ustawić identyfikatora w widoku głównym).

Twoje zdrowie :)



2

Czynność

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

3
Ten kod jest prosty, ale ma oczywisty problem: zamyka klawiaturę po dotknięciu w dowolnym miejscu . Oznacza to, że jeśli dotkniesz innego miejsca EditText, aby przesunąć kursor wejściowy, ukrywa on klawiaturę, a klawiatura wyskakuje ponownie przez system.
Cholerne warzywa

2

Po prostu dodaj ten kod do klasy @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);
}

3
Chociaż może to odpowiedzieć na pytanie, lepiej wyjaśnić podstawowe części odpowiedzi i być może problem z kodem OPs.
pirho,

tak @pirho Zgadzam się również z tobą Haseeb musi skoncentrować się na udzieleniu właściwej odpowiedzi.
Dilip,

2
@Dilip Czy wiesz, że możesz również głosować na komentarze, które zgadzasz się? Ma to na celu utrzymanie sekcji komentarzy w czystości, aby nie było wielu komentarzy mających ten sam punkt.
pirho

2

Zamiast iterować wszystkie widoki lub nadpisywać dispatchTouchEvent.

Dlaczego po prostu nie zastąpić onUserInteraction () działania, upewni się, że klawiatura zostanie zwolniona za każdym razem, gdy użytkownik stuknie poza EditText.

Działa nawet wtedy, gdy EditText znajduje się w scrollView.

@Override
public void onUserInteraction() {
    if (getCurrentFocus() != null) {
        InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
    }
}

to lepsza odpowiedź
ovluca

Tak! To bardzo czysta odpowiedź. A jeśli utworzysz AbstractActivity, który rozszerza się na wszystkie inne Twoje działania, możesz upiec to jako domyślne zachowanie w całej aplikacji.
wildcat12

1

Dostałem to z niewielkim wariantem rozwiązania Fernando Camarago. W metodzie onCreate dołączam pojedynczy widok onTouchListener do widoku głównego, ale przesyłam widok zamiast działania jako argument.

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

W osobnej klasie Utils jest ...

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

1

To może być stare, ale udało mi się to zaimplementować niestandardową klasę

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

najlepszą praktyką jest tutaj stworzenie klasy Pomocnika, a każdy kontener Względny / Liniowy Układ powinien to zaimplementować.

**** Uwaga: tylko główny kontener powinien implementować tę klasę (dla optymalizacji) ****

i zaimplementuj to w następujący sposób:

Parent.setOnClickListener( new DismissKeyboardListener(this) ); 

to słowo kluczowe dla Aktywności. więc jeśli jesteś na fragmencie, używasz jak getActivity ();

--- kciuki do góry, jeśli ci to pomoże ... --- na zdrowie Ralph ---


1

To jest nieco zmodyfikowana wersja odpowiedzi fje, która w większości działała idealnie.

Ta wersja używa ACTION_DOWN, więc wykonanie operacji przewijania również zamyka klawiaturę. Nie propaguje również zdarzenia, chyba że klikniesz inny EditText. Oznacza to, że kliknięcie w dowolnym miejscu poza tekstem edycji, nawet w innym klikalnym, po prostu zamyka klawiaturę.

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

Coś tu nie wygląda; jesteś przypisując zarówno viewi viewTmpdo getCurrentFocus(), więc oni zawsze będzie ta sama wartość.
Andy Dennie,

1

Zrobiłem w ten sposób:

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

Ukryj kod klawiatury :

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

Gotowy

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.