当前位置: 首页 > news >正文

android 实现表格效果

效果如下:

代码实现:

  // 列的默认宽度(dp)private val defaultColumnWidthDp = 40private lateinit var excelTable: ExcelTable  /*** 初始化表格*/private fun initTableView() {// 初始化Excel表格excelTable = ExcelTable()// 初始化按钮事件setupButtons()// 添加初始行列initTable()// 更新表格显示updateTableDisplay()}// 初始化表格为3行4列,第一列为标题列private fun initTable() {// 添加3列,都是文本类型excelTable.addColumn(ColumnType.TEXT, CellData.TextData("工作地点"))excelTable.addColumn(ColumnType.TEXT, CellData.TextData("工作内容"))excelTable.addColumn(ColumnType.IMAGE, CellData.TextData("工作图片"))// 添加4行数据(包括标题行)// 已经添加了第一行作为标题行,再添加3行数据行// 已添加1行(标题行),再添加3行数据行,总共4行repeat(3) { rowIndex ->val row = excelTable.addRow()// 为数据行设置默认值for (col in 0 until 3) {val data = when (excelTable.getColumnType(col)) {ColumnType.TEXT -> CellData.TextData("")ColumnType.IMAGE -> CellData.ImageData(null)null -> null}data?.let { excelTable.setCellData(row, col, it) }}}}// 设置按钮点击事件private fun setupButtons() {mBinding.btnAddRow.singleClick {excelTable.addRow()updateTableDisplay()}mBinding.btnAddImageColumn.singleClick {if (excelTable.getColumnCount() >= 3) {toast("目前最多支持3列!")return@singleClick}PuzzleAddColumPopup.newInstance { content, type ->excelTable.addColumn(type, CellData.TextData(content))updateTableDisplay()}.show(supportFragmentManager, "PuzzleAddColumPopup")}}//表格图片数private var tableImg = 0// 更新表格显示private fun updateTableDisplay() {mBinding.tableContainer.removeAllViews()tableImg = 0val rowCount = excelTable.getRowCount()val colCount = excelTable.getColumnCount()if (colCount == 0 || rowCount == 0) return// 默认行高(转换为像素)val defaultRowHeightPx = excelTable.defaultRowHeight.dp// 计算列宽度val columnWidthPx = if (colCount <= 3) {// 小于等于3列时,使用权重均分宽度null // 用null表示使用权重模式} else {// 大于3列时,使用固定宽度defaultColumnWidthDp.dp}for (row in 0 until rowCount) {val rowLayout = LinearLayout(this)rowLayout.orientation = LinearLayout.HORIZONTALrowLayout.layoutParams = LinearLayout.LayoutParams(DensityUtil.getPhoneWidth(mContext) - 60.dp,LinearLayout.LayoutParams.WRAP_CONTENT)rowLayout.minimumHeight = defaultRowHeightPx// 设置行背景色(第一行为标题行,使用特殊颜色)if (row == 0) {rowLayout.setBackgroundColor(Color.parseColor("#EAEAEA"))}for (col in 0 until colCount) {val cellView = LayoutInflater.from(this).inflate(R.layout.layout_text_img_cell, rowLayout, false)val textView = cellView.findViewById<TextView>(R.id.tvCellText)val imageView = cellView.findViewById<ImageView>(R.id.ivCellImage)// 设置单元格宽度val cellLayoutParams = if (columnWidthPx == null) {// 使用权重模式(小于等于3列)LinearLayout.LayoutParams(0,LinearLayout.LayoutParams.MATCH_PARENT,1f // 等权重分配)} else {// 使用固定宽度(大于3列)LinearLayout.LayoutParams(columnWidthPx,LinearLayout.LayoutParams.MATCH_PARENT)}cellView.layoutParams = cellLayoutParams// 根据列类型显示不同内容val columnType = excelTable.getColumnType(col)val cellData = excelTable.getCellData(row, col)when (columnType) {ColumnType.TEXT -> {textView.visibility = View.VISIBLEimageView.visibility = View.GONEtextView.typeface = if (row == 0) {// 标题行文本加粗Typeface.DEFAULT_BOLD} else {val params = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,  // 宽度 wrap_contentLinearLayout.LayoutParams.MATCH_PARENT // 高度 wrap_content)params.gravity = Gravity.STARTparams.setMargins(4.dp)textView.layoutParams = paramsTypeface.DEFAULT}textView.text = (cellData as? CellData.TextData)?.value ?: ""}ColumnType.IMAGE -> {// 图片列的标题行仍显示文本if (row == 0) {textView.visibility = View.VISIBLEimageView.visibility = View.GONEtextView.typeface = Typeface.DEFAULT_BOLDtextView.text = (cellData as? CellData.TextData)?.value ?: "图片列"} else {textView.visibility = View.GONEimageView.visibility = View.VISIBLE// 创建LinearLayout.LayoutParamsval params = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,  // 宽度 wrap_contentLinearLayout.LayoutParams.MATCH_PARENT // 高度 wrap_content)params.gravity = Gravity.CENTER_VERTICALparams.setMargins(4.dp)imageView.adjustViewBounds = trueimageView.layoutParams = paramstableImg++val imageResId = (cellData as? CellData.ImageData)?.imageResIdif (imageResId != null) {Glide.with(mContext).load(imageResId).dontTransform().into(imageView)}imageView.requestLayout()}}null -> {}}// 设置单元格点击事件cellView.setOnClickListener {showEditDialog(row, col)}rowLayout.addView(cellView)}mBinding.tableContainer.addView(rowLayout)}}/*** 加载指定行所有数据*/private fun loadRowDataForEditing(colum: Int) {val tableStr = JSON.toJSONString(excelTable, SerializerFeature.WriteClassName)PuzzleTableTitleDialog.newInstance(tableStr, colum) { ta ->if (ta != null) {excelTable = ta}updateTableDisplay()}.show(supportFragmentManager, "PuzzleTableTitleDialog")}/*** 编辑列表内容*/private fun editTable(row: Int, colum: Int) {val tableStr = JSON.toJSONString(excelTable, SerializerFeature.WriteClassName)PuzzleEditTableDialog.newInstance(tableStr, row, colum) {if (it != null) {excelTable = it}updateTableDisplay()}.show(supportFragmentManager, "PuzzleEditTableDialog")}// 显示编辑对话框 row行 col 列private fun showEditDialog(row: Int, col: Int) {if (row == 0) {loadRowDataForEditing(col)} else {editTable(row, col)}}/*************添加/删除操作*****************///添加行excelTable.addRow()
