EditText, четкий фокус на прикосновении снаружи


100

Мой макет содержит ListView, SurfaceViewи EditText. Когда я нажимаю на EditText, он получает фокус и появляется экранная клавиатура. Когда я щелкаю где-нибудь за пределами EditText, он все еще в фокусе (а не должен). Думаю, я мог бы настроить OnTouchListener's на других представлениях в макете и вручную сбросить EditTextфокус. Но кажется слишком хакерским ...

У меня такая же ситуация и в другом макете - в виде списка с разными типами элементов, некоторые из которых имеют EditTextвнутри. Они действуют так же, как я писал выше.

Задача - заставить EditTextпотерять фокус, когда пользователь касается чего-то вне его.

Я видел здесь похожие вопросы, но не нашел решения ...

Ответы:


67

Я пробовал все эти решения. edc598 был наиболее близок к работающему, но события касания не запускались на других, Viewсодержащихся в макете. Если кому-то нужно такое поведение, я в итоге сделал вот что:

Я создал (невидимый) FrameLayoutпод названием touchInterceptor в качестве последнего Viewв макете, чтобы он перекрывал все ( отредактируйте: вы также должны использовать a RelativeLayoutв качестве родительского макета и указать fill_parent атрибуты touchInterceptor ). Затем я использовал его для перехвата касаний и определения, было ли касание поверх EditTextили нет:

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

Верните false, чтобы не допустить обработки касания.

Это хаки, но это единственное, что у меня сработало.


21
Вы можете сделать то же самое, не добавляя дополнительный макет, переопределив dispatchTouchEvent (MotionEvent ev) в своей операции.
— pcans

спасибо за подсказку clearFocus (), она также сработала для меня в SearchView
— Джимми Иленлоа

1
Я хотел бы намекнуть на ответ @zMan ниже. Он основан на этом, но не требует представления stackoverflow.com/a/28939113/969016
— Мальчик

Кроме того, если кто-то сталкивается с ситуацией, когда клавиатура не скрывается, а hideSoftInputFromWindow()
— очищается

234

Основываясь на ответе Кена, вот самое модульное решение для копирования и вставки.

XML не требуется.

Поместите его в свою Activity, и он будет применяться ко всем EditTexts, включая те, которые находятся во фрагментах внутри этого действия.

@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
Вроде нормально. Тем не менее, у меня есть одна проблема: весь мой текст редактирования находится внутри режима прокрутки, а верхний текст редактирования всегда имеет видимый курсор. Даже когда я щелкаю снаружи, фокус теряется и клавиатура исчезает, но курсор все еще виден в верхнем тексте редактирования.
— Вайбхав Гупта

1
Лучшее решение, с которым я столкнулся !! Раньше я использовал @Override public boolean onTouch (View v, событие 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; }
— Прадип Кумар Кушваха

1
Одноразовое решение :)
— karthik kolanji

15
Лучшее решение, которое я никогда раньше не видел! Я сохраню этот код, чтобы передать его сыну и внуку.
— Fan Applelin

3
Если пользователь щелкает другой EditText, клавиатура немедленно закрывается и открывается снова. Для того, чтобы решить этот я изменил , MotionEvent.ACTION_DOWNчтобы MotionEvent.ACTION_UPна вашем коде. Кстати, отличный ответ!
— Thanasis1101

35

Для родительского представления EditText пусть следующие 3 атрибута будут « истинными »:
кликабельный , фокусируемый , focusableInTouchMode .

Если представление хочет получить фокус, оно должно удовлетворять этим трем условиям.

См. Android.view :

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

Надеюсь, поможет.



2
Чтобы очистить фокус, другой фокусируемый вид должен быть родительским для сфокусированного вида. Это не сработает, если представление, на которое нужно сфокусироваться, находится на другой иерархии.
— AxeEffect

Технически ваше утверждение неверно. Родительский взгляд должен быть (clickable) *OR* (focusable *AND* focusableInTouchMode);)
— Мартин Маркончини

25

Просто поместите эти свойства в самый верхний родительский.

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

Спасибо чувак. Я боролся с этой проблемой последние два часа. Добавление этих строк в верхний родительский элемент сработало.
— Däñish Shärmà

К вашему сведению: хотя он работает в большинстве случаев, он не работает для некоторых внутренних элементов макета.
— SV Мадхава Редди

17

Ответ Кена работает, но он хакерский. Как намекает pcans в комментарии к ответу, то же самое можно сделать с помощью dispatchTouchEvent. Это решение более чистое, так как оно позволяет избежать взлома XML с помощью прозрачного фиктивного FrameLayout. Вот как это выглядит:

@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

Для меня ниже работает -

1. Добавление android:clickable="true"и android:focusableInTouchMode="true"к parentLayoutо EditTextтandroid.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. Переопределение dispatchTouchEventв классе активности и hideKeyboard()функция вставки

@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. Добавление setOnFocusChangeListenerдля EditText

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

