EditText, wyraźne skupienie się na dotyku na zewnątrz


100

Mój układ zawiera ListView, SurfaceViewi EditText. Kiedy klikam EditText, otrzymuje fokus i pojawia się klawiatura ekranowa. Kiedy klikam gdzieś poza obszarem EditText, nadal ma fokus (nie powinien). Myślę, że mógłbym ustawić je OnTouchListenerna innych widokach w układzie i ręcznie wyczyścić EditTextfokus. Ale wydaje się zbyt hakerski ...

Ja też mam taką samą sytuację w innym layoucie - widok listy z różnymi typami elementów, z których część ma EditTextwewnątrz. Działają tak, jak napisałem powyżej.

Zadaniem jest EditTextutrata koncentracji, gdy użytkownik dotknie czegoś poza nim.

Widziałem tutaj podobne pytania, ale nie znalazłem żadnego rozwiązania ...

Odpowiedzi:


66

Wypróbowałem wszystkie te rozwiązania. edc598 był najbliżej działania, ale zdarzenia dotykowe nie wyzwalały na innychView zawartych w układzie. Na wypadek, gdyby ktoś potrzebował takiego zachowania, oto co zrobiłem:

Stworzyłem (niewidoczny) o FrameLayoutnazwie touchInterceptor jako ostatni Vieww układzie, aby nakładał wszystko ( edycja: musisz również użyć RelativeLayoutjako układu nadrzędnego i nadać atrybuty touchInterceptor fill_parent ). Następnie użyłem go do przechwycenia dotknięć i określenia, czy dotyk był na górze, EditTextczy nie:

FrameLayout touchInterceptor = (FrameLayout)findViewById(R.id.touchInterceptor);
touchInterceptor.setOnTouchListener(new OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            if (mEditText.isFocused()) {
                Rect outRect = new Rect();
                mEditText.getGlobalVisibleRect(outRect);
                if (!outRect.contains((int)event.getRawX(), (int)event.getRawY())) {
                    mEditText.clearFocus();
                    InputMethodManager imm = (InputMethodManager) v.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 
                    imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
                }
            }
        }
        return false;
    }
});

Zwróć false, aby pozwolić na obsługę dotykową.

To hacky, ale to jedyna rzecz, która działała dla mnie.


20
Możesz zrobić to samo bez dodawania dodatkowego układu, zastępując metodę dispatchTouchEvent (MotionEvent ev) w swoim działaniu.
pcans

dzięki za wskazówkę clearFocus (), zadziałała również dla mnie w
SearchView

1
Chciałbym podpowiedzieć poniżej odpowiedź @ zMan. Opiera się na tym, ale nie wymaga widoku stackoverflow.com/a/28939113/969016
Boy

Ponadto, jeśli ktoś spotyka się z sytuacją, że klawiatura nie ukrywał jednak polanie Focus, pierwszym powołaniem się hideSoftInputFromWindow()wtedy jasne ostrości
Ahmet Gokdayi

231

Opierając się na odpowiedzi Kena, oto najbardziej modułowe rozwiązanie kopiuj i wklej.

Nie potrzeba XML.

Umieść go w swojej aktywności, a będzie on dotyczył wszystkich tekstów edycji, w tym fragmentów tej aktywności.

@Override
public boolean dispatchTouchEvent(MotionEvent event) {
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
        View v = getCurrentFocus();
        if ( v instanceof EditText) {
            Rect outRect = new Rect();
            v.getGlobalVisibleRect(outRect);
            if (!outRect.contains((int)event.getRawX(), (int)event.getRawY())) {
                v.clearFocus();
                InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
                imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
            }
        }
    }
    return super.dispatchTouchEvent( event );
}

12
Wydaje się w porządku. Nadal mam jeden problem, cały mój tekst edycji znajduje się w widoku przewijania, a kursor na górze jest zawsze widoczny. Nawet gdy klikam na zewnątrz, fokus znika i klawiatura znika, ale kursor jest nadal widoczny w tekście edycji u góry.
Vaibhav Gupta

