问题背景:
1.日夜模式的切换方式:使用uiMode,不重建页面,这样避免了需要页面恢复的问题。
2.焦点丢失的现象:焦点在任意View上,切换日夜模式后,都有比较高的概率偶现焦点丢失,通过addOnGlobalFocusChangeListener监听焦点变化,newFocus直接变为null,而oldFocus就是丢失焦点的view
3.尝试恢复焦点:开始时认为焦点丢失的原因是view在变更属性重绘时,没有绘制完成就设置焦点,导致焦点的丢失,所以进行了一系列恢复焦点的操作,如记录丢失焦点的view然后延时重新requestFocus、或者监听view绘制完成后再延时等等,都没有作用,开始认为是延迟时间不够的原因,后面发现延迟多久requestFocus都不生效,所以可以断定不是重绘导致的问题。
最终方案:
经过测试发现,当给view设置了android:focusableInTouchMode="true"属性后,焦点不会出现丢失的情况了。
focusableInTouchMode属性介绍:
在触摸模式下可以获取焦点。
没加这个属性之前,点击屏幕,是直接触发点击事件的,并且会导致焦点丢失,下次触发keyevent会从默认的初始焦点开始。
设置这个属性之后,点击屏幕,被点击的view会获取焦点,不会触发焦点丢失,但是也带来一个问题,就是第一次点击是触发获取焦点,而不是触发click事件。
所以我理解这个属性,在遥控器控制和触摸屏控制同时生效时,有至关重要的作用。
此时,如果你的事件触发是写在focusChange的事件监听里,就没有问题。但有些业务场景,不是获焦就触发的,是先获焦再点击确认键触发,这时通常实现click事件就能满足需求,但是如果同时需要触摸屏的功能并且希望触摸一次就触发事件,关实现click事件就无法达成目的了。
针对上面的问题解决方案是:
- 1.使用setOnTouchListener代替setOnClickListener
(原因是,使用focusableInTouchMode=true后会导致click需要点击两次才能触发->第一次会触发焦点)- 2.需要响应ok事件,用以代替原来按下ok键自动触发的Click事件
private var sLastClickTime: Long = 0
private const val NO_QUICK_CLICK_INTERVAL: Long = 200
private var isQuickClick = false
@SuppressLint("ClickableViewAccessibility")
@JvmOverloads
fun View.setTVClickListener(needChangeAlpha:Boolean = true, clickListener: () -> Unit){
setTVClickTouchListener(needChangeAlpha, clickListener)
setOnKeyListener { v, keyCode, event ->
if (KeyEventUtil.isOkKeyEvent(keyCode)) {
if (event.action == KeyEvent.ACTION_UP) {
v.isPressed = false
isQuickClick("setTVClickListener ACTION_DOWN isOkKeyEvent"){
sLastClickTime = System.currentTimeMillis()
clickListener.invoke()
}
return@setOnKeyListener true
}else { //点击事件
setQuickClick()
v.isPressed = true
}
}
return@setOnKeyListener false
}
}
@SuppressLint("ClickableViewAccessibility")
fun View.setTVClickTouchListener(needChangeAlpha:Boolean = true, clickListener: () -> Unit){
setOnTouchListener { v, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
setQuickClick()
if(needChangeAlpha){
isQuickClick("setTVClickListener ACTION_DOWN"){v.alpha = 0.5f}
}
true
}
MotionEvent.ACTION_UP -> {
isQuickClick("setTVClickListener ACTION_UP"){
if(needChangeAlpha){
v.alpha = 1.0f
}
requestFocus(this)
sLastClickTime = System.currentTimeMillis()
clickListener.invoke()
}
true // 返回true表示这个事件被消耗掉了
}
else -> false // 其他情况返回false
}
}
}
private fun setQuickClick(){
isQuickClick = abs(System.currentTimeMillis() - sLastClickTime) < NO_QUICK_CLICK_INTERVAL
}
private fun isQuickClick(tag: String, action: () -> Unit){
if (!isQuickClick) {
action.invoke()
} else {
LogUtils.e(TAG, "$tag -> quick click")
}
}
private fun requestFocus(view: View){
if(!view.hasFocus()){
view.requestFocus()
}
}