Android TV开发之VerticalGridView
Android TV应用开发和手机应用开发是一样的,只是多了焦点控制,即选中变色。
androidx.leanback.widget.VerticalGridView
继承 BaseGridView , BaseGridView 继承 RecyclerView 。
所以 VerticalGridView 就是 RecyclerView ,使用方法和 RecyclerView 一样。
既然一样,直接用 RecyclerView 不就行了,为什么要用它 ?
因为它的特性:
- 1.当列表滚动时,有翻页时选中项默认在中间,无翻页则逐渐滚动到表头或者表尾。(有点绕,看 gif 对比后就明白了)
- 2.焦点事件的处理,它提供了
setSelectedPosition(int position)
和getSelectedPosition()
方法,方便处理TV焦点事件。
列表默认是竖向排列,不用做 LayoutManager 的处理。
如果要横向排列,也是可以的,但是 特性1 就失效了。why ,看名字就知道它适用于横向排列。
要横向排列且有特性1 ,就用它的兄弟 androidx.leanback.widget.HorizontalGridView
。
在TV应用开发中,配合遥控器上下按键的操作,这个特性让页面操作更友好、丝滑。
对比
都是在模拟器中按遥控器下键。
RecyclerView :
VerticalGridView :
开始使用,其实使用方法和 RecyclerView 是基本一样的。
添加依赖
implementation 'androidx.leanback:leanback:1.1.0-rc01'
编写布局文件
很简单,VerticalGridView 居中显示, 底部添加 4 个 Button 用于增、删、改、交换。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".recyclerview.VerticalGridViewActivity"><androidx.leanback.widget.VerticalGridViewandroid:id="@+id/vertical_gridview"android:layout_width="600dp"android:layout_height="300dp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent"app:layout_constraintVertical_bias="0.498" /><LinearLayoutandroid:layout_width="0dp"android:layout_height="40dp"android:orientation="horizontal"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"><Buttonandroid:id="@+id/button_vg_add"android:text="add"android:onClick="onVGButtonClick"android:layout_width="0dp"android:layout_weight="1"android:layout_height="match_parent"/><Buttonandroid:id="@+id/button_vg_remove"android:text="remove"android:onClick="onVGButtonClick"android:layout_width="0dp"android:layout_weight="1"android:layout_height="match_parent"/><Buttonandroid:id="@+id/button_vg_update"android:text="update"android:onClick="onVGButtonClick"android:layout_width="0dp"android:layout_weight="1"android:layout_height="match_parent"/><Buttonandroid:id="@+id/button_vg_move"android:text="move"android:onClick="onVGButtonClick"android:layout_width="0dp"android:layout_weight="1"android:layout_height="match_parent"/></LinearLayout></androidx.constraintlayout.widget.ConstraintLayout>
编写Adapter
和 RecyclerView Adapter 的写法是一样的,
onCreateViewHolder
中加载布局文件,
onBindViewHolder
中进行数据处理,
getItemCount
返回数据容量,为 0 的话UI是加载不出来的,
OnVGItemClickListener 是我自己加的接口,方便 Activity 监听 item 点击的回调。如果没有需求,可以直接在 onBindViewHolder 中做点击事件处理。
package com.test.luodemo.recyclerview;import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;import com.test.luodemo.R;import java.util.List;public class VGAdapter extends RecyclerView.Adapter<VGAdapter.VGViewHolder> {private OnVGItemClickListener vgItemClickListener;public interface OnVGItemClickListener{void onVGItemClick(View view, int position);}private List<String> dataList;public VGAdapter(List<String> data, OnVGItemClickListener listener) {dataList = data;vgItemClickListener = listener;}@NonNull@Overridepublic VGViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_vertical_gridview, parent, false);return new VGViewHolder(v);}@Overridepublic void onBindViewHolder(@NonNull VGViewHolder holder, int position) {holder.textView.setText(dataList.get(position));holder.itemView.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {if (vgItemClickListener != null) {vgItemClickListener.onVGItemClick(holder.itemView ,position);}}});}@Overridepublic int getItemCount() {return dataList != null ? dataList.size() : 0;}public static class VGViewHolder extends RecyclerView.ViewHolder{TextView textView;public VGViewHolder(@NonNull View itemView) {super(itemView);textView = (TextView) itemView.findViewById(R.id.item_vg_textview);}}
}
item 的布局文件 R.layout.item_vertical_gridview 很简单,就一个 TextView ,
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="25dp"android:focusable="true"android:focusableInTouchMode="true"android:background="@drawable/sel_item"><TextViewandroid:id="@+id/item_vg_textview"android:duplicateParentState="true"android:textColor="@drawable/sel_item_textview"android:textSize="16sp"android:gravity="center"android:layout_width="match_parent"android:layout_height="wrap_content"/>
</LinearLayout>
两个 drawable (sel_item 、sel_item_textview)用 selector 实现,方便区分是否选中,选中有变色效果。
就不用做 item 的 setOnFocusChangeListener(OnFocusChangeListener l) 处理了。
res/drawable/sel_item.xml ,
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"><item android:drawable="@color/my_red" android:state_focused="true" /><item android:drawable="@color/my_red" android:state_selected="true" /><item android:drawable="@android:color/transparent" />
</selector>
res/drawable/sel_item_textview.xml ,
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"><item android:color="@android:color/white" android:state_focused="true" /><item android:color="@android:color/white" android:state_selected="true" /><item android:color="@android:color/black" />
</selector>
初始化
findViewById 找到布局。
setAdapter 设置适配器,传入数据。
requestFocus()
:TV应用开发常用,获取焦点,去掉的话默认是没选中的,加上就默认选中第一个(如无其他焦点操作)。
setVerticalSpacing(int spacing)
:设置横向排列的 Item 之间的间距。
setHorizontalSpacing(int spacing)
:设置纵向排列的 Item 之间的间距,适用于 HorizontalGridView 。
setSelectedPosition(int position)
:设置选中某一项,获得焦点。
scrollToPosition(int position)
:滚动到指定项并获取焦点;如果使用了这个,可以不用 setSelectedPosition(int position) 。
getSelectedPosition()
:获取选中项。
没有指定 LayoutManager ,默认是竖向排列。
public class VerticalGridViewActivity extends AppCompatActivity {private VerticalGridView verticalGridView;private VGAdapter adapter;private List<String> mList;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_vertical_grid_view);Objects.requireNonNull(getSupportActionBar()).setTitle(VerticalGridViewActivity.class.getSimpleName());mList = new ArrayList<>();for (int i = 0; i < 30 ; i++){mList.add("item" + i);}verticalGridView = (VerticalGridView)findViewById(R.id.vertical_gridview);adapter = new VGAdapter(mList, new VGAdapter.OnVGItemClickListener() {@Overridepublic void onVGItemClick(View view, int position) {Toast.makeText(VerticalGridViewActivity.this, "you click position" + position, Toast.LENGTH_SHORT).show();}});verticalGridView.setAdapter(adapter);verticalGridView.requestFocus();verticalGridView.setVerticalSpacing(10);//verticalGridView.setSelectedPosition(0);verticalGridView.scrollToPosition(28);}
}
增、删、改、交换
刷新可以直接用全局刷新 notifyDataSetChanged()
,但是不友好,局部刷新时没必要这样。
局部刷新用 notifyItemRangeChanged(int positionStart, int itemCount)
方法,
增、删、改、交换 都涉及它 ,
两个参数,第一个参数是 第一个发生变化的 item 的下标,第二个参数是发生变化(包括数据变化和位置变化)的 item 的个数。
/*** Notify any registered observers that the <code>itemCount</code> items starting at* position <code>positionStart</code> have changed.* Equivalent to calling <code>notifyItemRangeChanged(position, itemCount, null);</code>.** <p>This is an item change event, not a structural change event. It indicates that* any reflection of the data in the given position range is out of date and should* be updated. The items in the given range retain the same identity.</p>** @param positionStart Position of the first item that has changed* @param itemCount Number of items that have changed* @see #notifyItemChanged(int)*/public final void notifyItemRangeChanged(int positionStart, int itemCount) {mObservable.notifyItemRangeChanged(positionStart, itemCount);}
增加
新增一项。
新增项放在 index = 2 处,第一个变化的 index 是 2 ,
indext 从 2 到 mList.size() - 1 的 item 都发生了变化,
变化的 item 个数就是 mList.size() - 2 ,计算方法 “头减尾加1” , mList.size() - 1 - 2 + 1 。
mList.add(2, "new add item");
adapter.notifyItemInserted(2);
adapter.notifyItemRangeChanged(2, mList.size() - 2);
删除
删除一项。
删除第 index = 3 的 item ,第一个变化的 index 是 3 ,发生变化的 item 个数是 mList.size() - 3 ,
adapter.notifyItemRemoved(3);
mList.remove(3);
adapter.notifyItemRangeChanged(3, mList.size() - 3);
修改
修改第3项
mList.set(3 , "new item 3");
adapter.notifyItemChanged(3);
交换
交换第 3 、 第 5 项。
第一个变化的 index 是 Math.min(3,5) ,
发生变化的 item 个数是 Math.abs(3-5) + 1 ,Math.abs(int a) 是取绝对值,也就是说 index 3/4/5 的 item 都发生了变化的。
明明只是交换 index 3 和 index 5 ,为什么 index4 也发生了变化?
import java.util.Collections;Collections.swap(mList, 3 , 5);
adapter.notifyItemMoved(3,5);
adapter.notifyItemRangeChanged(Math.min(3,5) , Math.abs(3-5) + 1);
Collections.swap(List<?> list, int i, int j)
是交换列表元素。
相关文章:

