电视版智家App兼容触摸和遥控器交互方式

avatar
@海尔优家智能科技(北京)有限公司

 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 的使用场景

  1. 在使用键盘、鼠标、轨迹球、遥控器的情况下,只有setFocusable为true的控件,才可以获取焦点(选中时高亮)。
  2. 在触摸模式下,setFocusable为true,并无法保证控件可以获取焦点,它只能保证在非触摸模式下,该控件可以允许获取焦点。如果想在在触摸模式中,改变控件是否允许聚焦,需要使用setFocusableInTouchMode。
  3. 从上面我们也可以看出,不管是否在触摸模式下,控件获取焦点的前提是isFocusable()为true。而在触摸模式下,只有isFocusable()和isFocusableInTouchMode()都为ture的情况下,控件才允许聚焦。

2.3.3 调用 setFocusableInTouchMode 和 setFocusable 对相互的影响

  1. setFocusableInTouchMode为true,会使isFocusable也变为true,而setFocusableInTouchMode为false并不影响isFocusable。
  2. setFocusable为false,会使isFocusableInTouchMode变为false,而setFocusable为true并不影响isFocusableInTouchMode

2.3.4 各种常用控件的默认初始状态

控件FocusableFocusableInTouchModeClickableLongClickable
ViewFALSEFALSEFALSEFALSE
TextViewFALSEFALSEFALSEFALSE
EditTextTRUETRUETRUETRUE
ButtonTRUEFALSETRUEFALSE
ImageButtonTRUEFALSETRUEFALSE
ImageViewFALSEFALSEFALSEFALSE
CheckBoxTRUEFALSETRUEFALSE
RadioButtonTRUEFALSETRUEFALSE
ProgressBarFALSEFALSEFALSEFALSE
LinearLayoutFALSEFALSEFALSEFALSE
RelativeLayoutFALSEFALSEFALSEFALSE
其他Layout都几乎一样FALSEFALSEFALSEFALSE

从上面我们可以看出,大部分的控件FocusableInTouchMode属性都为false。只有类似EditText这种控件才为true,因为EditText需要提供在没用户点击的条件下,弹出一个软键盘进行输入的功能。

3. 智家App适配触摸屏要点

由于电视之前只有遥控器,所以它的交互方式一直是依赖焦点的,新电视支持触摸,这块肯定是需要适配的,经过上面的研究,发现现在的问题主要是使用focusableInTouchMode属性不当导致的,需要检查一下所有页面中的控件,此属性的使用情况,是否正确。

以下总结了在电视上适配触摸屏的要点及可能会出现的问题:

  1. 触摸模式的进入和退出系统已做好了,不用开发者操心;

  2. 开发者只需要管理好控件的focusableInTouchMode属性即可,一般情况(不需要输入时)下将它设置为false即可,可在xml中进行设置也可以通过 setFocusableInTouchMode() 在代码中设置;

  3. 当需要根据当前的模式进行自定义逻辑适配时,可使用方法:View#isInTouchMode()

  4. 可能会出现的问题需要注意:

    1. 普通的控件或自定义控件,滥用了setFocusableInTouchMode,会出现点击一下获取焦点,再点一下才是点击的情况。对于这种情况,将它的setFocusableInTouchMode设置为false即可;
    2. 以后开发时,要小心onTouchEvent()方法,因为它只会在触摸屏上才有,在非触摸屏上永远不会触发它,如果使用了此方法,到时候逻辑可能就不正常了;

4. 统一适配View

在实际使用过程中,我们需要将常用的一些适配放到基类中进行统一适配,可方便我们的开发及维护,这里我们统一做了几处适配。

4.1 封装方法判断是否是移动屏

fun tvProductTypeIsPad(): Boolean {
    return getTvProductType() == TvProductType.PAD
}

4.2 HoverViewExtension

通用扩展类,用于在鼠标/指向型遥控器移动到View时,智能修改focuseableInTouchMode,以适配移动屏和普通电视,关键代码如下所示

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,松开手指恢复原大小,如下

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. 参考

Android 触摸模式(Touch Mode)

7. 团队介绍

「三翼鸟数字化技术平台-应用软件框架开发」主要负责设计工具的研发,包括营销设计工具、家电VR设计和展示、水电暖通前置设计能力,研发并沉淀素材库,构建家居家装素材库,集成户型库、全品类产品库、设计方案库、生产工艺模型,打造基于户型和风格的AI设计能力,快速生成算量和报价;同时研发了门店设计师中心和项目中心,包括设计师管理能力和项目经理管理能力。实现了场景全生命周期管理,同时为水,空气,厨房等产业提供商机管理工具,从而实现了以场景贯穿的B端C端全流程系统。