EditText, jelaskan fokus pada sentuhan di luar

100

Tata letak saya berisi ListView, SurfaceViewdan EditText. Ketika saya mengkliknya EditText, itu menerima fokus dan keyboard di layar muncul. Saat saya mengklik di suatu tempat di luar EditText, itu masih memiliki fokus (seharusnya tidak). Saya kira saya bisa mengatur OnTouchListenertampilan lain dalam tata letak dan secara manual menghapus EditTextfokus. Tapi sepertinya terlalu hackish ...

Saya juga memiliki situasi yang sama di layout lain - tampilan daftar dengan berbagai jenis item, beberapa di antaranya ada EditTextdi dalamnya. Mereka bertindak seperti yang saya tulis di atas.

Tugasnya adalah membuat EditTextkehilangan fokus saat pengguna menyentuh sesuatu di luarnya.

Saya telah melihat pertanyaan serupa di sini, tetapi belum menemukan solusi apa pun ...

catatan173
sumber

Jawaban:

66

Saya mencoba semua solusi ini. edc598 adalah yang paling dekat untuk bekerja, tapi kejadian sentuh tidak memicu kejadian lain Viewyang ada dalam tata letak. Jika ada yang membutuhkan perilaku ini, inilah yang akhirnya saya lakukan:

Saya membuat (tidak terlihat) yang FrameLayoutdisebut touchInterceptor sebagai yang terakhir Viewdalam tata letak sehingga menutupi semuanya ( edit: Anda juga harus menggunakan a RelativeLayoutsebagai tata letak induk dan memberikan atribut touchInterceptor fill_parent ). Kemudian saya menggunakannya untuk mencegat sentuhan dan menentukan apakah sentuhan itu ada di atas EditTextatau tidak:

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

Kembalikan false agar handel touch gagal.

Ini hacky, tapi itu satu-satunya yang berhasil untuk saya.

Ken
sumber
20
Anda dapat melakukan hal yang sama tanpa menambahkan tata letak tambahan dengan mengganti dispatchTouchEvent (MotionEvent ev) dalam aktivitas Anda.
pcans
terima kasih atas petunjuk clearFocus (), ini juga berfungsi untuk saya di
SearchView
1
Saya ingin mengisyaratkan jawaban @ zMan di bawah ini. Ini didasarkan pada ini, tetapi tidak memerlukan tampilan stackoverflow.com/a/28939113/969016
Boy
Selain itu, jika ada yang menghadapi situasi di mana keyboard tidak bersembunyi tetapi fokus membersihkan, pertama-tama aktifkan hideSoftInputFromWindow()lalu hapus fokus
Ahmet Gokdayi
231

Berdasarkan jawaban Ken, inilah solusi salin dan tempel yang paling modular.

Tidak perlu XML.

Letakkan di Aktivitas Anda dan itu akan berlaku untuk semua EditText termasuk yang ada di dalam fragmen di dalam aktivitas itu.

@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
sumber
12
Sepertinya baik-baik saja. Masih saya mengalami satu masalah, Semua teks edit saya berada di dalam tampilan gulir dan teks edit teratas selalu memiliki kursor yang terlihat. Bahkan ketika saya mengklik di luar, fokus hilang dan keyboard hilang, tetapi kursor masih terlihat di teks edit atas.
Vaibhav Gupta
1
Solusi Terbaik yang saya temui !! Sebelumnya saya menggunakan @Override public boolean onTouch (View v, event MotionEvent) {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
Solusi satu tembakan :)
karthik kolanji
13
Solusi terbaik yang belum pernah saya lihat sebelumnya! Saya akan menyimpan kode ini ke dalam inti saya untuk diteruskan kepada putra dan cucu saya.
Fan Applelin
2
Jika pengguna mengklik EditText lain maka keyboard akan menutup dan segera terbuka kembali. Untuk mengatasi Saya ini berubah MotionEvent.ACTION_DOWNke MotionEvent.ACTION_UPpada kode Anda. Jawaban yang bagus btw!
Thanasis1101
35

