动手写一个简单的Android 表格控件支持固定列

Android 动手写一个简洁版表格控件

简介

源码已放到
Github
Gitee

作为在测绘地理信息行业中穿梭的打工人,遇到各种数据采集需求,既然有数据采集需求,那当然少不了数据展示功能,最常见的如表格方式展示。
当然,类似表格这些控件网上也有挺多开源的,但是经过我一番思考,决定自己动手撸一个,还能了解下原理。

实现思路

如下图所示,我们把表格拆分成三部分,表头、固定列、表格内容,其中固定列顾名思义,位置固定,内容部分,当宽度超过可视范围时,可左右滚动
表格结构
对于表格垂直方向的滚动,我们可以用Rrecyclerview 来实现,那么水平方向的滚动,我们可以使用HorizontalScrollerView,
这样我们就可以得到一个初步的表格雏形,对应类暂且叫RPWDataGridView
行设计

关键属性、接口代码:

class RPWDataGridView<T> @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null
) : LinearLayout(context, attrs) {private val headerView: RPWDataGridIRowItemView//表头private val recyclerView: RecyclerView//表格内容private val columns = mutableListOf<RPWDataGridColumn>()//列参数,每一行共用同一列参数,保证每个单元格的宽度一致private var horScrollOffset = 0//当前水平滚动偏移量,保证每一行滚动量一致private val dataSource = mutableListOf<T>()//数据源private var dataGridAdapter = DataGridAdapter() //数据适配器fun build(vararg columns: RPWDataGridColumn) {//构建表格结构//...}/*** 设置表格数据源*/fun setDataSource(data: List<T>) {//...}
}

众所周知,每一行里面又会按列分成狠多单元格,所以我们还得再把HorizontalScrollerView按列细分,里面单元格通过动态添加TextView来实现,由于需要固定列,所以为了方便实现固定的逻辑,我们做如下设计:

在这里插入图片描述
然后封装一个表格的行控件,暂且命名为RPWDataGridIRowItemView,该控件View的结构如上图所示,固定列使用一个LinearLayout ,滚动列使用HorizontalScrollerView, 代码层面,伪代码:

  1. View层:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/llRoot"android:layout_width="match_parent"android:layout_height="@dimen/ui_data_grid_row_min_height"android:background="@drawable/data_grid_view_row_item_background"android:clickable="true"android:focusable="true"android:focusableInTouchMode="true"android:orientation="horizontal"><LinearLayoutandroid:id="@+id/llFreezeColumn"android:layout_width="wrap_content"android:layout_height="match_parent"android:orientation="horizontal"android:showDividers="middle" /><Viewandroid:id="@+id/viewVerHeaderDivider"android:background="@color/ui_data_grid_header_divider_color"android:layout_width="@dimen/ui_data_grid_header_divider_size"android:layout_height="match_parent"/><com.rpw.view.RPWHorizontalScrollViewandroid:id="@+id/horScrollView"android:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:scrollbars="none"><LinearLayoutandroid:id="@+id/llScrollColumn"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="horizontal"android:showDividers="middle" /></com.rpw.view.RPWHorizontalScrollView>
</LinearLayout>
  1. 代码层