1
Najlepsze rozwiązanie, z którym się spotkałem !! Wcześniej korzystałem z @Override public boolean onTouch (View v, MotionEvent event) {if (v.getId ()! = Search_box.getId ()) {if (search_box.hasFocus ()) {InputMethodManager imm = (InputMethodManager) getSystemService ( Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow (search_box.getWindowToken (), 0); search_box.clearFocus (); } return false; } return false; }
Pradeep Kumar Kushwaha

1
Rozwiązanie na jeden strzał :)
karthik kolanji

13
Najlepsze rozwiązanie, jakiego nigdy wcześniej nie widziałem! Zapiszę ten kod w swoim tekście, aby przekazać mojemu synowi i wnukowi.
Fan Applelin

2
Jeśli użytkownik kliknie inny EditText, klawiatura natychmiast się zamknie i ponownie otworzy. Aby rozwiązać ten Zmieniłem MotionEvent.ACTION_DOWNsię MotionEvent.ACTION_UPna kodzie. Świetna odpowiedź przy okazji!
Thanasis1101

35

Dla widoku nadrzędnego EditText, niech następujące 3 atrybuty będą „ true ”:
klikalne , z możliwością fokusowania , focusableInTouchMode .

Jeśli widok chce się skupić, musi spełniać te 3 warunki.

Zobacz android.view :

public boolean onTouchEvent(MotionEvent event) {
    ...
    if (((viewFlags & CLICKABLE) == CLICKABLE || 
        (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
        ...
        if (isFocusable() && isFocusableInTouchMode()
            && !isFocused()) {
                focusTaken = requestFocus();
        }
        ...
    }
    ...
}

Mam nadzieję, że to pomoże.



2
Aby wyraźnie skupić się, drugi widok, który można skupić, musi być nadrzędny wobec skoncentrowanego widoku. Nie zadziała, jeśli widok, na którym ma się skupić, znajduje się w innej hierarchii.
AxeEffect

Technicznie rzecz biorąc, twoje stwierdzenie jest nieprawidłowe. Widok rodzica musi być (clickable) *OR* (focusable *AND* focusableInTouchMode);)
Martin Marconcini

25

Po prostu umieść te właściwości w najwyższej pozycji nadrzędnej.

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

dzięki. walczyłem z tym problemem od dwóch godzin. Dodanie tych wierszy do górnej części rodzica działało.
Danish Shärmà

FYI: Chociaż działa w większości przypadków, nie działa z niektórymi wewnętrznymi elementami układu.
SV Madhava Reddy

17

Odpowiedź Kena działa, ale jest zepsuta. Jak wspomina pcans w komentarzu do odpowiedzi, to samo można zrobić za pomocą metody dispatchTouchEvent. To rozwiązanie jest bardziej przejrzyste, ponieważ pozwala uniknąć hakowania kodu XML za pomocą przezroczystego, fikcyjnego układu FrameLayout. Oto jak to wygląda:

@Override
public boolean dispatchTouchEvent(MotionEvent event) {
    EditText mEditText = findViewById(R.id.mEditText);
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
        View v = getCurrentFocus();
        if (mEditText.isFocused()) {
            Rect outRect = new Rect();
            mEditText.getGlobalVisibleRect(outRect);
            if (!outRect.contains((int)event.getRawX(), (int)event.getRawY())) {
                mEditText.clearFocus();
                //
                // Hide keyboard
                //
                InputMethodManager imm = (InputMethodManager) v.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 
                imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
            }
        }
    }
    return super.dispatchTouchEvent(event);
}

7

Dla mnie poniżej rzeczy zadziałały -

1. Dodawanie android:clickable="true"i android:focusableInTouchMode="true"do parentLayoutz EditTextczyliandroid.support.design.widget.TextInputLayout

<android.support.design.widget.TextInputLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:clickable="true"
    android:focusableInTouchMode="true">
<EditText
    android:id="@+id/employeeID"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:ems="10"
    android:inputType="number"
    android:hint="Employee ID"
    tools:layout_editor_absoluteX="-62dp"
    tools:layout_editor_absoluteY="16dp"
    android:layout_marginTop="42dp"
    android:layout_alignParentTop="true"
    android:layout_alignParentRight="true"
    android:layout_alignParentEnd="true"
    android:layout_marginRight="36dp"
    android:layout_marginEnd="36dp" />
    </android.support.design.widget.TextInputLayout>

