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…...
51c自动驾驶~合集58
我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留,CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制(CCA-Attention),…...
中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试
作者:Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位:中南大学地球科学与信息物理学院论文标题:BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接:https://arxiv.…...
大型活动交通拥堵治理的视觉算法应用
大型活动下智慧交通的视觉分析应用 一、背景与挑战 大型活动(如演唱会、马拉松赛事、高考中考等)期间,城市交通面临瞬时人流车流激增、传统摄像头模糊、交通拥堵识别滞后等问题。以演唱会为例,暖城商圈曾因观众集中离场导致周边…...
从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路
进入2025年以来,尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断,但全球市场热度依然高涨,入局者持续增加。 以国内市场为例,天眼查专业版数据显示,截至5月底,我国现存在业、存续状态的机器人相关企…...
在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module
1、为什么要修改 CONNECT 报文? 多租户隔离:自动为接入设备追加租户前缀,后端按 ClientID 拆分队列。零代码鉴权:将入站用户名替换为 OAuth Access-Token,后端 Broker 统一校验。灰度发布:根据 IP/地理位写…...
【HTML-16】深入理解HTML中的块元素与行内元素
HTML元素根据其显示特性可以分为两大类:块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...
Python 实现 Web 静态服务器(HTTP 协议)
目录 一、在本地启动 HTTP 服务器1. Windows 下安装 node.js1)下载安装包2)配置环境变量3)安装镜像4)node.js 的常用命令 2. 安装 http-server 服务3. 使用 http-server 开启服务1)使用 http-server2)详解 …...
Kafka主题运维全指南:从基础配置到故障处理
#作者:张桐瑞 文章目录 主题日常管理1. 修改主题分区。2. 修改主题级别参数。3. 变更副本数。4. 修改主题限速。5.主题分区迁移。6. 常见主题错误处理常见错误1:主题删除失败。常见错误2:__consumer_offsets占用太多的磁盘。 主题日常管理 …...
JDK 17 序列化是怎么回事
如何序列化?其实很简单,就是根据每个类型,用工厂类调用。逐个完成。 没什么漂亮的代码,只有有效、稳定的代码。 代码中调用toJson toJson 代码 mapper.writeValueAsString ObjectMapper DefaultSerializerProvider 一堆实…...
