EditText, klarer Fokus auf Berührung außerhalb

100

Mein Layout enthält ListView, SurfaceViewund EditText. Wenn ich auf das klicke EditText, wird der Fokus angezeigt und die Bildschirmtastatur wird angezeigt. Wenn ich irgendwo außerhalb von klicke EditText, hat es immer noch den Fokus (sollte es nicht). Ich denke, ich könnte OnTouchListenerdie anderen Ansichten im Layout einrichten und den EditTextFokus manuell löschen . Scheint aber zu hackisch ...

Ich habe auch die gleiche Situation in der anderen Layout-Listenansicht mit verschiedenen Arten von Elementen, von denen einige EditTextdarin enthalten sind. Sie verhalten sich so, wie ich es oben geschrieben habe.

Die Aufgabe besteht darin, EditTextden Fokus zu verlieren, wenn der Benutzer etwas außerhalb davon berührt.

Ich habe hier ähnliche Fragen gesehen, aber keine Lösung gefunden ...

note173
quelle

Antworten:

66

Ich habe all diese Lösungen ausprobiert. edc598 war der Arbeit am nächsten, aber Berührungsereignisse wurden bei anderen Viewim Layout enthaltenen s nicht ausgelöst . Für den Fall, dass jemand dieses Verhalten benötigt, habe ich Folgendes getan:

Ich habe einen (unsichtbaren) FrameLayoutnamens touchInterceptor als letzten Viewim Layout erstellt, damit er alles überlagert ( Bearbeiten: Sie müssen auch einen RelativeLayoutals übergeordnetes Layout verwenden und die touchInterceptor- fill_parent Attribute angeben ). Dann habe ich es verwendet, um Berührungen abzufangen und festzustellen, ob die Berührung über der war EditTextoder nicht:

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

Geben Sie false zurück, damit das Touch-Handling durchfällt.

Es ist hacky, aber es ist das einzige, was für mich funktioniert hat.

Ken
quelle
20
Sie können dasselbe tun, ohne ein zusätzliches Layout hinzuzufügen, indem Sie dispatchTouchEvent (MotionEvent ev) in Ihrer Aktivität überschreiben.
pcans
danke für den clearFocus () Hinweis, es hat auch bei SearchView funktioniert
Jimmy Ilenloa
1
Ich möchte auf die Antwort von @ zMan unten hinweisen. Es basiert darauf, benötigt aber keine Ansicht stackoverflow.com/a/28939113/969016
Boy
Wenn jemand auf eine Situation stößt, in der sich die Tastatur nicht versteckt, sondern den Fokus hideSoftInputFromWindow()
löscht
229

Aufbauend auf Kens Antwort finden Sie hier die modularste Lösung zum Kopieren und Einfügen.

Kein XML erforderlich.

Fügen Sie es in Ihre Aktivität ein und es gilt für alle EditTexts, einschließlich derjenigen innerhalb von Fragmenten innerhalb dieser Aktivität.

@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 );
}
zMan
quelle
12
Sieht gut aus. Ich habe immer noch ein Problem: Alle meine Bearbeitungstexte befinden sich in einer Bildlaufansicht, und im oberen Bearbeitungstext ist immer der Cursor sichtbar. Selbst wenn ich nach draußen klicke, geht der Fokus verloren und die Tastatur ist weg, aber der Cursor ist im oberen Bearbeitungstext immer noch sichtbar.
Vaibhav Gupta
1
Beste Lösung, die mir begegnet ist !! Früher habe ich @Override public boolean onTouch (View v, MotionEvent-Ereignis) verwendet {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 (); } falsch zurückgeben; } falsch zurückgeben; }
Pradeep Kumar Kushwaha
Einfach, prägnant, funktional! Beste Lösung sicher. Danke
Aleksandar
13
Die beste Lösung, die ich noch nie gesehen habe! Ich werde diesen Code in meinem Kern speichern, um ihn an meinen Sohn und Enkel weiterzugeben.
Fan Applelin
2
Wenn der Benutzer auf einen anderen EditText klickt, wird die Tastatur geschlossen und sofort wieder geöffnet. Um dies löst mich geändert , MotionEvent.ACTION_DOWNum MotionEvent.ACTION_UPauf Ihrem Code. Tolle Antwort übrigens!
Thanasis1101
35

Für die übergeordnete Ansicht des EditText, lassen Sie die folgenden drei Attribute „werden wahr “:
anklickbare , fokussierbar , focusableInTouchMode .

