当你按下方向键,电视是如何寻找下一个焦点的

news2024/11/25 19:52:15

我工作的第一家公司主要做的是一个在智能电视上面运行的APP,其实就是一个安卓APP,也是混合开发的应用,里面很多页面是H5开发的。

电视我们都知道,是通过遥控器来操作的,没有鼠标也不能触屏,所以“点击”的操作变成了按遥控器的“上下左右确定”键,那么必然需要一个“焦点”来告诉用户当前聚焦在哪里。

当时开发页面使用的是一个前人开发的焦点库,这个库会自己监听方向键并且自动计算下一个聚焦的元素。

为什么时隔多年会突然想起这个呢,其实是因为最近在给我开源的思维导图添加方向键导航的功能时,想到其实和电视聚焦功能很类似,都是按方向键,来计算并且自动聚焦到下一个元素或节点:

那么如何寻找下一个焦点呢,结合我当时用的焦点库的原理,接下来实现一下。

1.最简单的算法

第一种算法最简单,根据方向先找出当前节点该方向所有的其他节点,然后再找出直线距离最近的一个,比如当按下了左方向键,下面这些节点都是符合要求的节点:

从中选出最近的一个即为下一个聚焦节点。

节点的位置信息示意如下:

focus(dir) {
    // 当前聚焦的节点
    let currentActiveNode = this.mindMap.renderer.activeNodeList[0]
    // 当前聚焦节点的位置信息
    let currentActiveNodeRect = this.getNodeRect(currentActiveNode)
    // 寻找的下一个聚焦节点
    let targetNode = null
    let targetDis = Infinity
    // 保存并维护距离最近的节点
    let checkNodeDis = (rect, node) => {
        let dis = this.getDistance(currentActiveNodeRect, rect)
        if (dis < targetDis) {
            targetNode = node
            targetDis = dis
        }
    }
    // 1.最简单的算法
    this.getFocusNodeBySimpleAlgorithm({
        currentActiveNode,
        currentActiveNodeRect,
        dir,
        checkNodeDis
    })
    // 找到了则让目标节点聚焦
    if (targetNode) {
        targetNode.active()
    }
}

无论哪种算法,都是先找出所有符合要求的节点,然后再从中找出和当前聚焦节点距离最近的节点,所以维护最近距离节点的函数是可以复用的,通过参数的形式传给具体的计算函数。

// 1.最简单的算法
getFocusNodeBySimpleAlgorithm({
    currentActiveNode,
    currentActiveNodeRect,
    dir,
    checkNodeDis
}) {
    // 遍历思维导图节点树
    bfsWalk(this.mindMap.renderer.root, node => {
        // 跳过当前聚焦的节点
        if (node === currentActiveNode) return
        // 当前遍历到的节点的位置信息
        let rect = this.getNodeRect(node)
        let { left, top, right, bottom } = rect
        let match = false
        // 按下了左方向键
        if (dir === 'Left') {
            // 判断节点是否在当前节点的左侧
            match = right <= currentActiveNodeRect.left
            // 按下了右方向键
        } else if (dir === 'Right') {
            // 判断节点是否在当前节点的右侧
            match = left >= currentActiveNodeRect.right
            // 按下了上方向键
        } else if (dir === 'Up') {
            // 判断节点是否在当前节点的上面
            match = bottom <= currentActiveNodeRect.top
            // 按下了下方向键
        } else if (dir === 'Down') {
            // 判断节点是否在当前节点的下面
            match = top >= currentActiveNodeRect.bottom
        }
        // 符合要求,判断是否是最近的节点
        if (match) {
            checkNodeDis(rect, node)
        }
    })
}

效果如下:

基本可以工作,但是可以看到有个很大的缺点,比如按上键,我们预期的应该是聚焦到上面的兄弟节点上,但是实际上聚焦到的是子节点:

因为这个子节点确实是在当前节点上面,且距离最近的,那么怎么解决这个问题呢,接下来看看第二种算法。

2.阴影算法

该算法也是分别处理四个方向,但是和前面的第一种算法相比,额外要求节点在指定方向上的延伸需要存在交叉,延伸处可以想象成是节点的阴影,也就是名字的由来:

找出所有存在交叉的节点后也是从中找出距离最近的一个节点作为下一个聚焦节点,修改focus方法,改成使用阴影算法:

