Android电视项目焦点跨层级流转

1. 背景

在智家电视项目中,主要操作方式不是触摸,而是遥控器,通过Focus进行移动,确定点击进行的交互,所以在电视项目中焦点、选中、确定、返回这几个交互比较重要。由于电视屏比较大,在一些复杂页面中会存在一级Tab选择,二级选择,三级选择等,这就涉及到了焦点与选中的联动实现业务逻辑。这块的逻辑比较复杂,在做好了一个页面后,把这块的内容记录一下,同时提炼出了一个辅助类,MultiLevelFocusHelper,后续可进行复用。

2. 基本使用:遥控器+焦点控制

2.1 使用原则

Android原生就能比较好的支持Focus及切换,使用时只要按照它本身的逻辑使用就好,如果碰到不能很好支撑业务的时候再进行扩展,如下是我们小组实践过后,总结出来的几项原则,实际效果很好:

  • 不进行过度控制,使用默认规则

  • 使用focusable、descendantFocusability把XML中的控件按照父控件统一管控,如必须下放时再进行子控件控制

  • nextFocusUp、nextFocusDown、nextFocusLeft、nextFocusRight、nextFocusForward这几个属性不要轻易使用,只要在需要定制的复杂页面才有可能用到

2.2 View中涉及到焦点的几个属性

属性

使用场景说明

focusable

物理按键时获得焦点的属性 android:focusable="false" android:focusable="true"

descendantFocusability

该属性是当一个view获取焦点时,定义viewGroup和其子控件两者之间的关系,属性的值有三种:

  • beforeDescendants:viewgroup会优先其子类控件而获取到焦点

  • afterDescendants:viewgroup只有当其子类控件不需要获取焦点时才获取焦点

  • blocksDescendants:viewgroup会覆盖子类控件而直接获得焦点

nextFocusUp

nextFocusDown

nextFocusLeft

nextFocusRight

android:nextFocusUp-定义当点up键时,哪个控件将获得焦点

android:nextFocusDown-定义当点down键时,哪个控件将获得焦点

android:nextFocusLeft-定义当点left键时,哪个控件将获得焦点

android:nextFocusRight--定义当点right键时,哪个控件将获得焦点

nextFocusForward

我是谁,我有什么用???

2.3 如何使用

1. XML中从顶到细,一层一层的看,如果此View及其子View不需要获得焦点,则直接把它的焦点屏蔽掉

android:focusable="false"
android:descendantFocusability="blocksDescendants"

2. 如果只有此ViewGroup需要获得焦点,它的子View不需要,则设置如下

android:focusable="true"
android:descendantFocusability="blocksDescendants"

3. RecyclerView或ListView,根据需要,如果是简单的能自动处理的则只修改XML即可,否则可以XML+代码进行控制

// 1. 第一种情况:recyclerView的 xml 设置 recyclerView 不获得焦点,子控件获得焦点
android:focusable="false"
android:descendantFocusability="afterDescendants"// recyclerView的item 布局中添加
android:focusable="true"
android:descendantFocusability="blocksDescendants"// 2. 第二种情况:代码控制时, recyclerView先获得焦点,然后根据需要,再在它的OnFocusChangeListener中进行焦点转移
android:focusable="true"
android:descendantFocusability="beforeDescendants"

4. 至此,如果没有特殊的需求,只是简单的焦点控制,通过XML配置属性+Android强大的默认功能即可完成

3. 高级用法:增加层级

3.1 层级是什么? 为什么要有三态?