Android TV开发之VerticalGridView
Android TV应用开发和手机应用开发是一样的,只是多了焦点控制,即选中变色。 androidx.leanback.widget.VerticalGridView 继承 BaseGridView , BaseGridView 继承 RecyclerView 。 所以 VerticalGridView 就是 RecyclerView ,使…...
SpringBoot+Vue项目添加腾讯云人脸识别
一、引言 人脸识别是一种基于人脸特征进行身份认证和识别的技术。它使用计算机视觉和模式识别的方法,通过分析图像或视频中的人脸特征,例如脸部轮廓、眼睛、鼻子、嘴巴等,来验证一个人的身份或识别出他们是谁。 人脸识别可以应用在多个领域…...
什么是IPv4?什么又是IPv6?
IPv4网络IPv4地址 IPv6网络IPv6地址 路由总结感谢 💖 hello大家好😊 IPv4网络 IPv4(Internet Protocol Version 4)是当今互联网上使用的主要网络协议。 IPv4地址 IPv4 地址有32位,通常使用点号分隔的四个十进制八位…...

飞腾FT-2000/4、D2000 log报错指导(3)
在爱好者群中遇见了很多的固件问题,这里总结记录了大家的交流内容和调试心得。主要是飞腾桌面CPU FT-2000/4 D2000相关的,包含uboot和UEFI。希望对大家调试有所帮助。 这个专题会持续更新,凑够一些就发。 23 在s3 唤醒时报错如下 check suspend ,Platform exception report…...

