非对称式focus设置
EditText 在页面启动时一般会自动获取焦点,就会弹出软键盘。
如果不希望页面启动就自动弹出软键盘,可以对 EditText 如下设置:
EditText.setFocusable(false);
果然就OK了。不会自动弹出界面了。
但这样之后,再点击EditText就没有反应了,无法呼出软键盘。
于是,理所当然滴再对称地反向设置一下,觉得应该就OK了吧:
EditText.setFocusable(true);
结果发现没效果,点击EditText仍然无反应。神了。
难道这个API的参数是一次性的?改变后没法再设置回去?
原来要恢复focus,这样设置才行:
EditText.setFocusableInTouchMode(true);
下面简单看下根本原因 root cause,setFocusable(false)是怎么操作的。来看看View.java里的原生API:
public void setFocusable(boolean focusable) {
setFocusable(focusable ? FOCUSABLE : NOT_FOCUSABLE);
}
//实际调用生效在这里
public void setFocusable(@Focusable int focusable) {
if ((focusable & (FOCUSABLE_AUTO | FOCUSABLE)) == 0) {
setFlags(0, FOCUSABLE_IN_TOUCH_MODE);//这句是关键
}
setFlags(focusable, FOCUSABLE_MASK);
}
代码面前就没有秘密了。setFocusable(false)的时候,会调用成setFocusable(NOT_FOCUSABLE),然后 if ((focusable & (FOCUSABLE_AUTO | FOCUSABLE)) == 0) 这个条件就会成立,就把FOCUSABLE_IN_TOUCH_MODE这个标志清除了。
但后来setFocusable(true),这个 if ((focusable & (FOCUSABLE_AUTO | FOCUSABLE)) == 0) 是不成立的,所以就相当于单纯的一句setFlags(focusable, FOCUSABLE_MASK)。
这个说明一下,FOCUSABLE_MASK 是针对非触屏有效的,如android电视机。FOCUSABLE_IN_TOUCH_MODE 就是针对触屏设备,就是手机平板。
所以针对触屏设备要恢复 focus 的能力,需要用setFocusableInTouchMode(true)。看看源码:
public void setFocusableInTouchMode(boolean focusableInTouchMode) {
// Focusable in touch mode should always be set before the focusable flag
// otherwise, setting the focusable flag will trigger a focusableViewAvailable()
// which, in touch mode, will not successfully request focus on this view
// because the focusable in touch mode flag is not set
setFlags(focusableInTouchMode ? FOCUSABLE_IN_TOUCH_MODE : 0, FOCUSABLE_IN_TOUCH_MODE);
// Clear FOCUSABLE_AUTO if set.
if (focusableInTouchMode) {
// Clears FOCUSABLE_AUTO if set.
setFlags(FOCUSABLE, FOCUSABLE_MASK);
}
}
总结一下,
setFocusable
- true 开启,只针对非触屏有效
- false 关闭,对所有设备生效
setFocusableInTouchMode
- true 开启,对所有设备有效
- false 关闭,只针对触屏设备
所以这两个API是的参数设置是非对称的。