如图,感兴趣的往下看,一切尽在图中,祭镇楼图

  • 图中的设备列表与全屋节能信息构成了一级焦点,后边的节电数据范围是二级焦点,它俩是一个整体,这里暂且起名叫节能数据查看

  • 其中全屋节能信息是一个ViewGroup,下边的设备列表是一个RecyclerView

  • 图中的帮助按钮是另一个可欺获得焦点的控件,与上边的节能数据查看是并列关系

  • 根据以上分析,得出:层级就是 完成同一个功能的多级多控件的可分别获得焦点的聚合体,特点如下:

    • 焦点可在多级中的多个控件中自由流转,同时只有一个控件具备焦点

    • 在同一级中,如果没有焦点,则需要有一个控件具备已选中状态,由此引出了三态:有焦点、无焦点选中、无焦点未选中

    • 焦点在多级流转时有一定的规则,大部分情况下是从一级流向另一级时,优先流到已选中的控件上

    • 多级具备方向性,比如1->2->3-4, 或 4->3->2->1, 在这个模型中,不可以跨级流转,如果后续有跨级流转的业务需求,再另说(产品经理不要搞太复杂呀...)

3.2 自定义的层级管理辅助类:MultiLevelFocusHelper

基于以上的层级焦点定义,我封装了一个辅助类,MultiLevelFocusHelper,可用于简化层级焦点的操作实现,它主要实现的功能有:

  • 当某一层级的控件获得焦点时,通过它可记录最新的有焦点控件,并同时设置其中选中状态

  • 设置当前层级有焦点的控件往下一级流转时的按键,并精准定位到下一级的选中控件上

  • 获得所有层级的当前控件对应的附加数据

  • 遵循了最小实现、不过渡设计的原则,当前只实现了两级,如果将来需要支持更多的级数,可扩展此类

代码如下:


class MultiLevelFocusHelper(private val totalLevel: Int) {private var mCurLevel1View: View? = nullprivate var mCurLevel1ViewId: Int? = nullprivate var mCurLevel1Data: Any? = nullprivate var mCurLevel2View: View? = nullprivate var mCurLevel2ViewId: Int? = nullprivate var mCurLevel2Data: Any? = null/*** 某一个控件得到了焦点*  @param level: 得到焦点的控件的层级*  @param view:  得到焦点的控件*  @param viewId: 得到焦点的控件的Id,如果是recycleView,则设置它的父控件的Id,主要是为的是上下级Level切换*  @param extraData: 得到焦点的控件对应的附属数据,暂存一下,后续业务需要时可直接get*  @param nextLevelMoveDirect:*/fun receiveFocus(level: Int, view: View, viewId: Int, extraData: Any) {if (level > totalLevel) returnwhen(level) {1 -> {if (mCurLevel1View != null) {mCurLevel1View!!.isSelected = false}mCurLevel1View = viewmCurLevel1View!!.isSelected = truemCurLevel1ViewId = viewIdmCurLevel1Data = extraData}2 -> {if (mCurLevel2View != null) {mCurLevel2View!!.isSelected = false}mCurLevel2View = viewmCurLevel2View!!.isSelected = truemCurLevel2ViewId = viewIdmCurLevel2Data = extraData}else -> {// nothing}}}/*** 设置某一层级当前选中View的 nextFocusLeftId, nextFocusDownId 等* @param moveDirect, 移动方向,用于决定设置当前级Level的哪个属性 nextFocusLeftId 等。 传入值:使用本类中定义的四个常量值,多个方向时可进行&运算再传进来* @param moveCommander, 移动命令,是前进还是后退,用于确定要设置的Value是上一级还是下一步当前选中的View*                      为 null,忽略,如果是头尾的,只有一个方向,直接设就行。 如果是中间的则忽略不进行设置了。*/fun setDirectToCurrentView(level: Int, moveDirect: Int, moveCommander: MoveCommander? = null) {if (level > totalLevel) returnwhen(level) {1 -> {// 第一层,只能往下移,不能回移setNextMoveTarget(mCurLevel1View, moveDirect, mCurLevel2ViewId)}2 -> {if (level < totalLevel) {if (moveCommander != null) {if (moveCommander == MoveCommander.forward) {// TODO, 当 totalLevel 大于等于 3 的时候,加上这一个分支, 它应该往 3 去移动了// setNextMoveTarget(mCurLevel2View, moveDirect, mCurLevel3ViewId)} else {setNextMoveTarget(mCurLevel2View, moveDirect, mCurLevel1ViewId)}}} else {// 这是最后一层, 只有一个方向setNextMoveTarget(mCurLevel2View, moveDirect, mCurLevel1ViewId)}}else -> {// nothing}}}/*** 所有控件失去焦点, 暂时应该没有场景调到它,如果有的话,需要考虑一下行为是否正确*/fun clearAllFocus() {if (mCurLevel1View != null) {mCurLevel1View!!.isSelected = false}mCurLevel1Data = nullif (mCurLevel2View != null) {mCurLevel2View!!.isSelected = false}mCurLevel2Data = null}/*** 获得某一层当前选中控件对应的 View*/fun getView(level: Int): View? {if (level > totalLevel) return nullreturn when(level) {1 -> {mCurLevel1View}2 -> {mCurLevel2View}else -> {null}}}/*** 获得某一层当前选中控件对应的数据*/fun getData(level: Int): Any? {if (level > totalLevel) return nullreturn when(level) {1 -> {mCurLevel1Data}2 -> {mCurLevel2Data}else -> {null}}}private fun setNextMoveTarget(view: View?, direct: Int?, nextViewId: Int?) {if (view == null || direct == null || nextViewId == null) {return}if (direct and Direct_Up > 0) {view.nextFocusUpId = nextViewId}if (direct and Direct_Right > 0) {view.nextFocusRightId = nextViewId}if (direct and Direct_Down > 0) {view.nextFocusDownId = nextViewIdview.nextFocusDownId}if (direct and Direct_Left > 0) {view.nextFocusLeftId = nextViewId}}
}

3.3 MultiLevelFocusHelper要点说明

