Modifica testo, messa a fuoco chiara sul tocco esterno


100

Il mio layout contiene ListView, SurfaceViewe EditText. Quando clicco su EditText, viene attivato e viene visualizzata la tastiera su schermo. Quando clicco da qualche parte al di fuori di EditText, ha ancora il focus (non dovrebbe). Immagino di poter impostare OnTouchListenerle altre viste nel layout e cancellare manualmente il EditTextfocus di. Ma sembra troppo hacker ...

Ho la stessa situazione anche nell'altro layout: visualizzazione elenco con diversi tipi di elementi, alcuni dei quali hanno l EditText'interno. Agiscono proprio come ho scritto sopra.

Il compito è far EditTextperdere la concentrazione quando l'utente tocca qualcosa al di fuori di esso.

Ho visto domande simili qui, ma non ho trovato alcuna soluzione ...

Risposte:


66

Ho provato tutte queste soluzioni. quello di edc598 era il più vicino al funzionamento, ma gli eventi di tocco non si sono attivati ​​su altri messaggi Viewcontenuti nel layout. Nel caso qualcuno avesse bisogno di questo comportamento, questo è quello che ho finito per fare:

Ho creato un (invisibile) FrameLayoutchiamato touchInterceptor come ultimo Viewnel layout in modo che si sovrapponga a tutto ( modifica: devi anche usare a RelativeLayoutcome layout genitore e dare gli attributi touchInterceptor fill_parent ). Quindi l'ho usato per intercettare i tocchi e determinare se il tocco era sopra EditTexto no:

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

Restituisci false per far cadere la gestione del tocco.

È hacky, ma è l'unica cosa che ha funzionato per me.


20
Puoi fare la stessa cosa senza aggiungere un layout aggiuntivo sovrascrivendo dispatchTouchEvent (MotionEvent ev) nella tua attività.
pcans

grazie per il suggerimento clearFocus (), ha funzionato anche per me su SearchView
Jimmy Ilenloa

1
Vorrei suggerire la risposta di @ zMan di seguito. Si basa su questo, ma non ha bisogno di una vista stackoverflow.com/a/28939113/969016
Boy

Inoltre, se qualcuno si imbatte in una situazione in cui la tastiera non si nasconde ma si cancella la messa a fuoco, prima invoca hideSoftInputFromWindow()poi la messa a fuoco chiara
Ahmet Gokdayi

231

Basandosi sulla risposta di Ken, ecco la soluzione di copia e incolla più modulare.

Nessun XML necessario.

Inseriscilo nella tua attività e verrà applicato a tutti gli EditText, inclusi quelli all'interno dei frammenti all'interno di quell'attività.

@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
Sembra a posto. Sto ancora riscontrando un problema, tutto il mio testo di modifica si trova all'interno di una visualizzazione a scorrimento e il testo di modifica in alto ha sempre il cursore visibile. Anche quando faccio clic all'esterno, lo stato attivo viene perso e la tastiera scompare, ma il cursore è ancora visibile nel testo di modifica superiore.
Vaibhav Gupta

1
La migliore soluzione che ho trovato !! In precedenza stavo usando @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
Soluzione one shot :)
karthik kolanji

13
La migliore soluzione che non ho mai visto prima! Salverò questo codice nella mia sintesi per passarlo a mio figlio e mio nipote.
Fan Applelin

2
Se l'utente fa clic su un altro EditText, la tastiera si chiude e si riapre immediatamente. Per risolvere questo problema sono passato MotionEvent.ACTION_DOWNal MotionEvent.ACTION_UPtuo codice. Ottima risposta btw!
Thanasis1101

35

Per la vista principale di EditText, lascia che i seguenti 3 attributi siano " true ":
clickable , focusable , focusableInTouchMode .

Se una vista vuole essere focalizzata, deve soddisfare queste 3 condizioni.

Vedi android.view :

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

Spero che sia d'aiuto.



2
Per chiarire la messa a fuoco, l'altra vista attivabile deve essere genitore della vista focalizzata. Non funzionerà se la vista su cui concentrarsi è su un'altra gerarchia.
AxeEffect

Tecnicamente, la tua dichiarazione non è corretta. L'opinione dei genitori deve essere (clickable) *OR* (focusable *AND* focusableInTouchMode);)
Martin Marconcini

25

Metti semplicemente queste proprietà nella parte superiore più genitore.

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

grazie uomo. stavo lottando con questo problema nelle ultime due ore. L'aggiunta di queste linee nel genitore superiore ha funzionato.
Däñish Shärmà

FYI: Sebbene funzioni per la maggior parte dei casi, non funziona per alcuni elementi interni del layout.
SV Madhava Reddy

17

La risposta di Ken funziona, ma è hacky. Come allude pcans nel commento della risposta, la stessa cosa potrebbe essere fatta con dispatchTouchEvent. Questa soluzione è più pulita in quanto evita di dover hackerare l'XML con un FrameLayout trasparente e fittizio. Ecco come appare:

@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

Per me le cose sotto hanno funzionato -

1. Aggiungendo android:clickable="true"e android:focusableInTouchMode="true"alla parentLayoutdi EditTextieandroid.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. Sovrascrittura dispatchTouchEventnella classe di attività e inserimento della hideKeyboard()funzione

@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. Aggiunta setOnFocusChangeListenerper EditText

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

5