5

Вы, вероятно, уже нашли ответ на эту проблему, но я искал, как решить эту проблему, и до сих пор не могу найти именно то, что искал, поэтому я решил опубликовать его здесь.

Я сделал следующее (это очень обобщенно, цель - дать вам представление о том, как действовать, копирование и вставка всего кода не сработает O: D):

Сначала создайте EditText и любые другие представления, которые вы хотите использовать в своей программе, в одном представлении. В моем случае я использовал LinearLayout, чтобы обернуть все.

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

Затем в вашем коде вы должны установить Touch Listener для вашего основного 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;
        }
    });

Я надеюсь, что это поможет некоторым людям. Или, по крайней мере, помогает им начать решать свою проблему.


Да, я тоже пришел к подобному решению. Затем я портировал подмножество системы просмотра Android на c ++ на opengl и встроил в него эту функцию)
— note173

Это действительно очень хороший пример. Я искал такой код, но смог найти только сложные решения. Я использовал обе координаты XY, чтобы быть более точным, поскольку у меня также было несколько многострочных EditText. Спасибо!
— Sumitk

3

Просто определите два свойства родительского элемента EditTextкак:

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

Поэтому, когда пользователь коснется за пределами EditTextобласти, фокус будет удален, потому что фокус будет перенесен в родительский вид.


2

У меня есть ListViewмножество EditTextмнений. Сценарий гласит, что после редактирования текста в одной или нескольких строках мы должны щелкнуть по кнопке с названием «Готово». Я использовал onFocusChangedна EditTextвид изнутри , listViewно после нажатия на финише данные не сохраняются. Проблема решилась добавлением

listView.clearFocus();

внутри onClickListenerкнопки «Готово», и данные были успешно сохранены.


Спасибо. Вы сэкономили мое время, у меня был этот сценарий в recyclerview, и я терял последнее значение editText после нажатия кнопки, потому что последний фокус editText не изменился, и поэтому onFocusChanged не вызывается. Я хотел бы найти решение, при котором последний editText теряет фокус, когда находится вне его, при нажатии той же кнопки ... и найдите свое решение. Это сработало отлично!
— Рейхан Фаршбаф,

2

Я действительно думаю, что это более надежный способ использования, getLocationOnScreenчем getGlobalVisibleRect. Потому что я столкнулся с проблемой. Существует Listview, который содержит некоторый Edittext и установленный ajustpanв действии . Я нахожу getGlobalVisibleRectвозвращаемое значение, которое выглядит как включение в него scrollY, но event.getRawY всегда находится рядом с экраном. Приведенный ниже код работает хорошо.

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

Спасибо большое! Он работал для всех сценариев компоновки, таких как RecyclerView и т. Д. И т. Д. Я пробовал другое решение, но оно подходит всем! :) Прекрасная работа!
— Woppi

1

Чтобы потерять фокус при касании другого представления, оба представления должны быть установлены как view.focusableInTouchMode (true).

Но, похоже, фокусировки в сенсорном режиме использовать не рекомендуется. Взгляните сюда: http://android-developers.blogspot.com/2008/12/touch-mode.html


Да, я прочитал это, но это не совсем то, что мне нужно. Мне не нужен другой взгляд, чтобы сосредоточиться. Хотя сейчас я не вижу альтернативного пути ... Может быть, каким-то образом заставить корневое представление захватывать фокус, когда пользователь нажимает на не фокусируемого дочернего элемента?
— note173

Насколько я знаю, фокус не может управляться классом, им управляет android ... нет идей :(
— forumercio

0

Лучший способ - использовать метод по умолчанию clearFocus()

Вы умеете правильно разгадывать коды onTouchListener?

Просто позвони EditText.clearFocus(). Это очистит фокус в last EditText.


0

Как предложил @pcans, вы можете сделать это переопределением dispatchTouchEvent(MotionEvent event)в своей деятельности.

Здесь мы получаем координаты касания и сравниваем их с границами просмотра. Если касание выполняется вне поля зрения, сделайте что-нибудь.

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

Также нет необходимости проверять наличие и видимость представления, если макет вашей деятельности не изменяется во время выполнения (например, вы не добавляете фрагменты или не заменяете / не удаляете представления из макета). Но если вы хотите закрыть (или сделать что-то похожее) настраиваемое контекстное меню (например, в Google Play Store при использовании меню переполнения элемента), необходимо проверить наличие представления. В противном случае вы получите файл NullPointerException.


0

Этот простой фрагмент кода делает то, что вы хотите

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

В Котлине

hidekeyboard () - это расширение Kotlin

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

В активность добавить 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)
}

Добавьте эти свойства в самый верхний родительский

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

0

Это моя версия, основанная на коде zMan. Клавиатура не скроется, если следующий вид также является текстом редактирования. Он также не скроет клавиатуру, если пользователь просто прокручивает экран.

@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);
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.