本文内容
- 可以添加蒙板来处理点击,以及如何把输入框顶到键盘上面
- 手动关闭键盘的时机问题
- EditText第一次点击不响应的解决方法
- 通过拦截Activity分发事件来判断此时点击的是EditText还是其他区域,并分别做处理的方法(主要是要说这个)
- 监听键盘打开的方法
- 一些其他的,关于EditText的焦点问题
更新:提供了一个判断键盘是否正处于弹出状态的方法,更新在4下面了,这个新的方法不能实时监听,但是优点在于不像
addOnGlobalLayoutListener
那样调用次数过多浪费性能
老样子,本文代码都是随便写的,想拿去用随便搜搜或者点链接,哪都有
参考链接以及问题描述
Android点击屏幕空白处,隐藏键盘 - yang_xing
总之就是点击没有EditText的地方就把键盘隐藏起来,根据这个思路我们也可以多做些别的
这里我搞了些多余的操作,因为我这边EditText的焦点不知道为什么怎么也取消不掉,关于这个问题文章末尾有提到
顺带一提,关闭键盘的时机最好在Activity的onDestroy
方法之前,在onDestroy时Activity已经不在窗口当中了,关闭键盘是要传入Context的,此时传入的和当前窗口中显示的自然不是同一个Activity了,会导致键盘无法关闭。当然,更省事的办法是在Manifest里面定义stateHidden
就好了,这表示进入此Activity时键盘默认会关闭,只有主动点击EditText等方法才会将其打开。
问题描述:
现在基本上绝大部分App都会有在调出键盘时点击屏幕任意区域将键盘取消掉的功能,方法各不相同吧,这个具体看设计。
下面我会写点别的,不感兴趣可以直接跳到下面的解决方案,都不感兴趣请直接点上面那个链接emm
分情况大概说一下(下面的代码是我随便写的):
- 添加蒙版,键盘上面是输入框,输入文字之后出现一层蒙板,点发送或者点蒙板都会让键盘消失。
View maskView = new View(getApplicationContext());
addContentView(maskView, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
maskView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
... // hide your keyboard or do something
}
});
想要输入框到键盘上面这事其实很简单,在输入框所在的布局文件的外层定义android:fitsSystemWindows="true"
就好了,这样键盘顶起来的时候会将最底下的输入框顶上去的。顺便一提,能滚动的布局请把设置成AdjustResize
,不能滚动的请设置成AdjustPan
,如果我记反了的话换了试试就好了emm
大概就是这种感觉android:windowSoftInputMode="adjustResize|stateHidden"
-
点了EditText弹出来一个Dialog用来输入内容
由于EditText在没焦点时设置OnClick之后第一件事是给它个焦点,于是你的点击事件就这么被消耗了,要避免这事的话几种方法- 鱼死网破法:给EditText设置
onFocusableInTouchMode = false
,设置完这个EditText就变成能点不能输的控件了,其实如果是弹个dialog另行输入的话这也不失为一个办法,顺便在父布局那里搞上descendantFocusability="blockDescendants"
也可以 - 曲线救国法:不设置
onClickListener
,然后设置onTouchListener
或者onFocusChangeListener
,思路大概就是因为第一次点击事件给focus消耗掉了,所以自然会出现focuschange,onTouch的话,DOWN被消耗掉了还有UP呢 - 偷天换日法:上面蒙层view,给那个view设置点击事件(感觉这个办法好蠢……)
- 画地为牢法:外面嵌套一层Layout,设置onIntercept
- 曲线救国和画地为牢的示例代码我补在后面,感兴趣可以翻过去看看
- 鱼死网破法:给EditText设置
-
屏幕里好大一张图,能点,下面是EditText,EditText旁边是用来发送的Button(类似我这的情况)
具体可以参考最上面的链接,我自己改了改用来处理我自己的情况
目的:在呼起键盘的时候,点击EditText和点击按钮有效,且如果没有输入的内容,键盘不会收下去。点击其他区域无效,键盘会收起来。
解决方案
思路:拦截Activity的事件分发,如果此时屏幕焦点在EditText上面(如果光标在闪就说明焦点在它那儿了),
具体来说的话,我希望上面的图不能点,但是EditText和右边的箭头都可以点,那么监听点击事件的位置,同时计算EditText的位置,如果点击事件位置在里面,那就调出键盘,再点什么也不做,点外面就把键盘收起来,其中上下的话,事件会被拦截,左右的话,事件可以继续处理。
public static boolean hideKeyboardByClick(MotionEvent event, View view, Activity activity) {
try {
if (view instanceof EditText) { // EditText获取焦点时
int[] location = { 0, 0 };
view.getLocationInWindow(location);
int top = location[1],
bottom = top + view.getHeight();
// 判断焦点位置坐标是否在空间内,如果位置在控件外,则隐藏键盘
if (event.getRawY() < top || event.getRawY() > bottom) {
// 在上下
hideSoftInputBoard(activity, null);
return true;
} // 在EditText的横向范围内,什么都不做
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
外面:(这里用了写在监听键盘那里的方法来做判断)
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
View view = getCurrentFocus();
if (isKeyboardShow && Utils.hideKeyboardByClick(ev, view, MainActivity.this)) { // 调用方法判断是否需要隐藏键盘
return true;
}
break;
default:
break;
}
return super.dispatchTouchEvent(ev);
}
如果你没什么好办法监测键盘的话,下面有个麻烦的方法,仅供参考:
再下面更新了一个靠谱一点的办法,下面这些可以跳过了
Android里面静态变量尽量少用,毕竟用起来逻辑很容易乱,又占空间,不过当作常量的final static倒是无所谓
// 用来记录键盘是否打开
public static boolean isEditSelected = false;
public static boolean hideKeyboard(MotionEvent event, View view,
Activity activity) {
try {
if (view instanceof EditText) { // EditText获取焦点时
int[] location = { 0, 0 };
view.getLocationInWindow(location);
int left = location[0], top = location[1], right = left
+ view.getWidth(), bottom = top + view.getHeight();
// 判断焦点位置坐标是否在空间内,如果位置在控件外,则隐藏键盘
if (!isEditSelected) {
// 没被选中的状态下,点哪里都有效
if (event.getRawY() > top && event.getRawY() < bottom
&& event.getRawX() > left && event.getRawX() < right) {
// 在EditText内,设置为选中状态
isEditSelected = true;
}
} else { // 如果在选中状态下,点击EditText的上下会隐藏键盘 + 失效,点击左右隐藏键盘但不失效,因为必定隐藏键盘
isEditSelected = false;
if (event.getRawY() < top || event.getRawY() > bottom) {
// 在上下
hideSoftInputBoard(activity, null);
return true;
} else if (event.getRawX() > left && event.getRawX() < right) {
// 在EditText内,什么都不做
isEditSelected = true;
return false;
}
// 不在上下,不在内部,但在左右,正常生效,但隐藏键盘
hideSoftInputBoard(activity, null);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
在这里说一下,如果键盘的弹出方式是将整个界面顶上去的话,最好用getRawY()
,因为键盘收取弹出会带来Padding的变化……
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
// 其实监听DOWN基本上就是监听所有触摸相关的事件了
View view = getCurrentFocus();
if (hideKeyboard(ev, view, MainActivity.this)) { // 调用方法判断是否需要隐藏键盘
// 这里返回true代表事件不再下发,意思是子控件不会响应点击事件
return true;
}
break;
default:
break;
}
return super.dispatchTouchEvent(ev);
}
除此之外,要考虑到EditText里面有内容时点击按钮是会收起键盘的,所以在按钮的点击事件里要把相应的值给调回去。
另外,要考虑到长按的事件,也要考虑到EditText在没有焦点时点上去的问题,总之上代码。
sendBtton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String message = editText.getText().toString();
if (Utils.isFastDoubleClick() || Utils.isEmpty(message)) {
// 上面这两个方法是我自己的,用来判断输入是否为空和是否连点两次
return;
}
Utils.isEditSelected = false;
Utils.hideSoftInputBoard(); // 收起键盘,和那个hideKeyBoard方法是两个不同的方法
... // 点击事件
}
});
editText.setOnTouchListener(new View.OnTouchListener() {
long preClickTime = 0;
@Override
public boolean onTouch(View v, MotionEvent event) {
if (MotionEvent.ACTION_DOWN == event.getAction()) {
preClickTime = System.currentTimeMillis();
}
if (MotionEvent.ACTION_UP == event.getAction()) {
if (System.currentTimeMillis() - preClickTime > 500) {
Utils.isEditSelected = false; // 记得改成自己的类
// 长按不打开键盘
return false;
}
Utils.isEditSelected = true; // 手从EditText上抬起,相当于点击
}
return false;
}
});
其实要收起键盘,输入法里面还自带个收键盘按钮……所以说
实际上这个方法有点蠢,要考虑的情况太多了,真要用还是和下面的监听键盘状态一起用会更好一些
or 如果谁有什么好办法监听键盘的话,欢迎提出~
更新:上面之前拿来判断键盘的是个boolean值吧,现在有个更好点的办法来判断键盘是不是在弹出状态了
思路来自github项目:
KeyboardVisibilityEvent
总体很简单,就是判断屏幕高度和当前显示的高度差了多少,我自己测了下,挺靠谱的
public boolean isKeyboardVisible(Activity activity) {
double KEYBOARD_MIN_HEIGHT_RATIO = 0.3;
Rect r = new Rect();
View activityRoot = getActivityRoot(activity);
activityRoot.getWindowVisibleDisplayFrame(r);
int screenHeight = activityRoot.getRootView().getHeight();
int heightDiff = screenHeight - r.height();
return heightDiff > screenHeight * KEYBOARD_MIN_HEIGHT_RATIO;
}
private View getActivityRoot(Activity activity) {
return ((ViewGroup) activity.findViewById(android.R.id.content)).getChildAt(0);
}
Android监听键盘状态
Android 监听键盘弹出收起 - 掘金
这个我懒得写太多,看链接就完事儿了,思路是监听View在窗口中的大小变化来判断键盘高度
Android点击空白区域收起键盘
这个是我把下面的内容整合了一下,自己写了一段
final View rootView = getActivity().getWindow().getDecorView();
globalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
private int rootViewVisibleHeight = 0;
@Override
public void onGlobalLayout() {
Rect r = new Rect();
rootView.getWindowVisibleDisplayFrame(r);
int visibleHeight = r.height();
System.out.println(""+visibleHeight);
if (rootViewVisibleHeight == 0) {
rootViewVisibleHeight = visibleHeight;
return;
}
//根视图显示高度没有变化,可以看作软键盘显示/隐藏状态没有改变
if (rootViewVisibleHeight == visibleHeight) {
return;
}
//根视图显示高度变小超过200,可以看作软键盘显示了
if (rootViewVisibleHeight - visibleHeight > 200) {
rootViewVisibleHeight = visibleHeight;
... // do whatever you want
return;
}
//根视图显示高度变大超过200,可以看作软键盘隐藏了
if (visibleHeight - rootViewVisibleHeight > 200) {
rootViewVisibleHeight = visibleHeight;
... // do whatever you want
}
}
};
rootView.getViewTreeObserver().addOnGlobalLayoutListener(globalLayoutListener);
这是一个会连续调用的方法,调用的时机是窗口中任何view重绘的时候,所以判断条件要全面一些,根据自己需要把具体方法放到具体情况底下
如果要计算滚动高度的话,屏幕高度 - (键盘高度 - EditText底部高度)就好了,要考虑StatusBar高度可以考虑用反射获取:
android开发:获取键盘的高度 - CSDN
随便找了一个,这个考虑到statusbar了
try {
Class<?> c = Class.forName("com.android.internal.R$dimen");
Object obj = c.newInstance();
Field field = c.getField("status_bar_height");
int x = Integer.parseInt(field.get(obj).toString());
statusBarHeight = context.getResources().getDimensionPixelSize(x);
} catch (Exception e) {
e.printStackTrace();
}
顺便,Listener不用时记得回收:
view.getViewTreeObserver().removeOnGlobalLayoutListener(globalLayoutListener);
这个最好注册给Activity,所以onPause和onDestroy时去掉就好了,onResume时可以考虑重新注册上。
如果只想使用一次,可以直接在方法里面remove掉,或者干脆点使用view的post()
方法
在这里补一下曲线救国和画地为牢法的代码
(没人忘了这俩是指啥吧……)
其实就随便写两行谁都会的代码而已:
- 曲线救国法
editText.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (MotionEvent.ACTION_UP == event.getAction()) {
... // do somethings
}
return false;
}
});
editText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus) {
... // do something
}
}
});
- 画地为牢法
public class EditIntercpetLayout extends FrameLayout {
public EditIntercpetLayout(@NonNull Context context) {
super(context);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
... // do something
// 这里的返回值true代表事件在这里消耗,即子控件不会再获取事件,如果不想这样,不修改返回值就好
return super.onInterceptTouchEvent(ev);
}
}
其他
关于EditText焦点的问题
总结一下,一般而言素质三连就好了
android:focusable="true"
,
android:focusableInTouchMode="true"
,
view.clearFocus()
需要注意的是那两条属性要配置在父布局里,否则的话focus清掉还会重新寻找焦点,如果没有父布局有这些属性,那focus就会回到EditText那里
如果在父布局中设置这些不生效的话:
这是我遇到的问题,最后的解决办法:
在EditText旁边创建一个不可见也基本不占位置的view,素质三连,然后在布局加载完成后requestFocus
就好了