当前位置: 首页 > 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.lryc.cn/news/624995.html

相关文章:

  • 力扣(LeetCode) ——100. 相同的树(C语言)
  • Rust 异步中的 Waker
  • PMP-项目管理-十大知识领域:资源管理-管理团队、设备、材料等资源
  • OpenCV Python——Numpy基本操作(Numpy 矩阵操作、Numpy 矩阵的检索与赋值、Numpy 操作ROI)
  • 3D检测笔记:基础坐标系与标注框介绍
  • JAiRouter 架构揭秘:一个面向 AI 时代的响应式网关设计
  • JUC读写锁
  • 宁波市第八届网络安全大赛初赛(REVERSE-Writeup)
  • 基于Spring Boot+Vue的社区便民服务平台 智慧社区平台 志愿者服务管理
  • day25|学习前端js
  • Product Hunt 每日热榜 | 2025-08-18
  • 【yocto】为什么要选择yocto?
  • 亚马逊新手突围:从流量破冰到持续出单
  • Less (CSS 预处理器)
  • 问答社区运营优化:cpolar 提升 Answer 平台远程访问速度方案
  • 性能测试(Jemter)
  • day44_2025-08-18
  • PMP-项目管理-十大知识领域:风险管理-识别、评估、应对项目风险
  • 兴趣爱好——虾哥开源小智AI机器人搭建(丐版—最低成本)ESP32开发板 MicroPython V1.0.0 Rev1
  • 继承中的向上转型、向下转型与动态绑定的深入解析
  • 学习游戏制作记录(各种独特物品效果)8.18
  • 【Langchain系列二】LangChain+Prompt +LLM智能问答入门
  • Prompt engineering(PE) —— prompt 优化如何进行?
  • 集成电路学习:什么是Face Detection人脸检测
  • leetcode4_452 and 763
  • 【论文学习】UoMo: 一个用于无线网络优化的移动流量预测通用模型
  • 学习嵌入式的第二十天——数据结构
  • 如何解决机器翻译的“幻觉“问题(Hallucination)?
  • 特赞内容运营解决方案,AI重构品牌内容价值链
  • (Arxiv-2025)OPENS2V-NEXUS:一个面向主体到视频生成的详细基准与百万规模数据集