Wenn eine Ansicht den Fokus erhalten möchte, muss sie diese drei Bedingungen erfüllen.

Siehe android.view :

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

Ich hoffe es hilft.

suber
quelle
2
Um den Fokus zu löschen, muss die andere fokussierbare Ansicht der fokussierten Ansicht übergeordnet sein. Es funktioniert nicht, wenn sich die zu fokussierende Ansicht auf einer anderen Hierarchie befindet.
AxeEffect
Technisch gesehen ist Ihre Aussage falsch. Die Elternansicht muss sein (clickable) *OR* (focusable *AND* focusableInTouchMode);)
Martin Marconcini
24

Fügen Sie diese Eigenschaften einfach in die oberste übergeordnete Eigenschaft ein.

android:focusableInTouchMode="true"
android:clickable="true"
android:focusable="true" 
Chandan
quelle
Danke, Mann. Ich hatte dieses Problem in den letzten zwei Stunden zu kämpfen. Das Hinzufügen dieser Zeilen im obersten übergeordneten Element hat funktioniert.
Däñish Shärmà
Zu Ihrer Information: Obwohl es in den meisten Fällen funktioniert, funktioniert es nicht für einige innere Elemente des Layouts.
SV Madhava Reddy
16

Kens Antwort funktioniert, aber es ist hacky. Wie pcans im Kommentar der Antwort anspielt, könnte dasselbe mit dispatchTouchEvent geschehen. Diese Lösung ist sauberer, da das XML nicht mit einem transparenten Dummy-FrameLayout gehackt werden muss. So sieht das aus:

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

Für mich unten haben die Dinge funktioniert -

1. Hinzufügen android:clickable="true"und android:focusableInTouchMode="true"zum parentLayoutvon EditTextdhandroid.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. Überschreiben der dispatchTouchEventAktivitätsklasse und Einfügen der hideKeyboard()Funktion

@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. Hinzufügen setOnFocusChangeListenerfür EditText

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

Sie haben wahrscheinlich bereits die Antwort auf dieses Problem gefunden, aber ich habe nach einer Lösung gesucht und kann immer noch nicht genau das finden, wonach ich gesucht habe. Deshalb dachte ich, ich würde es hier posten.

Was ich getan habe, war das Folgende (dies ist sehr verallgemeinert, der Zweck ist es, Ihnen eine Vorstellung davon zu geben, wie Sie vorgehen sollen. Das Kopieren und Einfügen des gesamten Codes funktioniert nicht O: D):

Lassen Sie zuerst den EditText und alle anderen Ansichten, die Sie in Ihrem Programm haben möchten, von einer einzelnen Ansicht umschließen. In meinem Fall habe ich ein LinearLayout verwendet, um alles zu verpacken.

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

Dann müssen Sie in Ihrem Code einen Touch Listener für Ihr Haupt-LinearLayout festlegen.

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

Ich hoffe das hilft einigen Leuten. Oder hilft ihnen zumindest, ihr Problem zu lösen.

edc598
quelle
Ja, ich bin auch zu einer ähnlichen Lösung gekommen. Dann portierte ich eine Teilmenge des Android View Systems auf Open ++ nach C ++ und baute diese Funktion ein)
note173
Dies ist wirklich ein sehr gutes Beispiel. Ich suchte nach einem solchen Code, konnte aber nur komplexe Lösungen finden. Ich habe beide XY-Koordinaten verwendet, um genauer zu sein, da ich auch nur wenige mehrzeilige EditText hatte. Vielen Dank!
Sumitk
3

Definieren Sie einfach zwei Eigenschaften des übergeordneten Elements EditTextals:

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

Wenn der Benutzer außerhalb des EditTextBereichs berührt , wird der Fokus entfernt, da der Fokus auf die übergeordnete Ansicht übertragen wird.

Dhruvam Gupta
quelle
2

Ich habe eine ListViewReihe von EditTextAnsichten. Das Szenario besagt, dass wir nach dem Bearbeiten von Text in einer oder mehreren Zeilen auf eine Schaltfläche namens "Fertig stellen" klicken sollten. Ich habe onFocusChangeddie EditTextAnsicht innerhalb von verwendet, listViewaber nach dem Klicken auf Fertig stellen werden die Daten nicht gespeichert. Das Problem wurde durch Hinzufügen gelöst

listView.clearFocus();

innerhalb der onClickListenerSchaltfläche "Fertig stellen" und die Daten wurden erfolgreich gespeichert.