focus(dir) {
    // 当前聚焦的节点
    let currentActiveNode = this.mindMap.renderer.activeNodeList[0]
    // 当前聚焦节点的位置信息
    let currentActiveNodeRect = this.getNodeRect(currentActiveNode)
    // 寻找的下一个聚焦节点
    // ...
    // 保存并维护距离最近的节点
    // ...

    // 2.阴影算法
    this.getFocusNodeByShadowAlgorithm({
        currentActiveNode,
        currentActiveNodeRect,
        dir,
        checkNodeDis
    })

    // 找到了则让目标节点聚焦
    if (targetNode) {
        targetNode.active()
    }
}
// 2.阴影算法
getFocusNodeByShadowAlgorithm({
    currentActiveNode,
    currentActiveNodeRect,
    dir,
    checkNodeDis
}) {
    bfsWalk(this.mindMap.renderer.root, node => {
        if (node === currentActiveNode) return
        let rect = this.getNodeRect(node)
        let { left, top, right, bottom } = rect
        let match = false
        if (dir === 'Left') {
            match =
                left < currentActiveNodeRect.left &&
                top < currentActiveNodeRect.bottom &&
                bottom > currentActiveNodeRect.top
        } else if (dir === 'Right') {
            match =
                right > currentActiveNodeRect.right &&
                top < currentActiveNodeRect.bottom &&
                bottom > currentActiveNodeRect.top
        } else if (dir === 'Up') {
            match =
                top < currentActiveNodeRect.top &&
                left < currentActiveNodeRect.right &&
                right > currentActiveNodeRect.left
        } else if (dir === 'Down') {
            match =
                bottom > currentActiveNodeRect.bottom &&
                left < currentActiveNodeRect.right &&
                right > currentActiveNodeRect.left
        }
        if (match) {
            checkNodeDis(rect, node)
        }
    })
}

就是判断条件增加了是否交叉的比较,效果如下:

可以看到阴影算法成功解决了前面的跳转问题,但是它也并不完美,比如下面这种情况按左方向键找不到可聚焦节点了:

因为左侧没有存在交叉的节点,但是其实可以聚焦到父节点上,怎么办呢,我们先看一下下一种算法。

3.区域算法

所谓区域算法也很简单,把当前聚焦节点的四周平分成四个区域,对应四个方向,寻找哪个方向的下一个节点就先找出中心点在这个区域的所有节点,再从中选择距离最近的一个即可:

focus(dir) {
    // 当前聚焦的节点
    let currentActiveNode = this.mindMap.renderer.activeNodeList[0]
    // 当前聚焦节点的位置信息
    let currentActiveNodeRect = this.getNodeRect(currentActiveNode)
    // 寻找的下一个聚焦节点
    // ...
    // 保存并维护距离最近的节点
    // ...

    // 3.区域算法
    this.getFocusNodeByAreaAlgorithm({
        currentActiveNode,
        currentActiveNodeRect,
        dir,
        checkNodeDis
    })

    // 找到了则让目标节点聚焦
    if (targetNode) {
        targetNode.active()
    }
}
// 3.区域算法
getFocusNodeByAreaAlgorithm({
    currentActiveNode,
    currentActiveNodeRect,
    dir,
    checkNodeDis
}) {
    // 当前聚焦节点的中心点
    let cX = (currentActiveNodeRect.right + currentActiveNodeRect.left) / 2
    let cY = (currentActiveNodeRect.bottom + currentActiveNodeRect.top) / 2
    bfsWalk(this.mindMap.renderer.root, node => {
        if (node === currentActiveNode) return
        let rect = this.getNodeRect(node)
        let { left, top, right, bottom } = rect
        // 遍历到的节点的中心点
        let ccX = (right + left) / 2
        let ccY = (bottom + top) / 2
        // 节点的中心点坐标和当前聚焦节点的中心点坐标的差值
        let offsetX = ccX - cX
        let offsetY = ccY - cY
        if (offsetX === 0 && offsetY === 0) return
        let match = false
        if (dir === 'Left') {
            match = offsetX <= 0 && offsetX <= offsetY && offsetX <= -offsetY
        } else if (dir === 'Right') {
            match = offsetX > 0 && offsetX >= -offsetY && offsetX >= offsetY
        } else if (dir === 'Up') {
            match = offsetY <= 0 && offsetY < offsetX && offsetY < -offsetX
        } else if (dir === 'Down') {
            match = offsetY > 0 && -offsetY < offsetX && offsetY > offsetX
        }
        if (match) {
            checkNodeDis(rect, node)
        }
    })
}

比较的逻辑可以参考下图:

结合阴影算法和区域算法

前面介绍阴影算法时说了它有一定局限性,区域算法计算出的结果则可以对它进行补充,但是理想情况下阴影算法的结果是最符合我们的预期的,那么很简单,我们可以把它们两个结合起来,调整一下顺序,先使用阴影算法计算节点,如果阴影算法没找到,那么再使用区域算法寻找节点,简单算法也可以加在最后:

