使用 leanback 库 GridView 管理AnroidTV的焦点

一、前情提要

  • 我当前需要开发一个TV应用,但是之前处理过的焦点问题的很少,现在空下来了,对过往的工作做一个总结分享。
  • 在手机APP开发中常用的 RecycleView 在 TV 中开发时,无法解决大量的焦点问题,所以使用leanback进行列表数据展示,以此来解决焦点问题。本文主要记录 leanback 库的基础用法以及一些技巧分享

二、leanback 库 GridView 的基础使用

1. 引入 leanback 库

// 引入 leanback 库
implementation 'com.android.support:leanback-v17:28.0.0'

2. xml中引入容器

<!--xml中使用GrideView,以Vertical竖向为例-->
<androidx.leanback.widget.VerticalGridViewandroid:layout_width="match_parent"android:layout_height="match_parent"app:focusOutEnd="true"tools:listitem="@layout/item_choose_zip" />
<!--GrideView系列中的一些特殊属性值-->
<declare-styleable name="lbBaseGridView"><!--允许方向键在视图的前端(位置= 0处)进行导航,默认值为false  --><attr format="boolean" name="focusOutFront"/><!-- 允许DPAD键在视图末尾导航出去,默认值为false --><attr format="boolean" name="focusOutEnd"/><!-- 允许使用DPAD键导航到第一行之外,对于HorizontalGridView,它是顶部边缘,对于VerticalGridView,它是“开始”边缘。默认值为true。  --><attr format="boolean" name="focusOutSideStart"/><!-- 允许DPAD键导航出最后一行,对于HorizontalGridView,它是底部边缘,对于VerticalGridView,它是“结束”边缘。默认值为true。  --><attr format="boolean" name="focusOutSideEnd"/><!-- 定义两个项目之间的水平空间 --><attr name="android:horizontalSpacing"/><!-- 定义两个项目之间的垂直空间 --><attr name="android:verticalSpacing"/><!-- 定义子视图的gravity --><attr name="android:gravity"/>
</declare-styleable>

3. kotlin/java 中填充数据

  • 其实至此、GridView 和 RecycleView 的使用是没有区别的,但是在填充数据时,GridView 中使用了不一样的方式。
  • GridView 的 adapter-ViewHolder 概念被淡化,开发中不再需要过多的关注这一步,已被封装为 ItemBridgeAdapter(继承自RecycleView.Adapter)ObjectAdapter,而 ObjectAdapter的初始化需要一个新的 玩意儿Presenter
  • 这个 Presenter不是MVP结构中的Presenter,但是他们的功能以及职责是相似的,用于承载数据,并将数据封装到视图中。这里以一个例子来说明使用方法。
<!--写一个 item 的 layout-->
<TextViewandroid:id="@+id/gvItem"android:layout_width="match_parent"android:layout_height="@dimen/px100"/>
// 写一个 Presenter 用于接收数据并将数据显示到 item 的视图
class TestPresenter: Presenter() {override fun onCreateViewHolder(parent: ViewGroup): ViewHolder = DataBindingUtil.inflate(LayoutInflater.from(parent.context),R.layout.item_test,parent,false)override fun onBindViewHolder(viewHolder: ViewHolder?, item: Any?) {// 在Presenter中,没有泛型的概念,所以需要自行判断类型来传参。// 事实上,在leanback 库这个模块的设计理念中,它支持你以多种形式来构建自己的 Presenter// 1. 你可以将你的 Presenter 指定为独立的专为一种数据服务,比如当前我们固定以 String 类型接收显示数据if(viewHolder is MViewHolder) {if (item is String) {viewHolder.bindData(item)}}}override fun onUnbindViewHolder(viewHolder: ViewHolder?) {// 可以在这里回收资源}inner class MViewHolder(mBinding: TestItemBinding) : ViewHolder(mBinding.root) {override fun bindData(data: String) {mBinding.gvItem.setText(data)}}
}
// 使用 Presenter 向 GridView 中填充数据// 列表数据格式相同,即仅一种 Presenter时
val mAdapter = ArrayObjectAdapter(MPresenter())// 如果你有多种数据混合,可以使用这种方式初始化 ObjectAdapter
val mAdapter = ArrayObjectAdapter(object : PresenterSelector() {override fun getPresenter(item: Any?): Presenter {if (item is String) {return MPresenter()} else {...}}
})val itemBridgeAdapter = ItemBridgeAdapter(mAdapter)
binding.chooseZipGridView.adapter = itemBridgeAdapterval testData = arrayOf("111", "222", "333")
mAdapter.clear()
mAdapter.addAll(testData)
// 其实还有一个 setData( list, diff) 的方法,但是这个方法自身存在问题,所以不建议使用,后边会讲// 在 leanback 的Presenter中, Presenter 失去了很多 RecycleView 的Adapter信息
// - 比如itemType、itemPosition 等
// - 事实上这些信息都在 ItemBridgeAdapter 中(正如前边所说,BridgeAdapter 继承自 RecycleView.Adapter)
// - 如果你需要使用这些信息,你需要使用别的方法
val bridgeAdapter = ItemBridgeAdapter(mAdapter)
// 你可以在这个回调中进行处理。
bridgeAdapter.setAdapterListener(object : ItemBridgeAdapter.AdapterListener() {override fun onAddPresenter(presenter: Presenter?, type: Int) {}override fun onCreate(viewHolder: ItemBridgeAdapter.ViewHolder?) {}override fun onBind(viewHolder: ItemBridgeAdapter.ViewHolder?) {}override fun onBind(viewHolder: ItemBridgeAdapter.ViewHolder?, payloads: List<*>?) {onBind(viewHolder)}override fun onUnbind(viewHolder: ItemBridgeAdapter.ViewHolder?) {}override fun onAttachedToWindow(viewHolder: ItemBridgeAdapter.ViewHolder?) {}override fun onDetachedFromWindow(viewHolder: ItemBridgeAdapter.ViewHolder?) {}
})

三、优化结构,简化使用,提高效率