基于安卓的考研助手系统app 微信小程序
,设计并开发实用、方便的应用程序具有重要的意义和良好的市场前景。HBuilder技术作为当前最流行的操作平台,自然也存在着大量的应用服务需求。 本课题研究的是基于HBuilder技术平台的安卓的考研助手APP,开发这款安卓的考研助手APP主要是为了…...

Leetcode:238. 除自身以外数组的乘积【题解超详细】
纯C语言实现(小白也能看明白) 题目 给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数…...

基于单片机的智能数字电子秤proteus仿真设计
一、系统方案 1、当电子称开机时,单片机会进入一系列初始化,进入1602显示模式设定,如开关显示、光标有无设置、光标闪烁设置,定时器初始化,进入定时器模式,如初始值赋值。之后液晶会显示Welcome To Use Ele…...

大数据(二)大数据行业相关统计数据
大数据(二)大数据行业相关统计数据 目录 一、大数据相关的各种资讯 二、转载自网络的大数据统计数据 2.1、国家大数据政策 2.2、产业结构分析 2.3、应用结构分析 2.4、数据中心 2.5、云计算 一、大数据相关的各种资讯 1. 据IDC预测࿰…...

Ruoyi安装部署(linux环境、前后端不分离版本)
目录 简介 1 新建目录 2 安装jdk 2.1 jdk下载 2.2 解压并移动文件夹到/data/service目录 2.3 配置环境变量 3 安装maven 3.1 进入官网下载最新的maven 3.2 解压并移动文件夹到/data//service目录 3.3 配置环境变量 3.4 配置本地仓库地址与阿里云镜像 4 安装git 4.…...

PHP聚合支付网站源码/对接十多个支付接口 第三方/第四方支付/系统源码
PHP聚合支付网站源码/对接十多个支付接口 第三方/第四方支付/系统源码 内附数十个支付接口代码文件。 下载地址:https://bbs.csdn.net/topics/616764485...

容器化微服务:用Kubernetes实现弹性部署
随着云计算的迅猛发展,容器化和微服务架构成为了构建现代应用的重要方式。而在这个过程中,Kubernetes(常简称为K8s)作为一个开源的容器编排平台,正在引领着容器化微服务的部署和管理革命。本文将深入探讨容器化微服务的…...

DevOps系列文章 之 Python基础
Python语法结构 语句块缩进 1.python代码块通过缩进对齐表达代码逻辑而不是使用大括号 2.缩进表达一个语句属于哪个代码块 3.缩进风格 : 建议使用四个空格 如果是Linux系统的话,可以这样做,实现自动缩进 : vim ~/.vimrc set ai…...

Harbour.Space Scholarship Contest 2023-2024 (Div. 1 + Div. 2) A ~ D
比赛链接 A 正常枚举就行,从最后一位往前枚举,-1、-2、-3...这样 #include<bits/stdc.h> #define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0); #define endl \nusing namespace std;typedef pair<int, int> PII; typedef long l…...