  1. 构造函数中的参数 totalLevel

    1. 总级数,从1开始的, 比如totalLevel为3, 则所有级别即为1,2,3

    2. 目前 totalLevel 最大为 2,超过2 按 2 计算

  2. 对外函数receiveFocus(level: Int, view: View, viewId: Int, extraData: Any)

    1. 当层级中的某一个控件获得焦点时调用此函数

    2. 参数说明

      1. @param level: 得到焦点的控件的层级

      2. @param view: 得到焦点的控件

      3. @param viewId: 得到焦点的控件的Id,如果是recycleView,则设置它的父控件的Id,主要是为的是上下级Level切换

      4. @param extraData: 得到焦点的控件对应的附属数据,暂存一下,后续业务需要时可直接get

    3. 这里的 viewId 可以是 view 的Id,也可以不是, 基本用法是,如果是ListView或RecyclerView,则可以把viewId设置为 recyclerView 的Id,这样再在业务代码的 recyclerView 获得焦点事件中转一下即可

  3. 层级流转

    1. level 移动顺序: 目前是一个约定,不能自定义。 1->2->3->4, 或 4->3->2->1。 如果后续有不同需求,可以再进行扩充

    2. 两个概念:MoveCommander, MoveDirect:

      // 层级移动命令,向前进,还是后退,参考按照类说明了中的移动顺序
      enum class MoveCommander {forward,back
      }// 焦点移动方向,比如按了遥控器上的上下左右, 使用Int值表示, 多个方向时可以进行&运算
      val Direct_Up = 0x01
      val Direct_Right = 0x02
      val Direct_Down = 0x04
      val Direct_Left = 0x08
    3. 对外函数:setDirectToCurrentView(level: Int, moveDirect: Int, moveCommander: MoveCommander? = null)

      1. 设置某一层级当前选中View的 nextFocusLeftId, nextFocusDownId 等,当某一个控件获得焦点后,再马上调用此函数设置一下

      2. 参数说明

        1. moveDirect, 移动方向,用于决定设置当前级Level的哪个属性 nextFocusLeftId 等。 传入值:使用本类中定义的四个常量值,多个方向时可进行&运算再传进来

        2. moveCommander, 移动命令,是前进还是后退,用于确定要设置的Value是上一级还是下一步当前选中的View为 null,忽略,如果是头尾的,只有一个方向,直接设就行。 如果是中间的则忽略不进行设置了

4. 使用实例

这里附上全屋节能的使用示例,它结合了 MultiLevelFocusHelper,并在Activity中实现了业务关联的一部分代码

4.1 相关控件的XML设置

