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.