  • leanback库的 presenter没有泛型的概念,虽然会有更多的发展方向,但是对我而言,我是更需要泛型的存在的。
  • 如果你也同样需要像 RecycleView中一样,需要明确知道自己的数据类型,而不是全靠类型判断的话,可以参考本节内容,否则直接看下一节就好

1. 创建基类

// 创建 Presenter 基类import android.view.View
import android.view.ViewGroup
import androidx.leanback.widget.Presenterabstract class BaseGridPresenter<D, B: BaseGridViewHolder<*, D>>: Presenter() {override fun onCreateViewHolder(parent: ViewGroup): ViewHolder = getViewHolder(parent)abstract fun getViewHolder(parent: ViewGroup): Boverride fun onBindViewHolder(viewHolder: ViewHolder?, item: Any?) {castViewHolder(viewHolder)?.bindData(castItem(item) ?: return)}abstract fun castViewHolder(viewHolder: ViewHolder?): B?abstract fun castItem(item: Any?): D?override fun onUnbindViewHolder(viewHolder: ViewHolder?) {}
}
// 创建 ViewHolder 基类
import androidx.leanback.widget.Presenter.ViewHolder
import androidx.viewbinding.ViewBindingabstract class BaseGridViewHolder<B: ViewBinding, D>(val mBinding: B) : ViewHolder(mBinding.root) {abstract fun bindData(data: D)
}

2. 创建范本

Live Templates

  • 范本内容如下
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import com.loostone.ui.base.leanback.BaseGridPresenter
import com.loostone.ui.base.leanback.BaseGridViewHolder
import com.loostone.ui.extension.castTarget
import com.lscm.lskaraoke.Rclass $className$: BaseGridPresenter<$D$, $className$.MViewHolder>() {override fun getViewHolder(parent: ViewGroup) = MViewHolder(DataBindingUtil.inflate(LayoutInflater.from(parent.context),R.layout.$layout$,parent,false))override fun castViewHolder(viewHolder: ViewHolder?) = viewHolder.castTarget<MViewHolder>()override fun castItem(item: Any?) = item.castTarget<$D$>()class MViewHolder(mBinding: $B$) :BaseGridViewHolder<$B$, $D$>(mBinding) {override fun bindData(data: $D$) {}}
}
  • 配置范本默认值
    配置范本默认值
  • 其中,上文中的 castTarget 方法是扩展,方法如下:
inline fun <reified T> Any?.castTarget(): T? {return if (this is T) this else null
}

3. 使用范本快速创建 Presenter

  • 现在你已经配置了范本,在你需要创建一个新的presenter时,只需要新建一个类,然后在类中输入 presenter,即可完成一个 Presenter 的创建
    范本快速填充
  • 填写剩余的三个变量,即可完成这个新的 Presenter 的创建
    填写三个剩余的变量

四、坑点吐槽