[管理与领导-53]:IT基层管理者 - 8项核心技能 - 8 - 持续改进
前言: 管理者存在的价值就是制定目标,即目标管理、通过团队(他人)拿到结果。 要想通过他人拿到结果: (1)目标:制定符合SMART原则的符合业务需求的目标,团队跳一跳就可以…...

芯片验证板卡设计原理图:446-基于VU440T的多核处理器多输入芯片验证板卡
基于VU440T的多核处理器多输入芯片验证板卡 一、板卡概述 基于XCVU440-FLGA2892的多核处理器多输入芯片验证板卡为实现网络交换芯片的验证,包括四个FMC接口、DDR、GPIO等,北京太速科技芯片验证板卡用于完成甲方的芯片验证任务,多任务…...

几个nlp的小任务(机器翻译)
几个nlp的小任务(机器翻译) 安装依赖库数据集介绍与模型介绍加载数据集看一看数据集的样子评测测试数据预处理测试tokenizer处理目标特殊的token预处理函数对数据集的所有数据进行预处理微调预训练模型设置训练参数需要一个数据收集器,把处理好数据喂给模型设置评估方法参数…...

飞腾X100 LPDDR颗粒线序配置辅助工具
B站讲解视频: 正文内容: 一、 飞腾X100显存使用LPDDR4时,需要工程师在X100的固件中去配置线序交换说明,就类似下面这个: 图1 我们需要输入每个slice中DQ的线序,也需要输入slice之间的交换关系,这个工作量也不小,同时容易出现错误,所以开发了一款辅助小工具,…...

二、数学建模之整数规划篇
1.定义 2.例题 3.使用软件及解题 一、定义 1.整数规划(Integer Programming,简称IP):是一种数学优化问题,它是线性规划(Linear Programming,简称LP)的一个扩展形式。在线性规划中&…...
C语言日常刷题 4
文章目录 题目答案与解析123456 题目 1、设变量已正确定义,以下不能统计出一行中输入字符个数(不包含回车符)的程序段是( ) A: n0;while(chgetchar()!‘\n’)n; B: n0;while(getchar()!‘\n’)n; C: for(n0;getchar()…...

MyBatis plus 多数据源实现
1. 项目背景 最近写文章发布到【笑小枫】小程序和我的个人网站上,因为个人网站用的是halo框架搭建,两边数据结构不一致,导致我每次维护文章都需要两边维护,这就很烦~ 于是,本文就诞生了。通过项目连接这两个数据库&a…...

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明
LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造,完美适配AGV和无人叉车。同时,集成以太网与语音合成技术,为各类高级系统(如MES、调度系统、库位管理、立库等)提供高效便捷的语音交互体验。 L…...

装饰模式(Decorator Pattern)重构java邮件发奖系统实战
前言 现在我们有个如下的需求,设计一个邮件发奖的小系统, 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其…...
R语言AI模型部署方案:精准离线运行详解
R语言AI模型部署方案:精准离线运行详解 一、项目概述 本文将构建一个完整的R语言AI部署解决方案,实现鸢尾花分类模型的训练、保存、离线部署和预测功能。核心特点: 100%离线运行能力自包含环境依赖生产级错误处理跨平台兼容性模型版本管理# 文件结构说明 Iris_AI_Deployme…...
MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例
一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...

微信小程序 - 手机震动
一、界面 <button type"primary" bindtap"shortVibrate">短震动</button> <button type"primary" bindtap"longVibrate">长震动</button> 二、js逻辑代码 注:文档 https://developers.weixin.qq…...

深入解析C++中的extern关键字:跨文件共享变量与函数的终极指南
🚀 C extern 关键字深度解析:跨文件编程的终极指南 📅 更新时间:2025年6月5日 🏷️ 标签:C | extern关键字 | 多文件编程 | 链接与声明 | 现代C 文章目录 前言🔥一、extern 是什么?&…...
CMake控制VS2022项目文件分组
我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...

深度学习习题2
1.如果增加神经网络的宽度,精确度会增加到一个特定阈值后,便开始降低。造成这一现象的可能原因是什么? A、即使增加卷积核的数量,只有少部分的核会被用作预测 B、当卷积核数量增加时,神经网络的预测能力会降低 C、当卷…...
Angular微前端架构:Module Federation + ngx-build-plus (Webpack)
以下是一个完整的 Angular 微前端示例,其中使用的是 Module Federation 和 npx-build-plus 实现了主应用(Shell)与子应用(Remote)的集成。 🛠️ 项目结构 angular-mf/ ├── shell-app/ # 主应用&…...