2. Nadpisywanie dispatchTouchEventw klasie Activity i wstawianie hideKeyboard()funkcji

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            View view = getCurrentFocus();
            if (view != null && view instanceof EditText) {
                Rect r = new Rect();
                view.getGlobalVisibleRect(r);
                int rawX = (int)ev.getRawX();
                int rawY = (int)ev.getRawY();
                if (!r.contains(rawX, rawY)) {
                    view.clearFocus();
                }
            }
        }
        return super.dispatchTouchEvent(ev);
    }

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

3. Dodawanie setOnFocusChangeListenerdo EditText

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

5

Prawdopodobnie już znalazłeś odpowiedź na ten problem, ale szukałem sposobu rozwiązania tego problemu i nadal nie mogę znaleźć dokładnie tego, czego szukałem, więc pomyślałem, że opublikuję to tutaj.

To, co zrobiłem, było następujące (jest to bardzo uogólnione, celem jest przedstawienie ci, jak postępować, kopiowanie i wklejanie całego kodu nie zadziała O: D):

Najpierw miej EditText i wszystkie inne widoki, które chcesz w swoim programie opakowane pojedynczym widokiem. W moim przypadku użyłem LinearLayout do zawijania wszystkiego.

<LinearLayout 
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/mainLinearLayout">
 <EditText
  android:id="@+id/editText"/>
 <ImageView
  android:id="@+id/imageView"/>
 <TextView
  android:id="@+id/textView"/>
 </LinearLayout>

Następnie w swoim kodzie musisz ustawić Odbiornik dotyku na swój główny LinearLayout.

final EditText searchEditText = (EditText) findViewById(R.id.editText);
mainLinearLayout.setOnTouchListener(new View.OnTouchListener() {

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            // TODO Auto-generated method stub
            if(searchEditText.isFocused()){
                if(event.getY() >= 72){
                    //Will only enter this if the EditText already has focus
                    //And if a touch event happens outside of the EditText
                    //Which in my case is at the top of my layout
                    //and 72 pixels long
                    searchEditText.clearFocus();
                    InputMethodManager imm = (InputMethodManager) v.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
                    imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
                }
            }
            Toast.makeText(getBaseContext(), "Clicked", Toast.LENGTH_SHORT).show();
            return false;
        }
    });

Mam nadzieję, że to pomoże niektórym ludziom. A przynajmniej pomaga im zacząć rozwiązywać problem.


Tak, ja też doszedłem do podobnego rozwiązania. Następnie przeportowałem podzbiór systemu widoku Androida na C ++ na OpenGL i zbudowałem w nim tę funkcję)
uwaga173

To naprawdę bardzo fajny przykład. Szukałem takiego kodu, ale udało mi się znaleźć tylko złożone rozwiązania. Użyłem obu współrzędnych XY, aby być bardziej precyzyjnym, ponieważ miałem również kilka wielowierszowych EditText. Dzięki!
Sumitk,

3

Po prostu zdefiniuj dwie właściwości rodzica tego EditTextjako:

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

Dlatego gdy użytkownik dotknie EditTextobszaru poza obszarem, fokus zostanie usunięty, ponieważ fokus zostanie przeniesiony do widoku nadrzędnego.


2

Mam ListViewzłożone EditTextwidoki. Scenariusz mówi, że po edycji tekstu w jednym lub kilku wierszach powinniśmy kliknąć przycisk „Zakończ”. Użyłem onFocusChangedna EditTextwidoku w środku, listViewale po kliknięciu Zakończ dane nie są zapisywane. Problem został rozwiązany poprzez dodanie

listView.clearFocus();

wewnątrz przycisku onClickListener„Zakończ” i dane zostały pomyślnie zapisane.