  • 在前文中有提到,给 ObjectAdapter 设置数据有两种方法:
  1. clear + addAll
  2. setData
  • 事实上 setData 不仅可以少一行调用,还可以使用 DiffCallback,这个回调可以支持比对 item 数据,并结合 ItemAnimation 实现很多酷炫的动画效果,而使用方法也很简单:
mAdapter.setItems(dataList, object : DiffCallback<Any>() {override fun areItemsTheSame(oldItem: Any, newItem: Any): Boolean {// 判断是否为同一个 item,如果返回 true,则认为这两个是同一个 item// 会将本视图移动到 newItem 在新的数据中所处的位置,并触发 move 动画// 如果返回 false,则认为不是同一个 item// 在本轮判断结束后:// 1. 当前视图中显示的 所有未匹配到新数据的item将被移除,并触发 remove 动画// 2. 所有未匹配到旧数据的新数据将会为其创建新的 item,并触发 create 动画return true}override fun areContentsTheSame(oldItem: Any, newItem: Any): Boolean {// 判断数据是否有变化,仅在 areItemsTheSame 通过后才会判断// 如果返回true,则认为有变化,并触发 change 动画return true}override fun getChangePayload(oldItem: Any, newItem: Any): Any {// 负载标记 - 后续由 SimpleItemAnimator 调用判断是否需要触发动画return "state change"}
})// 为 GridView 添加动画
binding.grideView.itemAnimator = SimpleItemAnimator()
// 如果你需要调整动画,进行翻转等,可继承 SimpleItemAnimator 重写里边的动画
  • 注意!在数据变化不频繁时,此方法是可用的,且效果很好,可以很好的展示数据,处理焦点,以及动画
  • 但是如果数据频繁变化,将有可能触发 RecycleView 的内部错误 RecycleView$Recycler.unscrpView on a null object reference
    在这里插入图片描述
  • 这是GridView 的内部处理逻辑问题,具体错误原因这里就不过多赘述了(篇幅有些长了)解决方法很简单,改为 clear + setData 的方式就可以了

五、结尾

  1. 如果我的文章有帮到你,请给我一个点赞收藏,这对我真的很重要~
  2. 如果你有任何问题,欢迎在评论区提问交流~

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

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

相关文章

ElasticSearch核心之DSL查询语句实战

什么是DSL&#xff1f; Elasticsearch提供丰富且灵活的查询语言叫做DSL查询(Query DSL),它允许你构建更加复杂、强大的查询。 DSL(Domain Specific Language特定领域语言)以JSON请求体的形式出现。目前常用的框架查询方法什么的底层都是构建DSL语句实现的&#xff0c;所以你必…

git 学习总结

文章目录 一、 git 基础操作1、工作区2、暂存区3、本地仓库4、远程仓库 二、git 的本质三、分支git 命令总结 作者: baron 一、 git 基础操作 如图所示 git 总共有几个区域 工作区, 暂存区, 本地仓库, 远程仓库. 1、工作区 存放项目代码的地方&#xff0c;他有两种状态 Unm…

2024新版 黑马程序员《C++零基础入门》笔记——第一章24 三元运算符

1.三元运算符 2.代码实践 #include "iostream" using namespace std;int main() {// 表达式? v1 : v2;int num1, num2;cout << "请输入num1的值" << endl;cin >> num1;cout << "请输入num2的值" << endl;cin >…

Flink-CDC解析(第47天)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言1. CDC 概述1.1 什么是CDC&#xff1f;1.2 CDC的实现机制1) 基于主动查询的 CDC&#xff1a;2) 基于事件接收CDC&#xff1a; 前言 本文主要概述了Flink-CDC. …

万界星空科技灯具行业MES系统:点亮生产管理的未来

在快速迭代的灯具行业中&#xff0c;高效、精准的生产管理是企业保持竞争力的关键。万界星空科技推出的灯具行业MES&#xff08;制造执行系统&#xff09;系统&#xff0c;以其强大的功能和完善的管理体系&#xff0c;正成为众多灯具生产企业的首选解决方案。本文将重点介绍万界…

u盘文件删除如何恢复?u盘恢复数据的8个诀窍,新手必看!

随着互联网的普及&#xff0c;u盘已成为人手必备的一个工具&#xff0c;承载着我们许多的重要文件和数据。然而&#xff0c;当意外删除或丢失这些关键数据时&#xff0c;我们常常会感到无助。您是否也曾经面对过u盘文件误删的尴尬吗&#xff1f;亦或者是否曾为如何从u盘中恢复已…

【漏洞复现】phpStudy 小皮 Windows面板 存在RCE漏洞

靶场资料后台自行领取【靶场】 image-20240726092307252 PhpStudy小皮面板曝RCE漏洞&#xff0c;本质是存储型XSS引发。攻击者通过登录用户名输入XSS代码&#xff0c;结合后台计划任务功能&#xff0c;实现远程代码执行&#xff0c;严重威胁服务器安全。建议立即更新至安全版…

Study--Oracle-07-ASM相关参数(四)

一、ASM主要进程 1、ASM主要后台进程 ASM实例除了传统的DBWn、LGWR、CKPT、SMON和PMON等进程还包含如下几个新后台进程: 2、牛人笔记 邦德图文解读ASM架构,超详细 - 墨天轮 二、数据库实例于ASM实例之间的交互关系 数据库实例与ASM实例之间的交互关系涉及多个步骤和过程,…

ICML最佳论文SD3上线公共教程!DreamBench++图像自动评估新基准来了,实现人类偏好深度对齐

近日&#xff0c;ICML 2024 最佳论文公布了&#xff01;其中就包含年度图像生成的「网红模型」——Stable Diffusion 3&#xff08;简称 SD3&#xff09;。SD3 是由 Stability AI 开发的最新文本到图像生成模型&#xff0c;前段时间已经全网开源&#xff01;HyperAI 超神经现已…

【PyCharm】PyCharm 2024.1 的最新变化-代码补全与智能提示

目录 代码补全与智能提示 更智能的代码补全功能 基于机器学习的代码建议 上下文相关的代码片段推荐 全行代码补全支持 (JavaScript 和 TypeScript) 本地 ML 模型的全行补全 示例 示例 1: 基于上下文的代码建议 示例 2: 全行代码补全 详细对比示例 示例 3: 传统代码补…

【iOS】3G share仿写

3G share 前言登录、注册界面首页搜索搜索更换照片折叠cell 文章活动我的 前言 本周进行了3G share的仿写&#xff0c;这个仿写内容较多&#xff0c;用到了很多之前学习的东西&#xff0c;也学习了许多的新知识。 登录、注册界面 这两个界面主要运用属性传值和协议传值&…

5. 开发环境搭建

1. 概述 基于ubuntu20.04搭建开发环境 2. 开发环境安装 恒玄SDK编译&#xff0c;依赖gcc-arm的编译工具&#xff0c;编译工具由恒玄提供&#xff1b; 2.1 配置编译工具链的环境变量 修改~/.profile文件 source ~/.profile 2.2 安装依赖包 sudo apt install ccache sudo a…

YOLO 车辆测速

原文:YOLO 车辆测速 - 知乎 (zhihu.com) 单目测速:多目标追踪 + 单目测距 + 速度公式 原理 目标检测并追踪视频中车辆的车尾(假定摄像头安装在单行道上),根据连续两帧的检测框计算得到像素距离。然后通过预先计算的 ppm (pixel per meter) ——道路不同,其值不同——得…

MyBatis操作数据库 -- 动态SQL

T04BF &#x1f44b;专栏: 算法|JAVA|MySQL|Spring &#x1faf5; 与天斗其乐无穷 文章目录 1. 动态SQL<if>标签<trim>标签<where> 标签<set> 标签<foreach> 标签<include>标签注解方式 1. 动态SQL 动态sql能够实现不同条件下的sql拼接 …

大模型深度神经网络(Deep Neural Network, DNN)

大模型深度神经网络&#xff08;Deep Neural Network, DNN&#xff09;是一种复杂的机器学习模型&#xff0c;其特点在于包含多个隐藏层&#xff0c;从而赋予模型强大的非线性表达能力和对复杂数据模式的学习能力。以下是对大模型DNN的详细介绍&#xff1a; 一、基本概念 深度…

C++初阶:string(字符串)

✨✨所属专栏&#xff1a;C✨✨ ✨✨作者主页&#xff1a;嶔某✨✨ 为什么要学习string类 C语言中&#xff0c;字符串是以\0结尾的一些字符的集合&#xff0c;为了操作方便&#xff0c;C标准库中提供了一些str系列 的库函数&#xff0c;但是这些库函数与字符串是分离开的&#…

贪心算法总结(2)

一、买卖股票的最佳时机 . - 力扣&#xff08;LeetCode&#xff09; class Solution { public:int maxProfit(vector<int>& prices) {int miniINT_MAX;int ret0;for(int&price:prices){//遍历的时候&#xff0c;我们随时去更新最小的值&#xff0c;然后让每一位…

shell脚本学习以及案列练习

&#xff08;一&#xff09;用shell脚本自动化部署安装nginx 首先创建一个目录&#xff0c;用于存放该脚本 mkdir -p /root/shell 然后创建脚本文件 vim /root/shell/install_nginx.sh 再给脚本文件加上执行权限 chmod x /root/shell/install_nginx.sh 然后执行&#xff0c…

新手必备:iPhone新机官网验机流程详解

目录 一、准备工作 二、外包装检查 三、序列号查询 四、开箱验机 五、开机验机 六、功能检测 七、售后服务验证 八、总结 一、准备工作 检查包裹&#xff1a;确保快递包裹完好无损。准备录像设备&#xff1a;使用另一台设备录制整个验机过程&#xff0c;以防日后发生纠…

【JAVA开发笔记】Reids下载、安装、配置-Windows篇(超详细,含Redis可视化管理工具!!!)

目录 1. Redis 简介 2. 下载 Redis 安装包 3. 开启 Redis 服务 4. 配置环境变量 5. Redis 服务注册为系统服务 6. Redis 服务测试和简单使用 7. 下载安装 Redis 管理工具 8. 管理工具连接 Redis 服务器 1. Redis 简介 Redis&#xff08;Remote Dictionary Server&…