EditText, чіткий фокус на дотику зовні


100

Моя розкладка містить ListView, SurfaceViewі EditText. Коли я натискаю на кнопку EditText, вона отримує фокус, і екранна клавіатура спливає. Коли я натискаю десь поза межами EditText, на ньому все ще залишається фокус (він не повинен). Я думаю, я міг би налаштувати OnTouchListenerна інші погляди в макеті та очистити EditTextфокус вручну . Але здається занадто хакерським ...

У мене також така ж ситуація в іншому макеті - переліку списку з різними типами елементів, деякі з яких знаходяться EditTextвсередині. Вони діють так само, як я писав вище.

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

Тут я бачив подібні запитання, але не знайшов жодного рішення ...

Відповіді:


66

Я спробував усі ці рішення. edc598's був найближчим до роботи, але сенсорні події не спрацьовували на інших елементах, Viewрозміщених у макеті. У випадку, якщо комусь потрібна така поведінка, я закінчив це:

Я створив (невидимий) FrameLayoutпід назвою touchInterceptor як останній Viewу макеті, щоб він накладав усе ( редагувати: ви також повинні використовувати 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;
    }
});

Поверніть помилково, щоб дотик не пропускав.

Це хакі, але це єдине, що працювало на мене.


20
Ви можете зробити те ж саме, не додаючи додатковий макет, перемістивши dispatchTouchEvent (MotionEvent ev) у своїй діяльності.
pcans

дякую за підказку clearFocus (), вона також працювала для мене на SearchView
Джиммі Іленлоа

1
Я хотів би натякнути на відповідь @ zMan нижче. Він заснований на цьому, але не потребує перегляду stackoverflow.com/a/28939113/969016
Хлопчик

Крім того, якщо хтось стикається з ситуацією, за якою клавіатура не приховує, а очищає фокус, спочатку hideSoftInputFromWindow()
зверніться,

230

Спираючись на відповідь Кена, ось найбільш модульне рішення для копіювання та вставки.

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

12
Здається чудово. Все-таки у мене є одна проблема, весь текст моєї редагування знаходиться у вікні прокрутки, а текст верхнього редагування завжди повинен бути курсором. Навіть коли я натискаю назовні, фокус втрачається, а клавіатура відпадає, але курсор усе ще видно у верхньому редагуванні тексту.
Вайбхав Гупта

1
Найкраще рішення, на яке я потрапив !! Раніше я використовував @Override загальнодоступний булінг onTouch (View v, подія MotionEvent) {if (v.getId ()! = Search_box.getId ()) {if (search_box.hasFocus ()) {InputMethodManager im = (InputMethodManager) getSystemService ( Контекст.INPUT_METHOD_SERVICE); im.hideSoftInputFromWindow (search_box.getWindowToken (), 0); search_box.clearFocus (); } повернути помилкове; } повернути помилкове; }
Pradeep Kumar Kushwaha

Простий, стислий, функціональний! Краще рішення точно. Спасибі
Олександр

13
Найкраще рішення, якого я ніколи не бачив! Я збережу цей код у своїй суті, щоб передати моєму синові та онуку.
Вболівальник Applelin

2
Якщо користувач натискає інший EditText, клавіатура закривається та знову відкривається. Для того, щоб вирішити це я змінив , MotionEvent.ACTION_DOWNщоб MotionEvent.ACTION_UPна вашому коді. Чудова відповідь btw!
Thanasis1101

35

Для батьківського подання EditText нехай наступні 3 атрибути є " true" »:
інтерактивний , фокусируемое , focusableInTouchMode .

Якщо погляд хоче отримати фокус, він повинен відповідати цим 3 умовам.

Дивіться 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);)
Мартін Маркончіні

24

Просто поставте ці властивості в самий верхній з батьків.

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

дякую людині. я боровся з цим питанням протягом останніх двох годин. Додавання цих рядків у верхній з батьків працює.
Däñish Shärmà

FYI: Хоча це працює в більшості випадків, він не працює для компонування деяких внутрішніх елементів.
С. В. Мадхава Редді

17

Відповідь Кена працює, але це хакі. Як наголошують учасники коментарів у коментарі відповіді, те саме можна зробити і з 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. Додавання setOnFocusChangeListenerEditText

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>

Тоді у своєму коді потрібно встановити сенсорний слухач для вашого основного 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. Дякую!
Сумітк

3

Просто визначте два властивості батьківського, EditTextяк:

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

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


2

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

listView.clearFocus();

всередині кнопки onClickListener"закінчити", і дані були успішно збережені.


Дякую. Ви заощадили мій час, у мене був такий сценарій у перегляді рециркулятора і втрачав останнє значення editText після натискання на кнопку, тому що фокус останнього редагуванняText не змінювався, і так даліFocusChanged не закликався. Я хотів би знайти рішення, завдяки якому останнє редагуванняText втратить фокус, коли поза ним, натисканням тієї самої кнопки ... і знайдіть своє рішення. Це, чудово працювало!
Рейхане Фаршбаф

2

Я дійсно думаю, що це більш надійний спосіб використання getLocationOnScreen ніж getGlobalVisibleRect. Тому що я зустрічаю проблему. Існує перегляд списку, який містить деякий редакційний текст і встановлений ajustpanу дії. Я знаходжу getGlobalVisibleRectповернення значення, яке схоже на включення в нього прокруткиY, але 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 тощо. Я пробував інше рішення, але цей підходить усім! :) Чудова робота!
Вуппі

1

Щоб втратити фокус при дотику до іншого виду, обидва погляди слід встановити як view.focusableInTouchMode (true).

Але здається, що використовувати фокуси в сенсорному режимі не рекомендується. Погляньте тут: http://android-developers.blogspot.com/2008/12/touch-mode.html


Так, я це прочитав, але це не зовсім те, що я хочу. Мені не потрібен інший погляд, щоб бути зосередженим. Хоча зараз я не бачу жодного альтернативного способу ... Можливо, якимось чином зробити корінний вид, щоб захопити фокус, коли користувач натискає на нефокусовану дитину?
примітка173

Наскільки я знаю, фокусом не можна керувати клас, ним керує 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 при використанні переливного меню цього пункту), необхідно перевірити наявність перегляду. Інакше ви отримаєте 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

У Котліні

hidkeyboard () - розширення 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.