Muhannad A.Alhariri
quelle
Vielen Dank. Sie haben meine Zeit gespart, ich hatte dieses Szenario in recyclerview und verlor den letzten editText-Wert nach dem Klicken auf die Schaltfläche, da sich der letzte editText-Fokus nicht geändert hat und onFocusChanged nicht aufgerufen wurde. Ich möchte eine Lösung finden, bei der der letzte editText den Fokus verliert, wenn er sich außerhalb befindet. Klicken Sie auf dieselbe Schaltfläche ... und finden Sie Ihre Lösung. Es hat super funktioniert!
Reyhane Farshbaf
2

Ich denke wirklich, dass es eine robustere Art ist getLocationOnScreenals getGlobalVisibleRect. Weil ich auf ein Problem stoße. Es gibt eine Listenansicht, die Edittext enthält und ajustpanin der Aktivität festgelegt ist. Ich finde, getGlobalVisibleRectdass ein Wert zurückgegeben wird, der so aussieht, als würde er das scrollY enthalten, aber das event.getRawY befindet sich immer auf dem Bildschirm. Der folgende Code funktioniert gut.

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())));
}
Victor Choy
quelle
ich danke dir sehr! Es hat für alle Layout-Szenarien wie RecyclerView usw. usw. funktioniert. Ich habe eine andere Lösung ausprobiert, aber diese passt für alle! :) Gut gemacht!
Woppi
1

Um den Fokus zu verlieren, wenn eine andere Ansicht berührt wird, sollten beide Ansichten als view.focusableInTouchMode (true) festgelegt werden.

Es scheint jedoch, dass die Verwendung von Fokussen im Touch-Modus nicht empfohlen wird. Bitte schauen Sie hier: http://android-developers.blogspot.com/2008/12/touch-mode.html

forumercio
quelle
Ja, ich habe das gelesen, aber es ist nicht genau das, was ich will. Ich brauche nicht die andere Sichtweise, um mich zu konzentrieren. Obwohl ich momentan keinen alternativen Weg sehe ... Vielleicht machen Sie irgendwie die Stammansicht, um den Fokus zu erfassen, wenn der Benutzer auf ein nicht fokussierbares Kind klickt?
note173
Solange ich weiß, kann der Fokus nicht von einer Klasse verwaltet werden, sondern von Android ... keine Ideen :(
forumercio
0

Am besten verwenden Sie die Standardmethode clearFocus()

Sie wissen, wie man Codes onTouchListenerrichtig löst ?

Rufen Sie einfach an EditText.clearFocus(). Es wird klar fokussieren in last EditText.

Huy Tower
quelle
0

Wie von @pcans vorgeschlagen, können Sie dies dispatchTouchEvent(MotionEvent event)in Ihrer Aktivität überschreiben .

Hier erhalten wir die Berührungskoordinaten und vergleichen sie, um Grenzen anzuzeigen. Wenn die Berührung außerhalb einer Ansicht ausgeführt wird, tun Sie etwas.

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

Es ist auch nicht erforderlich, das Vorhandensein und die Sichtbarkeit von Ansichten zu überprüfen, wenn sich das Layout Ihrer Aktivität zur Laufzeit nicht ändert (z. B. fügen Sie keine Fragmente hinzu oder ersetzen / entfernen Ansichten aus dem Layout). Wenn Sie jedoch ein benutzerdefiniertes Kontextmenü schließen möchten (oder etwas Ähnliches tun möchten) (wie im Google Play Store, wenn Sie das Überlaufmenü des Elements verwenden), müssen Sie das Vorhandensein der Ansicht überprüfen. Andernfalls erhalten Sie eine NullPointerException.

Säbel
quelle
0

Dieser einfache Codeausschnitt macht, was Sie wollen

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));
Andrii
quelle
0

In Kotlin

hidekeyboard () ist eine Kotlin-Erweiterung

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

Fügen Sie in der Aktivität dispatchTouchEvent hinzu

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

Fügen Sie diese Eigenschaften im obersten übergeordneten Element hinzu

android:focusableInTouchMode="true"
android:focusable="true"
Webserveis
quelle
0

Dies ist meine Version, die auf dem Code von zMan basiert. Die Tastatur wird nicht ausgeblendet, wenn die nächste Ansicht auch ein Bearbeitungstext ist. Die Tastatur wird auch nicht ausgeblendet, wenn der Benutzer nur den Bildschirm scrollt.

@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);
}
Dennis H.
quelle