Probabilmente hai già trovato la risposta a questo problema, ma ho cercato come risolverlo e ancora non riesco a trovare esattamente quello che stavo cercando, quindi ho pensato di pubblicarlo qui.

Quello che ho fatto è stato il seguente (questo è molto generalizzato, lo scopo è darti un'idea di come procedere, copiare e incollare tutto il codice non funzionerà O: D):

Per prima cosa, fai in modo che l'EditText e tutte le altre viste che desideri nel tuo programma siano racchiuse in un'unica vista. Nel mio caso ho usato un LinearLayout per avvolgere tutto.

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

Quindi nel tuo codice devi impostare un Touch Listener sul tuo LinearLayout principale.

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

Spero che questo aiuti alcune persone. O almeno li aiuta a iniziare a risolvere il loro problema.


Sì, sono arrivato anche a una soluzione simile. Quindi ho portato un sottoinsieme del sistema di visualizzazione Android in c ++ su opengl e ho creato questa funzione al suo interno)
nota173

Questo è davvero un ottimo esempio. Stavo cercando un codice come questo, ma tutto ciò che ho trovato sono state soluzioni complesse. Ho usato entrambe le coordinate XY per essere più preciso poiché avevo anche pochi EditText multilinea. Grazie!
Sumitk

3

Definisci semplicemente due proprietà del genitore di quello EditTextcome:

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

Pertanto, quando l'utente toccherà al di fuori EditTextdell'area, lo stato attivo verrà rimosso perché lo stato attivo verrà trasferito alla visualizzazione genitore.


2

Ho un ListViewcomposto di EditTextopinioni. Lo scenario dice che dopo aver modificato il testo in una o più righe dovremmo fare clic su un pulsante chiamato "fine". L'ho usato onFocusChangednella EditTextvista all'interno di listViewma dopo aver fatto clic su Fine i dati non vengono salvati. Il problema è stato risolto aggiungendo

listView.clearFocus();

all'interno del onClickListenerpulsante "Fine" ei dati sono stati salvati con successo.


Grazie. Mi hai risparmiato tempo, avevo questo scenario in recyclerview e stavo perdendo l'ultimo valore editText dopo aver fatto clic sul pulsante, perché l'ultimo focus editText non è cambiato e così onFocusChanged non è stato chiamato. Mi piacerebbe trovare una soluzione che faccia in modo che last editText perda il focus una volta fuori, lo stesso pulsante clic ... e trovi la tua soluzione. Ha funzionato benissimo!
Reyhane Farshbaf

2

Penso davvero che sia un modo più robusto da usare getLocationOnScreenrispetto a getGlobalVisibleRect. Perché incontro un problema. C'è un Listview che contiene un testo edito e impostato ajustpannell'attività. Trovo getGlobalVisibleRectreturn un valore che sembra includere lo scrollY in esso, ma event.getRawY è sempre sullo schermo. Il codice seguente funziona bene.

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

grazie mille! Ha funzionato per tutti gli scenari di layout come RecyclerView, ecc. Ecc. Ho provato un'altra soluzione ma questa si adatta a tutti! :) Ottimo lavoro!
Woppi

1

Per perdere il focus quando viene toccata un'altra vista, entrambe le viste dovrebbero essere impostate come view.focusableInTouchMode (true).

Ma sembra che l'uso dei focus in modalità touch non sia consigliato. Dai un'occhiata qui: http://android-developers.blogspot.com/2008/12/touch-mode.html


Sì, l'ho letto, ma non è esattamente quello che voglio. Non ho bisogno dell'altra vista per concentrarmi. Anche se al momento non vedo alcun modo alternativo ... Forse, in qualche modo, fare in modo che la vista root catturi l'attenzione quando l'utente fa clic su un bambino non focalizzabile?
nota173

Per quanto ne so, il focus non può essere gestito da una classe, è gestito da android ... nessuna idea :(
forumercio

0

Il modo migliore è utilizzare il metodo predefinito clearFocus()

Sai come risolvere i codici in modo onTouchListenercorretto?

Chiama e basta EditText.clearFocus(). Chiarirà la messa a fuoco last EditText.


0

Come suggerito da @pcans, puoi eseguire questa operazione ignorando la dispatchTouchEvent(MotionEvent event)tua attività.

Qui otteniamo le coordinate di tocco e le confrontiamo per visualizzare i limiti. Se il tocco viene eseguito al di fuori di una vista, fare qualcosa.

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

Inoltre non è necessario controllare l'esistenza e la visibilità delle viste se il layout della tua attività non cambia durante il runtime (ad esempio, non aggiungi frammenti o sostituisci / rimuovi viste dal layout). Ma se vuoi chiudere (o fare qualcosa di simile) il menu contestuale personalizzato (come nel Google Play Store quando si utilizza il menu di overflow dell'elemento) è necessario verificare l'esistenza della vista. Altrimenti otterrai un file NullPointerException.


0

Questo semplice frammento di codice fa quello che vuoi

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

A Kotlin

hidekeyboard () è un'estensione Kotlin

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

Nell'attività aggiungi 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)
}

Aggiungi queste proprietà nella parte superiore del genitore

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

0

Questa è la mia versione basata sul codice di zMan. Non nasconderà la tastiera se anche la vista successiva è un testo di modifica. Inoltre, non nasconderà la tastiera se l'utente scorre semplicemente lo schermo.

@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);
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.