EditText, 외부 터치에 대한 명확한 초점


100

내 레이아웃에는 ListView, SurfaceViewEditText. 를 클릭하면 EditText포커스를 받고 화상 키보드가 나타납니다. 외부 어딘가를 클릭 EditText해도 여전히 포커스가 있습니다 (안됨). OnTouchListener레이아웃의 다른 뷰에서의를 설정하고 EditText의 포커스를 수동으로 지울 수 있다고 생각 합니다. 하지만 너무 끔찍해 보입니다 ...

다른 레이아웃에서도 동일한 상황이 발생합니다. 다른 유형의 항목이있는 목록보기 중 일부는 EditText내부에 '가 있습니다. 위에서 쓴 것처럼 행동합니다.

작업은 EditText사용자가 외부의 무언가를 만질 때 초점 을 잃게 만드는 것입니다.

여기에서 비슷한 질문을 보았지만 해결책을 찾지 못했습니다 ...

답변:


66

이 모든 솔루션을 시도했습니다. edc598은 작동에 가장 가깝지만 터치 이벤트는 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;
    }
});

터치 처리가 실패하도록하려면 false를 반환합니다.

그것은 해키이지만 나에게 일한 유일한 것입니다.


20
활동에서 dispatchTouchEvent (MotionEvent ev)를 재정 의하여 추가 레이아웃을 추가하지 않고도 동일한 작업을 수행 할 수 있습니다.
pcans

clearFocus () 힌트 덕분에 SearchView에서도 저에게 효과적이었습니다.
지미 Ilenloa

1
아래 @zMan의 답변에 힌트를주고 싶습니다. 이를 기반으로하지만 뷰가 필요하지 않습니다. stackoverflow.com/a/28939113/969016
Boy

또한 키보드가 숨겨지지 않고 포커스가 hideSoftInputFromWindow()지워지 는 상황이 발생하면 먼저 호출 한 다음 포커스를 지 웁니다.
Ahmet Gokdayi

230

Ken의 답변을 바탕으로 가장 모듈화 된 복사 및 붙여 넣기 솔루션이 있습니다.

XML이 필요하지 않습니다.

그것을 당신의 활동에 넣으면 해당 활동 내의 조각을 포함하여 모든 EditText에 적용됩니다.

@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
괜찮아 보인다. 여전히 한 가지 문제가 있습니다. 모든 편집 텍스트는 스크롤 뷰 안에 있고 상단 편집 텍스트에는 항상 커서가 표시됩니다. 외부를 클릭해도 포커스가 사라지고 키보드가 사라지지만 커서는 여전히 최상위 편집 텍스트에 표시됩니다.
Vaibhav Gupta 2015 년

1
내가 만난 최고의 솔루션 !! 이전에는 @Override public boolean onTouch (View v, MotionEvent event) {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

간단하고 간결하며 기능적입니다! 확실히 최고의 솔루션입니다. 감사합니다
알렉산다르

13
내가 본 적이없는 최고의 솔루션! 이 코드를 요점에 저장하여 아들과 손자에게 전달하겠습니다.
Fan Applelin

2
사용자가 다른 EditText를 클릭하면 키보드가 닫히고 즉시 다시 열립니다. 이 문제를 해결하기 위해 코드를 변경 MotionEvent.ACTION_DOWN했습니다 MotionEvent.ACTION_UP. 좋은 대답 btw!
Thanasis1101

35

EditText의 상위 뷰에 대해 clickable , focusable , focusableInTouchMode의 3 가지 속성을 " true "로 설정 합니다.

뷰가 포커스를 받으려면 다음 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 2014 년

기술적으로 귀하의 진술이 잘못되었습니다. 부모보기는 (clickable) *OR* (focusable *AND* focusableInTouchMode);)
Martin Marconcini

24

이 속성을 최상위 상위 항목에 넣으십시오.

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

감사합니다. 나는 지난 두 시간 동안이 문제로 고생했습니다. 이 줄을 상위 부모에 추가하면 효과가 있습니다.
Däñish Shärmà

참고 : 대부분의 경우 작동하지만 레이아웃의 일부 내부 요소에는 작동하지 않습니다.
SV Madhava Reddy

17

Ken의 대답은 작동하지만 해키입니다. pcans가 답변의 주석에서 언급했듯이 dispatchTouchEvent에서도 동일한 작업을 수행 할 수 있습니다. 이 솔루션은 투명한 더미 FrameLayout으로 XML을 해킹하지 않아도되므로 더 깔끔합니다. 다음은 다음과 같습니다.

@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"받는 사람 parentLayoutEditTextandroid.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. Activity 클래스 재정의 dispatchTouchEventhideKeyboard()함수 삽입

@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. EditText 추가setOnFocusChangeListener

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 뷰 시스템의 하위 집합을 OpenGL의 C ++로 포팅하고이 기능을 빌드했습니다.)
note173

이것은 정말 아주 깔끔한 예입니다. 나는 이와 같은 코드를 찾고 있었지만 내가 찾을 수있는 것은 복잡한 솔루션 뿐이었다. 여러 줄 EditText가 거의 없었기 때문에 XY 좌표를 더 정확하게 사용했습니다. 감사!
Sumitk

3

다음 EditText과 같이 부모의 두 속성을 정의하기 만하면 됩니다.

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

따라서 사용자가 EditText영역 외부를 터치 하면 포커스가 상위 뷰로 전환되므로 포커스가 제거됩니다.


2

나는 견해 로 ListView구성되어 EditText있습니다. 시나리오는 하나 이상의 행에서 텍스트를 편집 한 후 "마침"이라는 버튼을 클릭해야한다고 말합니다. 내부보기 onFocusChanged에서 사용 했지만 완료를 클릭하면 데이터가 저장되지 않습니다. 문제는 다음을 추가하여 해결되었습니다.EditTextlistView

listView.clearFocus();

내부 onClickListener"마침"버튼과 데이터를 성공적으로 저장되었습니다.


감사. 당신은 내 시간을 절약했고, 나는 recyclerview 에서이 시나리오를 가지고 있었고 버튼을 클릭 한 후 마지막 editText 값을 잃어 버렸습니다. 마지막 editText 포커스가 변경되지 않았기 때문에 onFocusChanged가 호출되지 않았기 때문입니다. 마지막 editText가 외부에있을 때 포커스를 잃게 만드는 솔루션을 찾고 싶습니다. 동일한 버튼을 클릭하고 솔루션을 찾습니다. 훌륭하게 작동했습니다!
Reyhane Farshbaf

2

난 정말이 사용하는 더 강력한 방법 생각 getLocationOnScreen보다 getGlobalVisibleRect. 문제가 발생했기 때문입니다. 일부 Edittext를 포함 ajustpan하고 활동에 설정된 Listview가 있습니다 . 내가 찾아 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

내가 아는 한, 초점은 수업에서 관리 할 수없고, 안드로이드에 의해 관리됩니다 ... 아이디어 없음 :(
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

Kotlin에서

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);
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.