EditText,将重点放在外部触摸上


100

我的布局包含ListViewSurfaceViewEditText。当我单击时EditText,它会收到焦点,并且会弹出屏幕键盘。当我点击以外的某个地方时EditText,它仍然具有焦点(应该没有)。我想我可以OnTouchListener在布局中的其他视图上设置,并手动清除EditText的焦点。但是似乎太客气了...

在其他布局中,我也有相同的情况-列表视图包含不同类型的项目,其中一些包含EditText。它们的行为就像我上面写的一样。

任务是EditText当用户触摸其外部的东西时使其失去焦点。

我在这里看到过类似的问题,但是还没有找到解决方案...

Answers:


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
您可以通过重写Activity中的dispatchTouchEvent(MotionEvent ev)来执行相同的操作而无需添加其他布局。
pcans

感谢clearFocus()暗示,这也为我工作在搜索查看
吉米Ilenloa

1
我想在下面暗示@zMan的答案。它基于此,但不需要查看stackoverflow.com/a/28939113/969016
男孩

另外,如果有人遇到键盘没有隐藏但清除焦点的情况,请先调用hideSoftInputFromWindow()然后清除焦点
Ahmet Gokdayi

229

在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

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(); 返回false;返回false;}
Pradeep Kumar Kushwaha

简单,简洁,实用!最好的解决方案。谢谢
Aleksandar

13
我从未见过的最佳解决方案!我将这段代码保存到要点中,以传递给我的儿子和孙子。
范·阿普林林'18

2
如果用户单击另一个EditText,则键盘将关闭并立即重新打开。为了解决这个问题,我改变MotionEvent.ACTION_DOWNMotionEvent.ACTION_UP你的代码。不错的答案!
Thanasis1101

35

对于EditText的父视图,将以下3个属性设置为“ true ”:
clickablefocusablefocusableInTouchMode

如果视图要获得焦点,则必须满足这三个条件。

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);)
马丁·马可西尼

24

只需将这些属性放在最上面的父级中即可。

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

谢啦。最近两个小时以来,我一直在努力解决这个问题。将这些行添加到最上层的父级行得通。
丹麦夏尔马

仅供参考:尽管它适用于大多数情况,但不适用于布局的某些内部元素。
SV Madhava Reddy

16

肯的​​答案行得通,但是很老套。正如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"parentLayoutEditText,即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. 覆盖dispatchTouchEventActivity类和插入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. 为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>

然后,在代码中,您必须将Touch Listener设置为主MainLinearLayout。

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 view系统移植到了opengl上的c ++并在其中建立了此功能)
note173 2011年

这确实是一个很好的例子。我一直在寻找这样的代码,但我只能找到复杂的解决方案。我也同时使用了XY坐标,因为我也很少有多行EditText。谢谢!
Sumitk

3

只需将其父级的两个属性定义EditText为:

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

因此,当用户触摸EditText区域外时,焦点将被删除,因为焦点将转移到父视图。


2

我有ListView很多EditText意见。该方案表明,在编辑一行或多行文本后,我们应单击一个名为“完成”的按钮。我onFocusChangedEditText里面的视图上使用过,listView但是单击完成后,数据没有保存。通过添加解决了问题

listView.clearFocus();

onClickListener“完成”按钮中,数据已成功保存。


谢谢。您节省了时间,我在recyclerview中遇到了这种情况,单击按钮后丢失了上一个editText值,因为上一个editText焦点没有更改,因此未调用onFocusChanged。我想找到一个解决方案,使最后一个editText在其外面时失去焦点,单击同一按钮...并找到您的解决方案。效果很好!
Reyhane Farshbaf,

2

我真的认为这是用更可靠的方法getLocationOnScreengetGlobalVisibleRect。因为我遇到了问题。有一个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 2011年

据我所知,焦点不能由一个类来管理,它是由android管理的...无主意:(
forumercio 2011年

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

在科特林

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);
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.