EditText, очистить фокус при касании снаружи

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

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

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

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

Ответ 1

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

Я создал (невидимый) FrameLayout, называемый touchInterceptor, как последний View в макете, чтобы он накладывал все (edit:), а также использовать RelativeLayout в качестве родительского макет и атрибуты touchInterceptor fill_parent). Затем я использовал его для перехвата касаний и определения, было ли касание поверх 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, чтобы обработать сенсорную обработку.

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

Ответ 2

Создание ответа от Ken, здесь самое модульное решение для копирования и вставки.

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

Поместите его в свою активность, и он применим ко всем файлам 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 );
}

Ответ 3

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

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

См. android.view:

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

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

Ответ 4

Кен отвечает, но он взломан. Как отмечают 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);
}

Ответ 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;
        }
    });

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

Ответ 6

Я действительно считаю это более надежным способом использования getLocationOnScreen, чем getGlobalVisibleRect. Потому что я встречаюсь с проблемой. Существует Listview, в котором содержатся некоторые Edittext и set 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())));
}

Ответ 7

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

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

Ответ 8

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

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

Ответ 9

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

listView.clearFocus();

внутри onClickListener для кнопки "закончить", и данные были успешно сохранены.

Ответ 10

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

Вы знаете, как правильно решать коды в onTouchListener?

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

Ответ 11

Как показано в @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.

Ответ 12

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

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

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

Ответ 13

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

1. Добавление android:clickable="true" и android:focusableInTouchMode="true" в parentLayout of EditText i.e 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 в классе Activity и вставка функции 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);
                }
            }
        });