focus(dir) {
    // 当前聚焦的节点
    let currentActiveNode = this.mindMap.renderer.activeNodeList[0]
    // 当前聚焦节点的位置信息
    let currentActiveNodeRect = this.getNodeRect(currentActiveNode)
    // 寻找的下一个聚焦节点
    // ...
    // 保存并维护距离最近的节点
    // ...

    // 第一优先级:阴影算法
    this.getFocusNodeByShadowAlgorithm({
        currentActiveNode,
        currentActiveNodeRect,
        dir,
        checkNodeDis
    })

    // 第二优先级:区域算法
    if (!targetNode) {
        this.getFocusNodeByAreaAlgorithm({
            currentActiveNode,
            currentActiveNodeRect,
            dir,
            checkNodeDis
        })
    }

    // 第三优先级:简单算法
    if (!targetNode) {
        this.getFocusNodeBySimpleAlgorithm({
            currentActiveNode,
            currentActiveNodeRect,
            dir,
            checkNodeDis
        })
    }

    // 找到了则让目标节点聚焦
    if (targetNode) {
        targetNode.active()
    }
}

效果如下:

是不是很简单呢,详细体验可以点击思维导图。

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

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

相关文章

ChatGPT已应用到跨境电商领域,规模化运营指日可待

最近各大平台都卷起了一股“ChatGPT”的热潮&#xff0c;论坛、贴吧、微博甚至短视频都对这个新兴的东西津津乐道&#xff0c;在这些评论区里我们可以发现&#xff0c;不管说什么职业&#xff0c;不管年龄性别&#xff0c;ChatGPT都开始被许多人关注。那么ChatGPT到底是个什么东…

大数据框架之Hadoop:HDFS(五)NameNode和SecondaryNameNode(面试开发重点)

5.1NN和2NN工作机制 5.1.1思考&#xff1a;NameNode中的元数据是存储在哪里的&#xff1f; 首先&#xff0c;我们做个假设&#xff0c;如果存储在NameNode节点的磁盘中&#xff0c;因为经常需要进行随机访问&#xff0c;还有响应客户请求&#xff0c;必然是效率过低。因此&am…

FPGA入门系列17--task

文章简介 本系列文章主要针对FPGA初学者编写&#xff0c;包括FPGA的模块书写、基础语法、状态机、RAM、UART、SPI、VGA、以及功能验证等。将每一个知识点作为一个章节进行讲解&#xff0c;旨在更快速的提升初学者在FPGA开发方面的能力&#xff0c;每一个章节中都有针对性的代码…

如何选择传感器输出模式——电流输出还是电压输出?

一 背景及挑战 传感器在汽车测试系统中发挥着信息的采集和传输作用&#xff0c;可以称为汽车的“神经元”。 按照功能可以将传感器分为压力传感器、流量传感器、温湿度传感器和电流传感器等。传感器的主要指标是精度、测量范围和响应时间等。在满足指标的情况下&#xff0c;通…

御黑行动来袭--助力三月重保,构筑安全防线!

三月重保在即&#xff0c;重要网站及业务系统“零风险 零事故”是终极目标&#xff0c;作为业界网络安全实战派“老兵”--知道创宇将一如既往&#xff0c;为您提供重保期间“万无一失”的重要网站及业务系统防护。 值此三月重保的重要备战期&#xff0c;知道创宇推出由主力产品…

高灵敏度压电传感器频率温度特性测量中的TEC型精密温控系统

摘要&#xff1a;为解决石英晶体微量天平这类压电传感器频率温度特性全自动测量中存在的温度控制精度差和测试效率低的问题&#xff0c;本文在TEC半导体制冷技术基础上&#xff0c;提出了小尺寸、高精度和全自动程序温控的解决方案&#xff0c;给出了温控装置的详细结构和实现高…

计算机网络常见面试题总结

网络分层结构 计算机网络体系大致分为三种&#xff0c;OSI七层模型、TCP/IP四层模型和五层模型。一般面试的时候考察比较多的是五层模型。 TCP/IP五层模型&#xff1a;应用层、传输层、网络层、数据链路层、物理层。 应用层&#xff1a;为应用程序提供交互服务。在互联网中的…

一、产品经理——【岗位和能力要求】【项目流程】【产品体验报告】

0. 产品经理课程路线图 产品基础阶段&#xff1a;核心目的是了解行业、掌握技能 1. 认识互联网行业 1.1. 传统行业 vs 互联网行业 1.2. 互联网行业概念 1.3. 小结 2. 认识产品经理 2.1. 不同场景下的产品经理的职责差异 公司团队、领导对产品经理的期望不同&#xff0c;做的…

交叉编译的概念及交叉编译工具的安装

目录 一.什么是交叉编译 二.为什么要交叉编译&#xff1f; 三.交叉编译链的安装 四.相关使用方法 五.软连接 一.什么是交叉编译 交叉编译是指将一种编程语言编写的程序编译成另一种编程语言的程序&#xff0c;通常是在不同的操作系统或硬件环境中使用的。这种编译过程会产…