//添加列
excelTable.addColumn(type, CellData.TextData("标题"))

工具类 ColumnType.kt:

// 列类型枚举
enum class ColumnType {TEXT, IMAGE
}// 单元格数据密封类
@JSONType(seeAlso = [CellData.TextData::class, CellData.ImageData::class]) // 关键:指定子类
sealed class CellData {@JSONType(typeName = "TextData")data class TextData(val value: String?) : CellData()@JSONType(typeName = "ImageData")data class ImageData(val imageResId: Uri?) : CellData()
}// Excel表格管理类
class ExcelTable {// 存储表格数据private val rows = mutableListOf<MutableList<CellData?>>()// 存储列类型private val columns = mutableListOf<ColumnType>()// 默认行高(dp)val defaultRowHeight = 40// 公开getter,供FastJSON访问// 为FastJSON添加的getter(返回不可变视图,但保留原始类型)fun getRows(): List<List<CellData?>> = rows.toList() // 转换为List确保序列化fun getColumns(): List<ColumnType> = columns.toList()// 为反序列化添加的setter(必须与getter对应)fun setRows(rows: List<List<CellData?>>) {this.rows.clear()this.rows.addAll(rows.map { it.toMutableList() }) // 转换为MutableList}fun setColumns(columns: List<ColumnType>) {this.columns.clear()this.columns.addAll(columns)}/*** 修改列类型*/fun changeColumnsType(col1: Int, type: ColumnType, columnTitle: String?) {// 验证列索引有效性if (col1 < 0 || col1 >= columns.size) {return}columns[col1] = typerows[0][col1] = CellData.TextData(columnTitle)// 交换每一行中对应列的单元格数据rows.forEachIndexed { index, _ ->if (index > 0) {rows[index][col1] = CellData.TextData("")}}}// 交换两列数据(列索引从0开始)fun swapColumns(col1: Int, col2: Int): Boolean {// 验证列索引有效性if (col1 < 0 || col2 < 0 || col1 >= columns.size || col2 >= columns.size || col1 == col2) {return false}// 交换列类型val tempType = columns[col1]columns[col1] = columns[col2]columns[col2] = tempType// 交换每一行中对应列的单元格数据rows.forEach { rowData ->val tempData = rowData[col1]rowData[col1] = rowData[col2]rowData[col2] = tempData}return true}// 添加列并指定类型和标题fun addColumn(type: ColumnType, headerData: CellData.TextData) {columns.add(type)// 为每一行添加对应单元格if (rows.isEmpty()) {// 如果还没有行,添加一行作为标题行val newRow = mutableListOf<CellData?>()newRow.add(headerData)rows.add(newRow)} else {// 为现有行添加单元格for (i in rows.indices) {if (i == 0) {// 标题行添加标题数据rows[i].add(headerData)} else {// 数据行添加默认值val defaultData = when (type) {ColumnType.TEXT -> CellData.TextData("")ColumnType.IMAGE -> CellData.ImageData(null)}rows[i].add(defaultData)}}}}// 添加新行并返回行索引fun addRow(): Int {val newRow = mutableListOf<CellData?>()// 为新行的每个列添加默认数据columns.forEachIndexed { colIndex, type ->val defaultData = when (type) {ColumnType.TEXT -> CellData.TextData("")ColumnType.IMAGE -> CellData.ImageData(null)}newRow.add(defaultData)}rows.add(newRow)return rows.size - 1}// 删除最后一行fun removeRow(index: Int) {if (rows.size > 1) { // 保留至少标题行rows.removeAt(index)}}// 删除最后一列fun removeColumn(index: Int) {if (columns.isNotEmpty()) {columns.removeAt(index)// 移除每行的最后一个单元格rows.forEach { it.removeAt(index) }}}// 设置单元格数据fun setCellData(row: Int, column: Int, data: CellData) {if (row in rows.indices && column in columns.indices) {// 验证数据类型是否与列类型匹配(标题行除外)if (row != 0) {val columnType = columns[column]if ((columnType == ColumnType.TEXT && data !is CellData.TextData) ||(columnType == ColumnType.IMAGE && data !is CellData.ImageData)) {throw IllegalArgumentException("数据类型与列类型不匹配")}}rows[row][column] = data}}// 获取单元格数据fun getCellData(row: Int, column: Int): CellData? {return if (row in rows.indices && column in columns.indices) {rows[row][column]} else null}// 获取列类型fun getColumnType(column: Int): ColumnType? {return if (column in columns.indices) columns[column] else null}// 获取行数fun getRowCount() = rows.size// 获取列数fun getColumnCount() = columns.size
}