Dzięki. Zaoszczędziłeś mój czas, miałem ten scenariusz w widoku recyklingu i traciłem ostatnią wartość editText po kliknięciu przycisku, ponieważ ostatnia fokus editText nie zmienił się i tak dalejFocusChanged nie został wywołany. Chciałbym znaleźć rozwiązanie, które sprawi, że ostatnia edycja tekstu straci fokus, gdy znajdzie się poza nim, kliknięcie tego samego przycisku ... i znalezienie rozwiązania. Działało świetnie!
Reyhane Farshbaf

2

Naprawdę uważam, że jest to solidniejszy sposób użycia getLocationOnScreenniż getGlobalVisibleRect. Ponieważ napotykam problem. Istnieje widok listy, który zawiera niektóre Edittext i jest ustawiony ajustpanw działaniu. Uważam, że zwracam getGlobalVisibleRectwartość, która wygląda jak zawierająca scrollY, ale event.getRawY jest zawsze na ekranie. Poniższy kod działa dobrze.

public boolean dispatchTouchEvent(MotionEvent event) {
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
        View v = getCurrentFocus();
        if ( v instanceof EditText) {
            if (!isPointInsideView(event.getRawX(), event.getRawY(), v)) {
                Log.i(TAG, "!isPointInsideView");

                Log.i(TAG, "dispatchTouchEvent clearFocus");
                v.clearFocus();
                InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
                imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
            }
        }
    }
    return super.dispatchTouchEvent( event );
}

/**
 * Determines if given points are inside view
 * @param x - x coordinate of point
 * @param y - y coordinate of point
 * @param view - view object to compare
 * @return true if the points are within view bounds, false otherwise
 */
private boolean isPointInsideView(float x, float y, View view) {
    int location[] = new int[2];
    view.getLocationOnScreen(location);
    int viewX = location[0];
    int viewY = location[1];

    Log.i(TAG, "location x: " + location[0] + ", y: " + location[1]);

    Log.i(TAG, "location xWidth: " + (viewX + view.getWidth()) + ", yHeight: " + (viewY + view.getHeight()));

    // point is inside view bounds
    return ((x > viewX && x < (viewX + view.getWidth())) &&
            (y > viewY && y < (viewY + view.getHeight())));
}

Dziękuję bardzo! To działało we wszystkich scenariuszach układu, takich jak RecyclerView itp. Wypróbowałem inne rozwiązanie, ale to pasuje do wszystkich! :) Dobra robota!
Woppi

1

Aby stracić fokus po dotknięciu innego widoku, oba widoki należy ustawić jako view.focusableInTouchMode (true).

Wygląda jednak na to, że nie zaleca się używania ostrości w trybie dotykowym. Zajrzyj tutaj: http://android-developers.blogspot.com/2008/12/touch-mode.html


Tak, przeczytałem to, ale nie jest to dokładnie to, czego chcę. Nie potrzebuję innego spojrzenia, żeby się skupić. Chociaż nie widzę teraz żadnego alternatywnego sposobu ... Może w jakiś sposób sprawić, by widok główny przechwytywał fokus, gdy użytkownik kliknie dziecko, na którym nie można skupić uwagi?
przypis173

O ile wiem, fokus nie może być zarządzany przez klasę, jest zarządzany przez androida ... brak pomysłów :(
forumercio

0

Najlepszym sposobem jest użycie metody domyślnej clearFocus()

Wiesz, jak rozwiązywać kody, onTouchListenerprawda?

Po prostu zadzwoń EditText.clearFocus(). Wyraźnie skupi się na last EditText.


0

Jak zasugerował @pcans, możesz to nadpisać dispatchTouchEvent(MotionEvent event) w swojej aktywności.

Tutaj otrzymujemy współrzędne dotyku i porównujemy je w celu wyświetlenia granic. Jeśli dotyk jest wykonywany poza widokiem, zrób coś.

@Override
public boolean dispatchTouchEvent(MotionEvent event) {
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
        View yourView = (View) findViewById(R.id.view_id);
        if (yourView != null && yourView.getVisibility() == View.VISIBLE) {
            // touch coordinates
            int touchX = (int) event.getX();
            int touchY = (int) event.getY();
            // get your view coordinates
            final int[] viewLocation = new int[2];
            yourView.getLocationOnScreen(viewLocation);

            // The left coordinate of the view
            int viewX1 = viewLocation[0];
            // The right coordinate of the view
            int viewX2 = viewLocation[0] + yourView.getWidth();
            // The top coordinate of the view
            int viewY1 = viewLocation[1];
            // The bottom coordinate of the view
            int viewY2 = viewLocation[1] + yourView.getHeight();

            if (!((touchX >= viewX1 && touchX <= viewX2) && (touchY >= viewY1 && touchY <= viewY2))) {

                Do what you want...

                // If you don't want allow touch outside (for example, only hide keyboard or dismiss popup) 
                return false;
            }
        }
    }
    return super.dispatchTouchEvent(event);
}