Untuk tampilan induk EditText, biarkan 3 atribut berikut menjadi " true ":
clickable , focusable , focusableInTouchMode .

Jika tampilan ingin menerima fokus, itu harus memenuhi 3 kondisi ini.

Lihat android.view :

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

Semoga membantu.

suber
sumber
2
Untuk membersihkan fokus, tampilan yang dapat difokuskan lainnya harus menjadi induk dari tampilan fokus. Ini tidak akan berfungsi jika tampilan yang akan difokuskan berada pada hierarki lain.
AxeEffect
Secara teknis, pernyataan Anda salah. Tampilan orang tua harus (clickable) *OR* (focusable *AND* focusableInTouchMode);)
Martin Marconcini
25

Letakkan saja properti ini di induk paling atas.

android:focusableInTouchMode="true"
android:clickable="true"
android:focusable="true" 
chandan
sumber
Terima kasih sobat. saya berjuang masalah ini dari dua jam terakhir. Menambahkan baris ini di induk atas berhasil.
Däñish Shärmà
FYI: Meskipun berfungsi untuk sebagian besar kasus, ini tidak berfungsi untuk beberapa elemen dalam tata letak.
SV Madhava Reddy
17

Jawaban Ken berhasil, tapi itu hacky. Seperti yang disinggung pc dalam komentar jawaban, hal yang sama dapat dilakukan dengan dispatchTouchEvent. Solusi ini lebih bersih karena menghindari keharusan meretas XML dengan FrameLayout tiruan yang transparan. Seperti inilah tampilannya:

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

Untuk Saya Di Bawah Hal-hal yang Berhasil -

1. Menambah android:clickable="true"dan android:focusableInTouchMode="true"ke parentLayoutdari EditTextyaituandroid.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. Mengesampingkan dispatchTouchEventdi kelas Aktivitas dan menyisipkan hideKeyboard()fungsi

@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. Menambahkan setOnFocusChangeListeneruntuk EditText

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

Anda mungkin sudah menemukan jawaban untuk masalah ini, tetapi saya telah mencari cara untuk menyelesaikannya dan masih tidak dapat benar-benar menemukan apa yang saya cari, jadi saya pikir saya akan mempostingnya di sini.

Apa yang saya lakukan adalah sebagai berikut (ini sangat umum, tujuannya adalah memberi Anda gambaran tentang bagaimana melanjutkan, menyalin dan menempel semua kode tidak akan berfungsi O: D):

Pertama memiliki EditText dan tampilan lain yang Anda inginkan dalam program Anda dibungkus oleh satu tampilan. Dalam kasus saya, saya menggunakan LinearLayout untuk membungkus semuanya.

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

Kemudian dalam kode Anda, Anda harus menyetel Touch Listener ke LinearLayout utama Anda.

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

Saya harap ini membantu beberapa orang. Atau setidaknya membantu mereka mulai memecahkan masalah mereka.

edc598.dll
sumber
Ya, saya juga menemukan solusi serupa. Kemudian saya mem-porting subset dari sistem tampilan android ke c ++ pada opengl dan membangun fitur ini di dalamnya)
note173
Ini benar-benar contoh yang sangat bagus. Saya mencari kode seperti ini tetapi yang dapat saya temukan hanyalah solusi yang kompleks. Saya menggunakan kedua koordinat XY agar lebih tepat karena saya juga memiliki beberapa EditText multi-baris. Terima kasih!
Sumitk
3

Cukup tentukan dua properti induknya EditTextsebagai:

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

Jadi saat pengguna akan menyentuh di luar EditTextarea, fokus akan terhapus karena fokus akan dialihkan ke tampilan induk.

Dhruvam Gupta
sumber
2

Saya telah a ListViewterdiri dari EditTextpandangan. Skenario mengatakan bahwa setelah mengedit teks dalam satu baris atau lebih, kita harus mengklik tombol yang disebut "selesai". Saya menggunakan onFocusChangedpada EditTexttampilan dalam listViewtapi setelah mengklik finish data tidak disimpan. Masalahnya diselesaikan dengan menambahkan