布局:

  <ScrollViewandroid:id="@+id/sclTable"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginStart="16dp"android:layout_marginEnd="16dp"android:visibility="gone"app:layout_constraintTop_toBottomOf="@+id/rl_top"><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:orientation="horizontal"><HorizontalScrollViewandroid:id="@+id/tabLayoutScroller"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_toLeftOf="@+id/btnAddImageColumn"><LinearLayoutandroid:id="@+id/tableContainer"android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical" /></HorizontalScrollView><TextViewandroid:id="@+id/btnAddImageColumn"android:layout_width="28dp"android:layout_height="match_parent"android:layout_alignBottom="@+id/tabLayoutScroller"android:layout_alignParentTop="true"android:layout_alignParentEnd="true"android:background="@drawable/bg_008bff_stroke_1"android:gravity="center"android:text="添\n加\n一\n列"android:textColor="@color/font_008bff"android:textSize="14sp" /><!-- 右侧添加列View:与表格高度相同 --><TextViewandroid:id="@+id/btnAddRow"android:layout_width="wrap_content"android:layout_height="28dp"android:layout_below="@+id/tabLayoutScroller"android:layout_alignEnd="@+id/tabLayoutScroller"android:layout_alignParentStart="true"android:background="@drawable/bg_008bff_stroke_1"android:gravity="center"android:text="添加一行"android:textColor="@color/font_008bff"android:textSize="14sp" /></RelativeLayout></ScrollView>

http://www.dtcms.com/a/338439.html

相关文章:

  • 《彩色终端》诗解——ANSI 艺术解(DeepSeek)
  • shell脚本第一阶段
  • Image-to-Music API 接入文档(图片生成音乐)
  • 【新手易混】find 命令中 -perm 选项的知识点
  • ANSI终端色彩控制知识散播(I):语法封装(Python)——《彩色终端》诗评
  • JavaScript 性能优化实战技术指南
  • Coze AI大模型 Docker 部署流程详解
  • 设计模式(四)——责任链模式
  • Spring 三级缓存:破解循环依赖的底层密码
  • 【Python语法基础学习笔记】常量变量运算符函数
  • LeetCode 每日一题 2025/8/11-2025/8/17
  • 【嵌入式基础梳理#12】风压计Modbus框架示例
  • RAG:让AI成为你的知识专家
  • Maven Assembly Plugin 插件使用说明
  • Linux下使用ssh-agent实现集群节点间无免密安装部署
  • 深度学习——R-CNN及其变体
  • 【轨物交流】轨物科技与华为鲲鹏生态深度合作 光伏清洁机器人解决方案获技术认证!
  • Session共享与Sticky模式:优化Web应用性能
  • [激光原理与应用-296]:理论 - 非线性光学 - 线性光学与非线性光学对比
  • SpringBoot校园商铺运营平台
  • 跨平台RTSP播放器深度对比:开源方案与商业SDK的取舍之道
  • MiniMax Agent 上线 Market Place ,AI一键复制克隆网站
  • 视觉语言导航(5)——VLN的具体工作原理——Seq2Seq CMA模型 KL散度 TRANSFORMER 3.1前半段
  • PMP-项目管理-十大知识领域:资源管理-管理团队、设备、材料等资源
  • Win10下配置WSL2后nvidia-smi不正常显示问题
  • 第一阶段C#基础-15:面向对象梳理
  • python-----机器学习中常用的数据预处理
  • 【前端面试题】JavaScript 核心知识点解析(第二十二题到第六十一题)
  • 【数据分析】R语言在生态学数据分析中的应用:从数据处理到可视化
  • 美图披露半年报:AI应用取得突破,净利润同比大增71.3%