Nie jest też konieczne sprawdzanie istnienia i widoczności widoku, jeśli układ Twojej aktywności nie zmienia się w trakcie działania (np. Nie dodajesz fragmentów ani nie zastępujesz / nie usuwasz widoków z układu). Ale jeśli chcesz zamknąć (lub zrobić coś podobnego) niestandardowe menu kontekstowe (jak w sklepie Google Play podczas korzystania z rozszerzonego menu elementu), konieczne jest sprawdzenie istnienia widoku. W przeciwnym razie otrzymasz plik NullPointerException.


0

Ten prosty fragment kodu robi, co chcesz

GestureDetector gestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
            @Override
            public boolean onSingleTapConfirmed(MotionEvent e) {
                KeyboardUtil.hideKeyboard(getActivity());
                return true;
            }
        });
mScrollView.setOnTouchListener((v, e) -> gestureDetector.onTouchEvent(e));

0

W Kotlinie

hidekeyboard () jest rozszerzeniem Kotlin

fun Activity.hideKeyboard() {
    hideKeyboard(currentFocus ?: View(this))
}

W aktywności dodaj dispatchTouchEvent

override fun dispatchTouchEvent(event: MotionEvent): Boolean {
    if (event.action == MotionEvent.ACTION_DOWN) {
        val v: View? = currentFocus
        if (v is EditText) {
            val outRect = Rect()
            v.getGlobalVisibleRect(outRect)
            if (!outRect.contains(event.rawX.toInt(), event.rawY.toInt())) {
                v.clearFocus()
                hideKeyboard()
            }
        }
    }
    return super.dispatchTouchEvent(event)
}

Dodaj te właściwości w najwyższym miejscu nadrzędnym

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

0

To jest moja wersja oparta na kodzie zMana. Nie ukryje klawiatury, jeśli następny widok również jest tekstem edycji. Nie ukryje również klawiatury, jeśli użytkownik tylko przewinie ekran.

@Override
public boolean dispatchTouchEvent(MotionEvent event) {
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
        downX = (int) event.getRawX();
    }

    if (event.getAction() == MotionEvent.ACTION_UP) {
        View v = getCurrentFocus();
        if (v instanceof EditText) {
            int x = (int) event.getRawX();
            int y = (int) event.getRawY();
            //Was it a scroll - If skip all
            if (Math.abs(downX - x) > 5) {
                return super.dispatchTouchEvent(event);
            }
            final int reducePx = 25;
            Rect outRect = new Rect();
            v.getGlobalVisibleRect(outRect);
            //Bounding box is to big, reduce it just a little bit
            outRect.inset(reducePx, reducePx);
            if (!outRect.contains(x, y)) {
                v.clearFocus();
                boolean touchTargetIsEditText = false;
                //Check if another editText has been touched
                for (View vi : v.getRootView().getTouchables()) {
                    if (vi instanceof EditText) {
                        Rect clickedViewRect = new Rect();
                        vi.getGlobalVisibleRect(clickedViewRect);
                        //Bounding box is to big, reduce it just a little bit
                        clickedViewRect.inset(reducePx, reducePx);
                        if (clickedViewRect.contains(x, y)) {
                            touchTargetIsEditText = true;
                            break;
                        }
                    }
                }
                if (!touchTargetIsEditText) {
                    InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
                    imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
                }
            }
        }
    }
    return super.dispatchTouchEvent(event);
}
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.