1. 背景
电视版智家App一直运行在海尔电视上,使用遥控器进行交互,后来添加了一个指向型遥控器,也是通过焦点进行交互的,一直运行正常。
现在增加了一款新的电视,此电视竟然可触摸,太厉害了,超级好用。但是我们的App跑上去后竟然水土不服,交互出现了问题,问题有很多,有UI适配的,有交互的,有横竖屏的,今天先研究一下交互模式,饭要一口一口吃。
2. 焦点与触摸模式
2.1 什么是焦点?它有什么作用?
在非触屏的手机或者电视上,我们通常需要用键盘、鼠标、遥控器与界面进行交互,当交互的时候必须先使目标控件获得焦点(比如高亮起来),这样用户才会注意到是什么控件接受输入。
而如果是在触屏时代,用户可以直接用手指点击控件,这个时候就没必要将目标高亮了(即获取焦点)。
总结,焦点主要用于非触屏手机上的,是与用户交互有关的产物,跟交互方式紧密相关(当然它还有其它用途,这个后续再说,现在先聚焦在这里)。
2.2 触摸模式
2.2.1 触摸模式是什么?
现在的手机大多是触摸屏,使用起来很方便,它与传统的键盘、鼠标、遥控器的交互方式不太一样的一点是,它不需要焦点辅助交互,更加方便人们使用手机,所以它是一种新的交互模式,我们称之为"触摸模式"(Touch Mode)。
在Android上,有一个方法(View#isInTouchMode() )用来指示当前是否是处于触摸模式,它是用户和手机进行交互时view层次结构的一个状态。代表了最近一次的交互是否是通过触摸屏发生的,因为在Android设备上还存在别的交互方式,比如键盘、鼠标等等。
2.2.2 进入/退出触摸模式,如何判断当前是什么模式
Android系统会自动维护当前的状态,即是否是触摸模式。对于支持触摸功能的设备,当用户触摸屏幕时,设备会立即进入触摸模式。无论何时,只要用户点击方向键或滚动轨迹球或点击遥控器,设备就会退出触摸模式并找到一个视图使其获得焦点。
整个系统(所有窗口和 Activity)都将保持同一个触摸模式的状态。可通过调用View#isInTouchMode() 来检查设备目前是否处于触摸模式,用于您自己的逻辑处理时使用。
2.3 触摸模式与焦点
前面说了,触摸模式是与传统的遥控器、键盘不一样的一种交互方式,它是不需要焦点的,任何已聚焦的控件当进入触摸模式时都变为未聚焦状态。而当使用鼠标和键盘时,就会立即离开触摸模式,控件就会变成聚焦的状态。
那么是不是触摸模式下就没有焦点的概念了呢?
非也,上边只是说不需要焦点进行辅助交互,但还是有其它场景是需要焦点的,比如文本输入框。这就引出了两个概念,搞清了它俩,也就弄清了焦点和触模模式了,它们是setFocusableInTouchMode 和 setFocusable 。
2.3.1 setFocusableInTouchMode 和 setFocusable 方法
- setFocusable:设置控件是否能获取焦点。可以通过isFocusable()获取其状态。
- setFocusableInTouchMode:在触摸模式下,设置控件是否允许聚焦。可以通过isFocusableInTouchMode() 获取其状态。
2.3.2 setFocusableInTouchMode 和 setFocusable 的使用场景
- 在使用键盘、鼠标、轨迹球、遥控器的情况下,只有setFocusable为true的控件,才可以获取焦点(选中时高亮)。
- 在触摸模式下,setFocusable为true,并无法保证控件可以获取焦点,它只能保证在非触摸模式下,该控件可以允许获取焦点。如果想在在触摸模式中,改变控件是否允许聚焦,需要使用setFocusableInTouchMode。
- 从上面我们也可以看出,不管是否在触摸模式下,控件获取焦点的前提是isFocusable()为true。而在触摸模式下,只有isFocusable()和isFocusableInTouchMode()都为ture的情况下,控件才允许聚焦。
2.3.3 调用 setFocusableInTouchMode 和 setFocusable 对相互的影响
- setFocusableInTouchMode为true,会使isFocusable也变为true,而setFocusableInTouchMode为false并不影响isFocusable。
- setFocusable为false,会使isFocusableInTouchMode变为false,而setFocusable为true并不影响isFocusableInTouchMode
2.3.4 各种常用控件的默认初始状态
| 控件 | Focusable | FocusableInTouchMode | Clickable | LongClickable |
| View | FALSE | FALSE | FALSE | FALSE |
| TextView | FALSE | FALSE | FALSE | FALSE |
| EditText | TRUE | TRUE | TRUE | TRUE |
| Button | TRUE | FALSE | TRUE | FALSE |
| ImageButton | TRUE | FALSE | TRUE | FALSE |
| ImageView | FALSE | FALSE | FALSE | FALSE |
| CheckBox | TRUE | FALSE | TRUE | FALSE |
| RadioButton | TRUE | FALSE | TRUE | FALSE |
| ProgressBar | FALSE | FALSE | FALSE | FALSE |
| LinearLayout | FALSE | FALSE | FALSE | FALSE |
| RelativeLayout | FALSE | FALSE | FALSE | FALSE |
| 其他Layout都几乎一样 | FALSE | FALSE | FALSE | FALSE |
从上面我们可以看出,大部分的控件FocusableInTouchMode属性都为false。只有类似EditText这种控件才为true,因为EditText需要提供在没用户点击的条件下,弹出一个软键盘进行输入的功能。
3. 智家App适配触摸屏要点
由于电视之前只有遥控器,所以它的交互方式一直是依赖焦点的,新电视支持触摸,这块肯定是需要适配的,经过上面的研究,发现现在的问题主要是使用focusableInTouchMode属性不当导致的,需要检查一下所有页面中的控件,此属性的使用情况,是否正确。
以下总结了在电视上适配触摸屏的要点及可能会出现的问题:
-
触摸模式的进入和退出系统已做好了,不用开发者操心;
-
开发者只需要管理好控件的focusableInTouchMode属性即可,一般情况(不需要输入时)下将它设置为false即可,可在xml中进行设置也可以通过 setFocusableInTouchMode() 在代码中设置;
-
当需要根据当前的模式进行自定义逻辑适配时,可使用方法:View#isInTouchMode() ;
-
可能会出现的问题需要注意:
- 普通的控件或自定义控件,滥用了setFocusableInTouchMode,会出现点击一下获取焦点,再点一下才是点击的情况。对于这种情况,将它的setFocusableInTouchMode设置为false即可;
- 以后开发时,要小心onTouchEvent()方法,因为它只会在触摸屏上才有,在非触摸屏上永远不会触发它,如果使用了此方法,到时候逻辑可能就不正常了;
4. 统一适配View
在实际使用过程中,我们需要将常用的一些适配放到基类中进行统一适配,可方便我们的开发及维护,这里我们统一做了几处适配。
4.1 封装方法判断是否是移动屏
kotlin
fun tvProductTypeIsPad(): Boolean {
return getTvProductType() == TvProductType.PAD
}
4.2 HoverViewExtension
通用扩展类,用于在鼠标/指向型遥控器移动到View时,智能修改focuseableInTouchMode,以适配移动屏和普通电视,关键代码如下所示
kotlin
fun View.addHoverListener(autoChangeItemFocusableInTouchMode: Boolean = true) {
this.setOnGenericMotionListener { v, event ->
when(event?.action) {
MotionEvent.ACTION_HOVER_ENTER -> {
if (autoChangeItemFocusableInTouchMode) {
v.isFocusableInTouchMode = true
}
}
MotionEvent.ACTION_HOVER_EXIT -> {
if (autoChangeItemFocusableInTouchMode) {
v.isFocusableInTouchMode = false
}
}
}
return@setOnGenericMotionListener true
}
}
4.3 UiUtils
创建Utils方法,实现点击时的动效,点击缩小View,松开手指恢复原大小,如下
kotlin
fun commonCardTouchScale(
view: View?,
scaleX: Float = SCALE_RATIO_SMART,
scaleY: Float = SCALE_RATIO_SMART,
duration: Long = SCALE_DURATION_SMART,
effectZ: Boolean = false
) {
view?.setOnTouchListener { v, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
if (effectZ) view.elevation += 1
scaleView(v,
getSmartScaleRatio(scaleX, v),
getSmartScaleRatio(scaleY, v),
getSmartScaleDuration(duration, event.action)
)
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
if (effectZ) view.elevation -= 1
scaleView(v, SCALE_DEFAULT, SCALE_DEFAULT,
getSmartScaleDuration(duration, event.action)
)
}
}
false
}
}
fun scaleView(
view: View,
scaleX: Float,
scaleY: Float,
duration: Long = SCALE_DURATION
) {
view.animate().scaleX(scaleX).scaleY(scaleY).setDuration(duration)
.setInterpolator(AccelerateDecelerateInterpolator())
.start()
}
如上,实现了一些通用的方法,然后在每个页面的合适位置中进行适配,即可完成整个工程的适配。当然适配工作是一项细致的事情,需要细心修改,并反复打磨,最后呈现出一个完美的作品。
5. 总结
本篇文章梳理总结了焦点、触摸模式的定义,维护是否是触摸模式的方式、focusable和focusableInTouchMode属性的区别,最后梳理出智家App适配触摸屏的要点,清晰了概念,根据要点,走查一遍App功能,大体上就算适配完了,后续再根据需要进行一些UI或其它逻辑的完善即可。
6. 参考
7. 团队介绍
「三翼鸟数字化技术平台-应用软件框架开发」主要负责设计工具的研发,包括营销设计工具、家电VR设计和展示、水电暖通前置设计能力,研发并沉淀素材库,构建家居家装素材库,集成户型库、全品类产品库、设计方案库、生产工艺模型,打造基于户型和风格的AI设计能力,快速生成算量和报价;同时研发了门店设计师中心和项目中心,包括设计师管理能力和项目经理管理能力。实现了场景全生命周期管理,同时为水,空气,厨房等产业提供商机管理工具,从而实现了以场景贯穿的B端C端全流程系统。