listView.clearFocus();

di dalam onClickListeneruntuk tombol "selesai" dan data berhasil disimpan.

Muhannad A. Alhariri
sumber
Terima kasih. Anda menghemat waktu saya, saya memiliki skenario ini di recyclerview dan kehilangan nilai editText terakhir setelah mengklik tombol, karena fokus editText terakhir tidak berubah dan onFocusChanged tidak dipanggil. Saya ingin menemukan solusi yang membuat editText terakhir akan kehilangan fokus saat berada di luarnya, klik tombol yang sama ... dan temukan solusi Anda. Itu bekerja dengan baik!
Reyhane Farshbaf
2

Saya benar-benar berpikir ini cara yang lebih kuat untuk digunakan getLocationOnScreendaripada getGlobalVisibleRect. Karena saya menemui masalah. Ada Listview yang berisi beberapa Edittext dan dan diatur ajustpandalam aktivitas. Saya menemukan getGlobalVisibleRectkembali nilai yang terlihat seperti termasuk scrollY di dalamnya, tetapi event.getRawY selalu dekat layar. Kode di bawah ini berfungsi dengan baik.

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
sumber
Terima kasih banyak! Ini bekerja untuk semua skenario tata letak seperti RecyclerView, dll. Saya mencoba solusi lain tetapi yang ini cocok untuk semua! :) Kerja bagus!
Woppi
1

Untuk kehilangan fokus saat tampilan lain disentuh, kedua tampilan harus disetel sebagai view.focusableInTouchMode (true).

Namun tampaknya penggunaan fokus dalam mode sentuh tidak disarankan. Silakan lihat di sini: http://android-developers.blogspot.com/2008/12/touch-mode.html

forumercio
sumber
Ya, saya telah membaca ini, tetapi sebenarnya bukan itu yang saya inginkan. Saya tidak membutuhkan pandangan lain untuk menjadi fokus. Meskipun saya tidak melihat cara alternatif saat ini ... Mungkin, entah bagaimana, membuat tampilan root untuk menangkap fokus saat pengguna mengklik anak yang tidak dapat difokuskan?
note173
Sejauh yang saya tahu, fokus tidak dapat dikelola oleh kelas, itu dikelola oleh android ... tidak ada ide :(
forumercio
0

Cara terbaik adalah menggunakan metode default clearFocus()

Anda tahu cara memecahkan kode dalam onTouchListener benar?

Telepon saja EditText.clearFocus(). Ini akan memperjelas fokus last EditText.

Menara Huy
sumber
0

Seperti yang disarankan @pcans, Anda dapat melakukan penggantian ini dispatchTouchEvent(MotionEvent event) dalam aktivitas Anda.

Di sini kita mendapatkan koordinat sentuh dan membandingkannya untuk melihat batas. Jika sentuhan dilakukan di luar pandangan, lakukan sesuatu.

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

Juga tidak perlu memeriksa keberadaan tampilan dan visibilitas jika tata letak aktivitas Anda tidak berubah selama waktu proses (misalnya, Anda tidak menambahkan fragmen atau mengganti / menghapus tampilan dari tata letak). Tetapi jika Anda ingin menutup (atau melakukan sesuatu yang serupa) menu konteks kustom (seperti di Google Play Store saat menggunakan menu luapan item), perlu untuk memeriksa keberadaan tampilan. Jika tidak, Anda akan mendapatkan file NullPointerException.

Pedang
sumber
0

Potongan kode sederhana ini melakukan apa yang Anda inginkan

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

Di Kotlin

hidekeyboard () adalah Ekstensi Kotlin

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

Dalam aktivitas, tambahkan 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)
}

Tambahkan properti ini di paling atas induk

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

Ini adalah Versi saya berdasarkan kode zMan. Itu tidak akan menyembunyikan keyboard jika tampilan berikutnya juga merupakan teks edit. Itu juga tidak akan menyembunyikan keyboard jika pengguna hanya menggulir layar.

@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.
sumber