我的布局包含ListView
,SurfaceView
和EditText
。当我点击EditText
时,它会获得焦点并弹出屏幕键盘。当我点击EditText
之外的某个地方时,它仍然有焦点(它不应该)。
我想我可以在布局中的其他视图上设置OnTouchListener
,并手动清除EditText
的焦点。但似乎过于苛刻......
我在其他布局中也有相同的情况 - 列表视图包含不同类型的项目,其中一些项目内部有EditText
。他们就像我上面写的那样行事。
当用户触摸外面的某些内容时,任务是让EditText
失去焦点。
我在这里看到过类似的问题,但没有找到任何解决方案......
答案 0 :(得分:182)
基于Ken的回答,这是最模块化的复制粘贴解决方案。
不需要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 );
}
答案 1 :(得分:63)
我尝试了所有这些解决方案。 edc598是最接近工作的,但触摸事件没有触发布局中包含的其他View
。如果有人需要这种行为,这就是我最终做的事情:
我创建了一个名为 touchInterceptor 的(不可见)FrameLayout
作为布局中的最后一个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以让触摸处理失效。
这很hacky,但它是唯一对我有用的东西。
答案 2 :(得分:32)
对于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();
}
...
}
...
}
希望它有所帮助。
答案 3 :(得分:17)
只需将这些属性放在最顶级的父级中。
android:focusableInTouchMode="true"
android:clickable="true"
android:focusable="true"
答案 4 :(得分:15)
肯的答案有效,但它很黑。正如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);
}
答案 5 :(得分: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设置Touch Listener。
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 :(得分:3)
对于我的工作 -
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. 覆盖Activity类中的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. 为EditText添加setOnFocusChangeListener
EmployeeId.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (!hasFocus) {
hideKeyboard(v);
}
}
});
答案 7 :(得分: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())));
}
答案 8 :(得分:1)
要在触摸其他视图时失去焦点,两个视图都应设置为view.focusableInTouchMode(true)。
但似乎不建议在触摸模式下使用焦点。 请看这里: http://android-developers.blogspot.com/2008/12/touch-mode.html
答案 9 :(得分:1)
我有ListView
个EditText
次观看。该场景说,在一行或多行中编辑文本后,我们应该点击一个名为“完成”的按钮。我在onFocusChanged
内的EditText
视图中使用了listView
,但点击完成后数据未保存。通过添加
listView.clearFocus();
在“完成”按钮的onClickListener
内,数据已成功保存。
答案 10 :(得分:1)
只需将EditText
的父级的两个属性定义为:
android:clickable="true"
android:focusableInTouchMode="true"
因此,当用户触摸EditText
区域之外时,焦点将被删除,因为焦点将转移到父视图。
答案 11 :(得分:1)
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"
答案 12 :(得分:1)
这是我基于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);
}
答案 13 :(得分:0)
最好的方法是使用默认方法clearFocus()
您知道如何解决onTouchListener
中的代码吗?
只需致电EditText.clearFocus()
即可。它将清晰地关注last EditText
。
答案 14 :(得分: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
。
答案 15 :(得分: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));