【手写 Vuex 源码】第十一篇 - Vuex 插件的开发

一&#xff0c;前言 上一篇&#xff0c;主要介绍了 Vuex-namespaced 命名空间的实现&#xff0c;主要涉及以下几个点&#xff1a; 命名空间的介绍和使用&#xff1b;命名空间的逻辑分析与代码实现&#xff1b;命名空间核心流程梳理&#xff1b; 本篇&#xff0c;继续介绍 Vu…

GWAS:mtag (Multi-Trait Analysis of GWAS) 分析

mtag (Multi-Trait Analysis of GWAS)作用&#xff1a;通过对多个表型相似的GWAS summary结果进行联合分析&#xff0c;发现更多的表型相关基因座。 以抑郁症状、神经质和主观幸福感这三个表型为例&#xff0c;分别对他们进行GWAS分析&#xff0c;鉴定得到32、9 和 13个基因座与…

前端食堂技术周刊第 70 期:Volar 的新开端、Lighthouse 10、良好的组件设计、React 纪录片、2022 大前端总结

美味值&#xff1a;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f; 口味&#xff1a;黑巧克力 食堂技术周刊仓库地址&#xff1a;https://github.com/Geekhyt/weekly 本期摘要 Volar 的新开端Chrome 110 的新功能Lighthouse 10Nuxt v3.2.0加速 JavaSc…

【github】解决超限制文件上传失败问题

之前因为push的一堆文件中有个104MB的大文件在里面&#xff0c;导致push一直失败一直失败超时又报错 一开始我还以为是VPN的问题&#xff0c;搞了好久都没解决 后来一步一步回撤发现是因为卡在了我那个104MB的文件这里 查阅了github的官方文档 关于 GitHub 上的大文件 - Git…

【计算机网络期末复习】第五章 传输层

✍个人博客&#xff1a;https://blog.csdn.net/Newin2020?spm1011.2415.3001.5343 &#x1f4e3;专栏定位&#xff1a;为想复习学校计算机网络课程的同学提供重点大纲&#xff0c;帮助大家渡过期末考~ &#x1f4da;专栏地址&#xff1a; ❤️如果有收获的话&#xff0c;欢迎点…

运动蓝牙耳机怎么选、最适合运动的蓝牙耳机推荐

很多人喜欢跑步时听歌来放松心情起到解压效果。但一般的无线蓝牙耳机很容易脱落丢失&#xff0c;甚至因为防水效果太菜导致耳机进水&#xff0c;很容易就损坏耳机了&#xff0c;加上运动出汗给耳腔带来的黏腻感与长期佩戴引发的疼痛感&#xff0c;这时一款好的运动设备就显得尤…

基于ISO13400 (DoIP) 实现车辆刷写

近年来&#xff0c;在整车研发中基于以太网实现车辆高带宽通讯无疑是人们热议的话题。无论是车内基于车载以太网减少线束成本&#xff0c;实现ADAS、信息娱乐系统等技术&#xff0c;还是基于新的电子电气架构以及远程诊断需求&#xff0c;实现以太网诊断&#xff08;DoIP&#…

prepend和append同时使用的时候,prepend中的内容不显示

前几天做项目的时候&#xff0c;遇到一个需求&#xff0c;需要做一个类似于下面的样式&#xff1a; 当我看完element的时候&#xff0c;自信满满&#xff0c;这不就是prepend和append嘛&#xff0c;简单&#xff01;&#xff01;&#xff01;此时的我不会想到后续经历的坎坷。 …

手语检测识别

论文&#xff1a;Real-Time Sign Language Detection using Human Pose Estimation Github&#xff1a;https://github.com/google-research/google-research/tree/master/sign_language_detection SLRTP 2020 手语识别任务包括手语检测&#xff08;Sign language detection&a…

蓝桥杯C/C++VIP试题每日一练之回形取数

💛作者主页:静Yu 🧡简介:CSDN全栈优质创作者、华为云享专家、阿里云社区博客专家,前端知识交流社区创建者 💛社区地址:前端知识交流社区 🧡博主的个人博客:静Yu的个人博客 🧡博主的个人笔记本:前端面试题 个人笔记本只记录前端领域的面试题目,项目总结,面试技…

以掘金示例,利用内链/外链进行网站SEO优化

前言 内链&#xff1a;从自己网站的一个页面指向另外一个页面。通过内链让网站内部形成网状结构&#xff0c;让蜘蛛的广度和深度达到最大化。 外链&#xff1a;在别的网站导入自己网站的链接。通过外链提升网站权重&#xff0c;提高网站流量。 一般来说&#xff0c;内链和外链…