  1. 设置所有没有焦点的控件中的属性, focusable 和 descendantFocusability

  2. 有焦点的控件属性设置上, focusable 和 descendantFocusability

  3. recyclerView 设置为: android:focusable="true" android:descendantFocusability="beforeDescendants"

4.2 帮助按钮的Focus监听不必设置,使用系统默认的即可

4.3 初始化时,把默认的Focus给到 一级中的全屋信息

mMultiLevelFocusHelper.receiveFocus(1, mFullHouseSaveInfo, mFullHouseSaveInfo.id, "all") // 初始一化一下 mMultiLevelFocusChangeManager 中的状态
mMultiLevelFocusHelper.setDirectToCurrentView(1, MultiLevelFocusHelper.Direct_Right)
mMultiLevelFocusHelper.receiveFocus(2, mTextViewSaveElectricDurationLastMonth, mTextViewSaveElectricDurationLastMonth.id, ElectricIndexDateRange.LAST_MONTH)
mMultiLevelFocusHelper.setDirectToCurrentView(2, MultiLevelFocusHelper.Direct_Down)
mFullHouseSaveInfo.requestFocus()

4.4 RecyclerView 和 它的 item 设置 OnFocusChangeListener

mRecyclerViewDeviceDetailInfo.setOnFocusChangeListener(object : OnFocusChangeListener {override fun onFocusChange(v: View?, hasFocus: Boolean) {if (v == null) returnif (!hasFocus) returnval view = mMultiLevelFocusHelper.getView(1)val tag = view?.getTag()    // 看它有没有存 tag 来判断它是不是 recyclerView 的 itemif (view == null || tag == null) {// 没有上一次的View 或 上一次的第一层View 不是 recyclerView的 item 时if (mRecyclerViewDeviceDetailInfo.getChildAt(0) != null) {mRecyclerViewDeviceDetailInfo.getChildAt(0).requestFocus()}} else {view.requestFocus()}}
})// 这里的最后一个参数 OnFocusChangeListener, 内部又传给了 item, 当它有 FocusChange事件时,再转调用此参数实例
mAdapterDeviceDetailInfo = SaveEnergyAdapterDeviceDetailInfo(mViewModal.getAllSavingDevice(),mViewModal.getAllSavingDeviceRank(),mViewModal.getAllSavingSwitchStatus(),object: OnFocusChangeListener {// 给 设备列表的 recycleview item 设置焦点移动回调override fun onFocusChange(v: View?, hasFocus: Boolean) {if (v == null) {return}if (!hasFocus) {return}val deviceId = v.getTag()mMultiLevelFocusHelper.receiveFocus(1, v, mRecyclerViewDeviceDetailInfo.id, deviceId)mMultiLevelFocusHelper.setDirectToCurrentView(1, MultiLevelFocusHelper.Direct_Right)initSavingElectricData()}})

这里啰嗦一下,RecyclerView拿到焦点时,把焦点转给它下边的之前具有焦点的控件;item中的view有一个tag,存的是业务数据(deviceId),当它拿到焦点时,取到此业务数据,传入到了 mMultiLevelFocusHelper 中

4.5 设置全屋信息 和 所有二级控件的 setOnFocusChangeListener,代码略

5. 总结

  1. 如果没有特殊的需求,只是简单的焦点控制,通过XML配置属性+Android强大的默认功能即可完成。

  2. 如果具有多个层级,焦点需要在多层级间进行流转并需要记忆功能,则可使用MultiLevelFocusHelper类,经过实践检验,可完美应用于此场景。

6. 团队介绍

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/62667.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

yolo辅助我们健身锻炼

使用软件辅助健身能够大大提升运动效果并帮助你更轻松地达成健身目标。确保每次锻炼都更加高效且针对性强,精确记录你的训练进度,帮助你更清晰地看到自己的进步,避免无效训练。 借助YOLO11的尖端计算机视觉技术,跟踪和分析锻炼变得异常简单。它可以无缝检测和监控多种锻炼…

Flume 与 Kafka 整合实战

目录 一、Kafka 作为 Source【数据进入到kafka中&#xff0c;抽取出来】 &#xff08;一&#xff09;环境准备与配置文件创建 &#xff08;二&#xff09;创建主题 &#xff08;三&#xff09;测试步骤 二、Kafka 作为 Sink数据从别的地方抽取到kafka里面】 &#xff08;…

SRS搭建直播推流服务

学习链接 5分钟教你搭建SRS流媒体服务器 - B站视频 SRS Stack 入门B站合集视频 - SRS官方教程 SRS官网 SRS官网文档 ossrs/srs github SRS for window - 可以安装windows版本的srs&#xff0c;SRS 5.0.89正式支持Windows&#xff0c;每个5.0的版本都会提供安装包 文章目录…

css—轮播图实现

一、背景 最近和朋友在一起讨论的时候&#xff0c;我们提出了这样的一个提问&#xff0c;难道轮播图的效果只能通过js来实现吗&#xff1f;经过我们的一系列的争论&#xff0c;发现了这是可以通过纯css来实现这一效果的&#xff0c;CSS轮播图也是一种常见的网页展示方式&#x…

nacos安装部署

nacos安装部署 1.安装nacos 1.安装nacos nacos的安装很简单下载后解压启动即可&#xff0c;但是在启动前请确保jdk环境正常&#xff1b; 1.首先我们要下载nacos安装包&#xff1a;可以到官网下载&#xff0c;注意我这里使用的是2.1.0版本&#xff1b; 2.下载完成后&#xff0…

tomcat 8.5.35安装及配置

安装包地址&#xff1a; 1.Index of /dist/tomcat/tomcat-8/v8.5.35/binhttps://archive.apache.org/dist/tomcat/tomcat-8/v8.5.35/bin/ 2.通过网盘分享的文件&#xff1a;tomcat 链接: https://pan.baidu.com/s/1z9bD4rIuIRvzQ4okm3iRzw?pwdp24p 提取码: p24p 3.通过官网…

YOLO系列论文综述(从YOLOv1到YOLOv11)【第12篇:YOLOv9——可编程梯度信息(PGI)+广义高效层聚合网络(GELAN)】

YOLOv9 1 摘要2 改进点3 网络架构 YOLO系列博文&#xff1a; 【第1篇&#xff1a;概述物体检测算法发展史、YOLO应用领域、评价指标和NMS】【第2篇&#xff1a;YOLO系列论文、代码和主要优缺点汇总】【第3篇&#xff1a;YOLOv1——YOLO的开山之作】【第4篇&#xff1a;YOLOv2—…

机器学习提高电子病历主要诊断编码正确率的路径分析

摘要 本研究探讨机器学习在强化病历书写质量和提高主要诊断编码正确率方面的应用。介绍了基于机器学习的病历质量分析方法、AI病历质控应用、智能预问诊系统和诊室听译机器人等在病历书写质量提升中的作用&#xff0c;以及基于机器学习的ICD智能诊断编码方法和重症病人ICD自动…

鸿蒙征文|鸿蒙技术分享:使用到的开发框架和技术概览

目录 每日一句正能量前言正文1. 开发环境搭建关键技术&#xff1a;2. 用户界面开发关键技术&#xff1a;3. 应用逻辑开发关键技术&#xff1a;4. 应用测试关键技术&#xff1a;5. 应用签名和打包关键技术&#xff1a;6. 上架流程关键技术&#xff1a;7. 后续维护和更新关键技术…

C++类中多线程的编码方式

问题 在C++代码中,一般的代码是需要封装在类里面,比如对象,方法等。否则就不能很好的利用C++面向对象的能力了。 但是这个方式在处理线程时会碰到一个问题。 考虑下面一个简单的场景: class demoC { public:std::thread t;int x;void threadFunc(){std::cout<<x&…

Android开发仿qq详情下拉头像变大

Android开发仿qq详情下拉头像变大 个人详情界面&#xff0c;很多都有下拉头像变大的效果&#xff0c;其实我觉得这效果还不如点击头像看大图呢 一、思路&#xff1a; 自定义ScrollView 二、效果图&#xff1a; 看视频更直观点&#xff1a; Android开发教程案例分享-仿qq详情…

深入解析下oracle date底层存储方式

之前我们介绍了varchar2和char的数据库底层存储格式&#xff0c;今天我们介绍下date类型的数据存储格式&#xff0c;并通过测试程序快速获取一个日期。 一、环境搭建 1.1&#xff0c;创建表 我们还是创建一个测试表t_code&#xff0c;并插入数据&#xff1a; 1.2&#xff0c;…

golang版本管理工具:scoop使用

安装 Scoophttps://scoop.sh/根据官方文档安装。 第一步&#xff1a;打开PowerShell。(注意不要使用管理员方式打开&#xff0c;否则在执行安装Scoop的过程中&#xff0c;会报错。) 第二步&#xff1a;切到C盘根目录下。 第三步&#xff1a; Set-ExecutionPolicy -Executi…

时装购物系统

私信我获取源码和万字论文&#xff0c;制作不易&#xff0c;感谢点赞支持。 摘 要 随着科学技术的飞速发展&#xff0c;社会的方方面面、各行各业都在努力与现代的先进技术接轨&#xff0c;通过科技手段来提高自身的优势&#xff0c;时装购物系统当然也不能排除在外。时装购物…

架构04-透明多级分流系统

零、文章目录 架构04-透明多级分流系统 1、透明多级分流系统 &#xff08;1&#xff09;概述 **定义&#xff1a;**透明多级分流系统是指在用户请求从客户端发出到最终查询或修改数据库信息的过程中&#xff0c;通过多个技术部件对流量进行合理分配&#xff0c;以提高系统的…

【AI】数据,算力,算法和应用(3)

三、算法 算法这个词&#xff0c;我们都不陌生。 从接触计算机&#xff0c;就知道有“算法”这样一个神秘的名词存在。象征着专业、权威、神秘、高难等等。 算法是一组有序的解决问题的规则和指令&#xff0c;用于解决特定问题的一系列步骤。算法可以被看作是解决问题的方法…

YOLO系列论文综述(从YOLOv1到YOLOv11)【第14篇:YOLOv11——在速度和准确性方面具有无与伦比的性能】

YOLOv11 1 摘要2 改进点3 模型性能4 模型架构 YOLO系列博文&#xff1a; 【第1篇&#xff1a;概述物体检测算法发展史、YOLO应用领域、评价指标和NMS】【第2篇&#xff1a;YOLO系列论文、代码和主要优缺点汇总】【第3篇&#xff1a;YOLOv1——YOLO的开山之作】【第4篇&#xff…

最短路径(Floyd-Warshall、Dijkstra、Bellman-Ford)

图的遍历&#xff0c;通过算法优雅实现。 上次使用遍历的方法求得最短路径&#xff08;图的遍历。-CSDN博客&#xff09;&#xff0c;这样虽然可以解决问题&#xff0c;但还是不够优雅&#xff0c;有一些弊端&#xff0c;时间复杂度和空间复杂度都比较高。本博客主要描述三种求…

一些优秀的布隆过滤器介绍

&#x1f680; 博主介绍&#xff1a;大家好&#xff0c;我是无休居士&#xff01;一枚任职于一线Top3互联网大厂的Java开发工程师&#xff01; &#x1f680; &#x1f31f; 在这里&#xff0c;你将找到通往Java技术大门的钥匙。作为一个爱敲代码技术人&#xff0c;我不仅热衷…

YOLOv8模型pytorch格式转为onnx格式

一、YOLOv8的Pytorch网络结构 model DetectionModel((model): Sequential((0): Conv((conv): Conv2d(3, 64, kernel_size(3, 3), stride(2, 2), padding(1, 1))(act): SiLU(inplaceTrue))(1): Conv((conv): Conv2d(64, 128, kernel_size(3, 3), stride(2, 2), padding(1, 1))(a…