什么是焦点
简单一点理解,在移动应用中,焦点就是当前正在处理事件的位置。在手机应用中,最有可能用到焦点的就是EditText,如果同一个界面中有多个EditText,通常情况下同一时间只有一个能够输入内容,此时,这个EditText就获取了焦点。
在Android中,对焦点的设置分为两种情况,TouchMode和非TouchMode。现在的手机基本都是触摸屏,我们用手指触摸屏幕来操作Android应用时,处于TouchMode。除了TouchMode之外,还有非TouchMode,利用外接设备来操作应用。比如键盘。使用Genymotion模拟器的时候,一个界面上有多个控件时,可以用电脑tab键来进行移动,被选中的控件会高亮显示,这时候就是非TouchMode,被选中的控件获得了焦点。
焦点的使用
在手机应用中,用到焦点的时候并不多,但是TV应用中,需要用遥控器来操作选中控件,这时候就需要对焦点进行处理了。关于焦点,常用方法如下:
isFocusableInTouchMode() //在TouchMode中是否能够获取焦点
isFocusable()//是否能获取焦点
setFocusableInTouchMode(boolean) //设置在TouchMode中是否能够获取焦点
setFocusable(boolean) //设置是否能获取焦点
setOnFocusChangeListener()//设置焦点变化监听
OnFocusChangeListener()
requestFocus() //获取焦点
isFocused()//是否获取到焦点
在View类中,isFocusable()
和isFocusableInTouchMode()
获取到的结果都是false,也就是说,直接继承自View的控件是不能获取焦点的。我们常用控件中对这两个方法进行了改写,比如EditText,这两个方法都是true,而Button则只有isFocusable()
返回true。这也就是为什么我们用tab键选取Button的时候能够高亮显示,而鼠标点击(模拟触控)的时候不能高亮显示的原因了。如果想在点击的时候也能高亮显示Button,需要手动设置setFocusableInTouchMode(true)
,就可以了。
如果想对控件的焦点状态进行监听,需要设置setOnFocusChangeListener()
,只要控件的焦点状态发生变化(获得或者失去焦点),都会调用onFocusChange
方法
关于焦点的移动,默认的算法会寻找指定方向上最近的可以获取焦点的元素(非TouchMode)。另外在创建控件的时候,也可以指定寻找焦点的方向,设置nextFocusDown、nextFocusLeft、nextFocusRight 和 nextFocusUp的值为指定元素就可以了。看以下例子:
<LinearLayout
android:orientation="vertical"
... >
<Button android:id="@+id/top"
android:nextFocusUp="@+id/bottom"
... />
<Button android:id="@+id/bottom"
android:nextFocusDown="@+id/top"
... />
</LinearLayout>
这里指定了上面的button向上寻找焦点时,下一个元素是id为bottom的元素,也就是说,上面的Button在获取了焦点之后,继续按向上键,系统会将焦点移动到id为bottom的元素上,而不是继续向上。
焦点与事件
在开发手机应用的过程中,对焦点的处理并不多,它与事件是两个不同的体系,通常情况下焦点和事件是相互独立并不冲突。但是在Button的点击事件中会有一点问题。如果我们队一个button设置了setFocusableInTouchMode(true)
,使他可以获取焦点,那么我们点击这个button的时候,第一次点击并不会执行onClick()
方法,而是执行onFocusChange()
。第二次点击的时候才会执行onClick()
方法。看起来好像onFocusChange()
消耗了点击事件,实际上并不是的。
这个问题我们看一下源码就清楚了:
onClick()
方法是在onTouchEvent的ACTION_UP里调用的,看一下View的onTouchEvent方法:
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
......
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
......
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
}
}
可以看到,只有当focusTaken为false的时候才会执行onClick,focusTaken的值默认是false的,但是在isFocusable() && isFocusableInTouchMode() && !isFocused()
为true的时候,会去requestFocus
获取焦点,并将值赋给focusTaken。
关键在于isFocused()
,如果当前Button没有获取焦点,isFocused()
返回false,!isFocused()
值为ture,Button就会去获取焦点,从而导致focusTaken
为true,onClick
方法就不会执行了,只有Button已经获取了焦点的时候才会执行onClick方法。