class RPWDataGridIRowItemView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null
) : LinearLayout(context, attrs) {//冻结列父布局private val llFreezeColumn: LinearLayout//滚动列父布局private val llScrollColumn: LinearLayout//RPWDataGridColumn为列参数fun addColumn(column: RPWDataGridView.RPWDataGridColumn) {if (column.freeze) {llFreezeColumn.addView(TextView())}else{llScrollColumn.addView(TextView())}}}

然后把他作为RecyclerViewItemView 加载到每一行中。
那么问题来了,每一行都有自己的滚动View,各滚各的,这跟表格也不一样。
所以,为了解决这个问题,我们需要给每个HorizontalScrollerView 注册滚动监听,当某个HorizontalScrollerView 发生滚动,我们把其他的HorizontalScrollerView 也设置同样的滚动量不就可以对齐了吗。
是的,但是在实现这个逻辑前,由于他不对外暴露滚动状态,我们还得继承HorizontalScrollerView 重写 onScrollChanged 函数,暂且命名为RPWHorizontalScrollView,我们专属的水平滚动View。
国际惯例,上关键代码:

public class RPWHorizontalScrollView extends HorizontalScrollView {@Overrideprotected void onScrollChanged(int l, int t, int oldl, int oldt) {super.onScrollChanged(l, t, oldl, oldt);if (null != listener)listener.onCustomScrollChange(RPWHorizontalScrollView.this, l, t, oldl, oldt);
//通知滚动变化}}

接下来,我们还需要补充一下对齐RecyclerView 中所有已加载的ItemView ,这个代码需要写到表格控件RPWDataGridView 中,与其他行共享同一偏移量,对齐关键代码如下:

/*** 对齐当前视图下每一行的滚动偏移*/private fun alignItems(scrollX: Int) {val layoutManager = recyclerView.layoutManager as LinearLayoutManagerfor (i in 0..layoutManager.childCount) {val v = layoutManager.getChildAt(i)if (v != null) {val vh = recyclerView.getChildViewHolder(v) as RPWDataGridView<*>.DataGridViewHoldervh.rowView.scrollTo(scrollX, 0)Log.i(TAG, "alignItems: $horScrollOffset")}}horScrollOffset = scrollXheaderView.setHorOffset(horScrollOffset)
tHorOffset(horScrollOffset)
//给表头也设置相同的滚动量}

然后在适配器中监听和绑定每一行的滚动量,给他设置到全局horScrollOffset 中,在适配器onBindViewHolder 的时候,给他设置这个偏移量,实现新的行也对齐。
完整封装的代码就不在这里详细展示了,有兴趣可以到Gitee上查看

使用方法

   with(rpwDataGridView) {verDividerParams.show = falseverDividerParams.showHeaderDivider = truehorDividerParams.show = truehorDividerParams.showHeaderDivider = true//region build column//构建表格结构build(RPWDataGridView.RPWDataGridColumn(DensityUtil.dpToPx(this@MainActivity, 100), "姓名", true),RPWDataGridView.RPWDataGridColumn(DensityUtil.dpToPx(this@MainActivity, 100), "密码", false),RPWDataGridView.RPWDataGridColumn(DensityUtil.dpToPx(this@MainActivity, 200), "身份证号码", false),RPWDataGridView.RPWDataGridColumn(DensityUtil.dpToPx(this@MainActivity, 200), "出生年月", false),RPWDataGridView.RPWDataGridColumn(DensityUtil.dpToPx(this@MainActivity, 60), "性别", false),RPWDataGridView.RPWDataGridColumn(DensityUtil.dpToPx(this@MainActivity, 150), "手机号码", false),RPWDataGridView.RPWDataGridColumn(DensityUtil.dpToPx(this@MainActivity, 150), "邮箱", false),RPWDataGridView.RPWDataGridColumn(DensityUtil.dpToPx(this@MainActivity, 300), "地址", false),)//endregion//绑定每行显示的数据setRowBuildListener(object : RPWDataGridView.RowBuildListener<ItemData> {override fun onBuildRow(rowItemView: RPWDataGridIRowItemView, data: ItemData) {rowItemView.cells[0].text = data.namerowItemView.cells[1].text = data.passwordrowItemView.cells[2].text = "11235842364564582"rowItemView.cells[3].text = "2024-04-28"rowItemView.cells[4].text = data.sexrowItemView.cells[5].text = data.phonerowItemView.cells[6].text = data.emailrowItemView.cells[7].text = data.address}})//监听单元格点击setRowClickListener(object : RPWDataGridView.RowClickListener<ItemData> {override fun onRowClick(data: ItemData, rowIndex: Int, columnIndex: Int) {Toast.makeText(this@MainActivity, "点击坐标[$rowIndex:$columnIndex]", Toast.LENGTH_SHORT).show()}override fun onRowLongClick(t: ItemData, rowIndex: Int, columnIndex: Any?): Boolean {rpwDataGridView.startSelect(true)return true}})//监听页面状态变化setStatusListener(object : RPWDataGridView.DataGridViewStatusListener {override fun onStatusChange(statusEnum: RPWDataGridViewStatusEnum) {Toast.makeText(this@MainActivity, "状态改变:$statusEnum", Toast.LENGTH_SHORT).show()}})val ds = mutableListOf<ItemData>()repeat(1000) {//添加1000条测试数据ds.add(ItemData("WPR$it",it.toString(),"$it","广东省广州市番禺区xxxxxx$it 号","123456789"))}setDataSource(ds)}

嗯嗯嗯~~按照这个思路,实现如下:
实现效果

总结

至此,简单的表格效果已有,目前发现有一些UI体验层面的bug,后面我会看情况在Gitee中完善,因为是想写一个简单易用的表格控件,所以对每个单元格里面的View都写死成TextView了,另一方面是我需求没那么复杂。。

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

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

相关文章

大模型时序预测初步调研20240506

AI预测相关目录 AI预测流程&#xff0c;包括ETL、算法策略、算法模型、模型评估、可视化等相关内容 最好有基础的python算法预测经验 EEMD策略及踩坑VMD-CNN-LSTM时序预测对双向LSTM等模型添加自注意力机制K折叠交叉验证optuna超参数优化框架多任务学习-模型融合策略Transform…

MySQL —— 表的基本操作

一、创建 1.语法 create table 表名称( 自定义变量1, 自定义变量2, 自定义变量3&#xff08;最后一个变量末尾不需要加任何标点符号&#xff09; )charset字符集 collate校验集 engine存储引擎; ps&#xff1a;若是不具体给字符集、校验集、储存引擎&#xff0c;则采用配置文件…

『跨端框架』Flutter环境搭建

『跨端框架』Flutter环境搭建 资源网站简介跨平台高性能发展历程跨平台框架的比较成功案例 环境搭建&#xff08;windows&#xff09;基础环境搭建Windows下的安卓环境搭建Mac下的安卓环境配置资源镜像JDKAndroid StudioFlutter SDK问题一问题二问题三修改项目中的Flutter版本 …

厂家自定义 Android Ant编译流程源码分析

0、Ant安装 Windows下安装Ant&#xff1a; ant 官网可下载 http://ant.apache.org ant 环境配置&#xff1a; 解压ant的包到本地目录。 在环境变量中设置ANT_HOME&#xff0c;值为你的安装目录。 把ANT_HOME/bin加到你系统环境的path。 Ubuntu下安装Ant&#xff1a; sudo apt…

visio studio 中.NET Core(.net8.0)框架和.net framewok 框架有什么区别?

更新vs到2022版本后&#xff0c;新建项目时就多出不少选项&#xff0c;这里来个大家分享下.NET Core&#xff08;.net8.0&#xff09;框架和.net framewok的区别 如下图&#xff0c;不带后缀的就是使用.NET Core框架&#xff0c;后续选项是.net8.0。 .net framewok框架选项&am…

从0到1:商场导览小程序开发笔记一

背景 购物中心与商场小程序&#xff1a;旨在提供便捷的购物、导航、活动报名、服务查询等功能&#xff0c;让用户更好地体验购物和享受服务。通过提供便捷的购物、信息查询和互动预约等功能&#xff0c;提升了商场的服务水平和用户体验&#xff0c;帮助商场与消费者建立更紧密…

YOLOv5入门(四)训练自己的目标检测模型

前言 通过前面几篇文章&#xff0c;已经完成数据集制作和环境配置&#xff08;服务器&#xff09;&#xff0c;接下来将继续实践如何开始训练自己数据集~ 往期回顾 YOLOv5入门&#xff08;一&#xff09;利用Labelimg标注自己数据集 YOLOv5入门&#xff08;二&#xff09;处…

Android 14 init进程解析

前言 当bootloader启动后&#xff0c;启动kernel&#xff0c;kernel启动完后&#xff0c;在用户空间启动init进程&#xff0c;再通过init进程&#xff0c;来读取init.rc中的相关配置&#xff0c;从而来启动其他相关进程以及其他操作。 init进程启动主要分为两个阶段&#xff1…

jsPDF + html2canvas + Vue3 + ts项目内,分页导出当前页面为PDF、A 页面内导出 B 页面的内容为PDF,隐藏导出按钮等多余元素

jsPDF html2canvas Vue3 ts Arco Design项目&#xff0c;分页导出当前页面为PDF、A 页面内导出 B 页面的内容为PDF&#xff0c;隐藏导出按钮等多余元素… 1.下载所需依赖 pnpm install --save html2canvaspnpm install --save jspdf引入依赖 <script setup lang"…

OpenCV 库来捕获和处理视频输入和相似度测量(73)

返回:OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 上一篇:OpenCV的周期性噪声去除滤波器(70) 下一篇 :OpenCV系列文章目录&#xff08;持续更新中......&#xff09; ​ 目标 如今&#xff0c;拥有数字视频录制系统供您使用是很常见的。因此&#xff0c;您…

FreeBSD RISCV 在QEME中实践-网络配置

在前一篇文章中&#xff0c;我们一起进行了FreeBSD RISCV 在QEME中实践 现在&#xff0c;让我们配置好网络吧&#xff01; 先上结论&#xff1a;用默认配置启动即可&#xff0c;网络就加载好了&#xff0c;只是不能ping罢了。因为不能ping&#xff0c;以为网络没通&#xff0…

opencv图片的平移-------c++

图片平移 cv::Mat opencvTool::translateImage(const cv::Mat& img, int dx, int dy) {// 获取图像尺寸int rows img.rows;int cols img.cols;// 定义仿射变换矩阵cv::Mat M (cv::Mat_<float>(2, 3) << 1, 0, dx, 0, 1, dy);// 进行仿射变换cv::Mat dst;cv…

[附源码+视频教程]暗黑纪元H5手游_架设搭建_畅玩三网全通西方3D世界_带GM

本教程仅限学习使用&#xff0c;禁止商用&#xff0c;一切后果与本人无关&#xff0c;此声明具有法律效应&#xff01;&#xff01;&#xff01;&#xff01; 教程是本人亲自搭建成功的&#xff0c;绝对是完整可运行的&#xff0c;踩过的坑都给你们填上了 一. 演示视频 暗黑纪…

目标检测——水下垃圾数据集DeepTrash

引言 亲爱的读者们&#xff0c;您是否在寻找某个特定的数据集&#xff0c;用于研究或项目实践&#xff1f;欢迎您在评论区留言&#xff0c;或者通过公众号私信告诉我&#xff0c;您想要的数据集的类型主题。小编会竭尽全力为您寻找&#xff0c;并在找到后第一时间与您分享。 …

[图解]不变式的构造和化简

1 00:00:02,420 --> 00:00:03,380 下面这个&#xff0c;我们来看 2 00:00:03,390 --> 00:00:09,940 X→select&#xff08;Y&#xff09;&#xff0c;用Y这个条件来筛选 3 00:00:09,950 --> 00:00:11,340 之后得到的集合 4 00:00:12,400 --> 00:00:14,390 forAl…

SD-WAN介绍,为何成为主推。

1、SD-WAN&#xff08;Software Defined Wide Area Network&#xff0c; 软件定义的广域网&#xff09; 将企业的分支、总部和多云之间互联起来&#xff0c;应用在不同混合链路&#xff08;MPLS&#xff0c;Internet&#xff0c;5G&#xff0c;LTE等&#xff09;之间选择最优的…

EmotionBench—— 基于 LLM 情绪的量化框架

介绍 大型语言模型&#xff08;LLM&#xff09;在近年来取得了显著的进展&#xff0c;这在计算机科学领域被视为一个重要的里程碑。像ChatGPT和Claude这样的综合性软件已经不再仅仅是用于句子校正、文本翻译和编程的工具&#xff0c;它们已经演进成为类似于人类的助手。因此&a…

Linux 操作系统IPC

目录 1、IPC简介 1.1、共享内存 1.1.1 创建/访问共享内存 1.1.2 映射 1.1.3 解除映射 1.1.4 删除/修改共享内存 1.2 信号量集 1.2.1 创建信号量集合 1.2.2 信号量的初始化 1.2.3 信号量的还原和消耗 1.3 消息队列 1.3.1 概念 1.3.3 添加消息队列 1.3.4 读取消息…

社区养老服务|基于Springboot+vue的社区养老服务平台设计与实现(源码+数据库+文档)

社区养老服务平台 目录 基于Java的社区养老服务平台设计与实现 一、前言 二、系统设计 三、系统功能设计 1用户信息管理 2 服务信息管理 3服务申请管理 4公告信息管理 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#…

【Linux系统化学习】网络套接字(编写简单的UDP服务端和客户端)

目录 理解源IP地址和目的IP地址 认识端口号 端口号和进程ID的区别 源端口号和目的端口号 认识TCP和UDP协议 TCP协议 UDP协议 网络字节序 socket编程接口 socket常见API sockaddr结构 简单的UDP网络程序 UDP服务端 创建套接字 填充本地网络信息 绑定 收取消息 …