**表格视图组件,支持:
- 无标题模式:只有数据行也可以正常滑动
- 两种滑动模式:固定第一列 或 全部滑动
- 全面的样式自定义能力
- 智能列宽计算**
1. 无标题模式支持
设置无标题:调用
Kotlin
setHeaderData(null)
或
Kotlin
setHeaderData(emptyList())
自动调整:
隐藏标题行相关视图
智能计算列宽时忽略标题行
保持数据行正常显示和滑动
2. 两种滑动模式
固定第一列模式:
Kotlin
tableView.setScrollMode(FlexibleTableView.ScrollMode.FIXED_FIRST_COLUMN)
第一列垂直固定
标题行和内容区域可水平滚动
适合需要固定标识列的场景
全滑动模式:
Kotlin
tableView.setScrollMode(FlexibleTableView.ScrollMode.FULL_SCROLL)
-
整个表格可水平滚动
-
标题行和内容区域同步滚动
-
适合所有列同等重要的场景
3. 智能列宽计算
等宽模式:
Kotlin
tableView.setEqualColumnWidth(true)
-
所有列使用相同宽度
-
宽度取所有列内容最大宽度
自适应模式:
Kotlin
tableView.setEqualColumnWidth(false)
每列根据内容计算宽度
可设置最小宽度保证可读性最小宽度设置:
Kotlin
// 设置第一列最小宽度
tableView.setFirstColumnMinWidth(120) // 120dp
// 设置其他列最小宽度
tableView.setOtherColumnMinWidth(90) // 90dp
4. 全面的样式自定义
Kotlin
//标题行样式:
tableView.setHeaderTextColor(Color.WHITE)
tableView.setHeaderBackgroundColor(Color.BLUE)
//第一列样式:
tableView.setFirstColumnTextColor(Color.DKGRAY)
tableView.setFirstColumnBackgroundColor(Color.LTGRAY)
//内容区域样式:
tableView.setContentTextColor(Color.BLACK)
tableView.setContentBackgroundColor(Color.WHITE)
//网格线样式:
tableView.setGridLineColor(Color.GRAY)
使用方法示例
Kotlin
class MainActivity : AppCompatActivity() {
private lateinit var tableView: FlexibleTableView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
tableView = findViewById(R.id.tableView)
// 1. 设置表格模式
tableView.setScrollMode(FlexibleTableView.ScrollMode.FIXED_FIRST_COLUMN)
// 2. 设置表格列宽配置
tableView.setEqualColumnWidth(true) // 所有列等宽
tableView.setFirstColumnMinWidth(120) // 第一列最小宽度120dp
tableView.setOtherColumnMinWidth(90) // 其他列最小宽度90dp
// 3. 设置样式
tableView.setHeaderTextColor(Color.WHITE)
tableView.setHeaderBackgroundColor(Color.parseColor("#3F51B5"))
tableView.setFirstColumnTextColor(Color.DKGRAY)
tableView.setFirstColumnBackgroundColor(Color.parseColor("#E8EAF6"))
tableView.setContentTextColor(Color.BLACK)
tableView.setContentBackgroundColor(Color.parseColor("#F5F5F5"))
tableView.setGridLineColor(Color.parseColor("#9E9E9E"))
// 4. 场景1: 有标题行的情况
setupWithHeaders()
// 5. 场景2: 无标题行的情况
setupWithoutHeaders()
// 6. 添加切换按钮
setupModeSwitchButton()
}
private fun setupWithHeaders() {
// 有标题行的数据
val headers = listOf("产品", "一月", "二月", "三月", "四月", "五月", "六月")
tableView.setHeaderData(headers)
val products = listOf(
listOf("智能手机", "1250", "1380", "1520", "1670", "1820", "1980"),
listOf("笔记本电脑", "780", "820", "890", "920", "950", "980"),
listOf("平板电脑", "620", "680", "710", "750", "790", "820")
)
tableView.setRowData(products)
}
private fun setupWithoutHeaders() {
// 无标题行的数据
tableView.setHeaderData(null) // 不设置标题行
val data = listOf(
listOf("张三", "90", "85", "95", "88", "92"),
listOf("李四", "88", "92", "90", "85", "90"),
listOf("王五", "78", "80", "85", "90", "86"),
listOf("赵六", "92", "90", "88", "92", "94"),
listOf("钱七", "76", "85", "80", "78", "82")
)
tableView.setRowData(data)
}
private fun setupModeSwitchButton() {
val switchButton: Button = findViewById(R.id.switchModeButton)
switchButton.setOnClickListener {
val newMode = if (tableView.getScrollMode() == FlexibleTableView.ScrollMode.FIXED_FIRST_COLUMN) {
FlexibleTableView.ScrollMode.FULL_SCROLL
} else {
FlexibleTableView.ScrollMode.FIXED_FIRST_COLUMN
}
tableView.setScrollMode(newMode)
switchButton.text = if (newMode == FlexibleTableView.ScrollMode.FIXED_FIRST_COLUMN) {
"切换到全滑动模式"
} else {
"切换到固定第一列模式"
}
}
}
}
完整实现代码
FlexibleTableView
Kotlin
class FlexibleTableView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) {
enum class ScrollMode {
FIXED_FIRST_COLUMN, // 固定第一列模式
FULL_SCROLL // 全部滑动模式
}
private val mContext: Context = context
private lateinit var rvHeader: RecyclerView
private lateinit var rvFirstColumn: RecyclerView
private lateinit var rvItems: RecyclerView
private lateinit var tvFirstHeader: TableCell
private lateinit var headerAdapter: TableAdapter
private lateinit var firstColumnAdapter: TableAdapter
private lateinit var itemAdapter: TableAdapter
private var headerList: List<String> = ArrayList()
private val firstColumnList: MutableList<String> = ArrayList()
private val itemList: MutableList<String> = ArrayList()
// 样式配置
@ColorInt private var headerTextColor = Color.WHITE
@ColorInt private var headerBackgroundColor = Color.parseColor("#3F51B5")
@ColorInt private var firstColumnTextColor = Color.DKGRAY
@ColorInt private var firstColumnBackgroundColor = Color.parseColor("#E8EAF6")
@ColorInt private var contentTextColor = Color.BLACK
@ColorInt private var contentBackgroundColor = Color.parseColor("#F5F5F5")
@ColorInt private var gridLineColor = Color.parseColor("#9E9E9E")
// 宽度配置
private var firstColumnMinWidth = dpToPx(120) // 第一列最小宽度
private var otherColumnMinWidth = dpToPx(100) // 其他列最小宽度
private var equalColumnWidth = true // 是否等宽显示
// 滚动模式
private var scrollMode = ScrollMode.FIXED_FIRST_COLUMN
// 滚动位置缓存
private var scrollX = 0
private var scrollY = 0
// 标题行可见性
private var headerVisible = true
init {
initView()
}
private fun initView() {
orientation = HORIZONTAL
removeAllViews()
when (scrollMode) {
ScrollMode.FIXED_FIRST_COLUMN -> initFixedFirstColumnMode()
ScrollMode.FULL_SCROLL -> initFullScrollMode()
}
}
private fun initFixedFirstColumnMode() {
// 固定第一列模式
addView(createFixedColumnHeader())
addView(createScrollableContentArea())
setupAdapters()
setupScrollSync()
}
private fun initFullScrollMode() {
// 全滑动模式
addView(createFullScrollContainer())
setupAdapters()
setupScrollSync()
}
private fun setupAdapters() {
headerAdapter = TableAdapter(mContext)
headerAdapter.isHeader(true)
firstColumnAdapter = TableAdapter(mContext)
itemAdapter = TableAdapter(mContext)
if (::rvHeader.isInitialized) rvHeader.adapter = headerAdapter
if (::rvFirstColumn.isInitialized) rvFirstColumn.adapter = firstColumnAdapter
if (::rvItems.isInitialized) rvItems.adapter = itemAdapter
if (::tvFirstHeader.isInitialized) {
tvFirstHeader.setHeader(true)
}
}
private fun setupScrollSync() {
if (!::rvItems.isInitialized || !::rvFirstColumn.isInitialized) return
rvFirstColumn.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (recyclerView.scrollState != RecyclerView.SCROLL_STATE_IDLE) {
rvItems.scrollBy(dx, dy)
scrollY += dy
}
}
})
rvItems.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (recyclerView.scrollState != RecyclerView.SCROLL_STATE_IDLE) {
rvFirstColumn.scrollBy(dx, dy)
scrollY += dy
scrollX += dx
}
}
})
}
private fun createFixedColumnHeader(): LinearLayout {
tvFirstHeader = TableCell(mContext, firstColumnMinWidth)
tvFirstHeader.setGridLineColor(gridLineColor)
tvFirstHeader.setTextColor(headerTextColor)
tvFirstHeader.setHeaderBackgroundColor(headerBackgroundColor)
tvFirstHeader.visibility = if (headerVisible) View.VISIBLE else View.GONE
val lyHeader = LinearLayout(mContext)
lyHeader.orientation = LinearLayout.VERTICAL
lyHeader.layoutParams = LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.MATCH_PARENT
)
lyHeader.addView(tvFirstHeader)
rvFirstColumn = RecyclerView(mContext)
rvFirstColumn.layoutManager = LinearLayoutManager(mContext)
lyHeader.addView(rvFirstColumn)
return lyHeader
}
private fun createScrollableContentArea(): HorizontalScrollView {
val layout = LinearLayout(mContext)
layout.orientation = LinearLayout.VERTICAL
layout.layoutParams = LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT
)
// 标题行容器
rvHeader = RecyclerView(mContext)
rvHeader.layoutParams = LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.WRAP_CONTENT
)
rvHeader.visibility = if (headerVisible) View.VISIBLE else View.GONE
val headerManager = LinearLayoutManager(mContext)
headerManager.orientation = LinearLayoutManager.HORIZONTAL
rvHeader.layoutManager = headerManager
layout.addView(rvHeader)
// 内容行容器
rvItems = RecyclerView(mContext)
rvItems.layoutParams = LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT
)
layout.addView(rvItems)
val scrollView = HorizontalScrollView(mContext)
scrollView.layoutParams = LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT
)
scrollView.addView(layout)
scrollView.isFillViewport = true
scrollView.overScrollMode = View.OVER_SCROLL_NEVER
scrollView.isHorizontalScrollBarEnabled = false
return scrollView
}
private fun createFullScrollContainer(): HorizontalScrollView {
val container = LinearLayout(mContext)
container.orientation = LinearLayout.VERTICAL
container.layoutParams = LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT
)
// 标题行(可滑动)
rvHeader = RecyclerView(mContext)
rvHeader.layoutParams = LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.WRAP_CONTENT
)
rvHeader.visibility = if (headerVisible) View.VISIBLE else View.GONE
val headerManager = LinearLayoutManager(mContext)
headerManager.orientation = LinearLayoutManager.HORIZONTAL
rvHeader.layoutManager = headerManager
container.addView(rvHeader)
// 内容区域(包括第一列和其余列)
val contentContainer = LinearLayout(mContext)
contentContainer.orientation = LinearLayout.HORIZONTAL
contentContainer.layoutParams = LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT
)
// 第一列(在完整滑动模式下也包含在可滚动区域)
val firstColumnContainer = LinearLayout(mContext)
firstColumnContainer.orientation = LinearLayout.VERTICAL
firstColumnContainer.layoutParams = LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.MATCH_PARENT
)
tvFirstHeader = TableCell(mContext, firstColumnMinWidth)
tvFirstHeader.setGridLineColor(gridLineColor)
tvFirstHeader.setTextColor(headerTextColor)
tvFirstHeader.setHeaderBackgroundColor(headerBackgroundColor)
tvFirstHeader.visibility = if (headerVisible) View.VISIBLE else View.GONE
firstColumnContainer.addView(tvFirstHeader)
rvFirstColumn = RecyclerView(mContext)
rvFirstColumn.layoutManager = LinearLayoutManager(mContext)
firstColumnContainer.addView(rvFirstColumn)
contentContainer.addView(firstColumnContainer)
// 其余列
rvItems = RecyclerView(mContext)
rvItems.layoutParams = LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT
)
contentContainer.addView(rvItems)
container.addView(contentContainer)
val scrollView = HorizontalScrollView(mContext)
scrollView.layoutParams = LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT
)
scrollView.addView(container)
scrollView.isFillViewport = true
scrollView.overScrollMode = View.OVER_SCROLL_NEVER
scrollView.isHorizontalScrollBarEnabled = false
scrollView.scrollTo(scrollX, 0)
return scrollView
}
fun setHeaderData(headerData: List<String>?) {
if (headerData == null || headerData.isEmpty()) {
// 没有标题行
headerVisible = false
headerList = emptyList()
if (::tvFirstHeader.isInitialized) {
tvFirstHeader.visibility = View.GONE
}
if (::rvHeader.isInitialized) {
rvHeader.visibility = View.GONE
}
headerAdapter.setItemList(emptyList())
return
}
// 有标题行
headerVisible = true
headerList = ArrayList(headerData)
val headers = ArrayList(headerData)
if (::tvFirstHeader.isInitialized) {
tvFirstHeader.text = headers[0]
tvFirstHeader.visibility = View.VISIBLE
}
headers.removeAt(0)
headerAdapter.setItemList(headers)
if (::rvHeader.isInitialized) {
rvHeader.visibility = View.VISIBLE
}
if (::rvItems.isInitialized) {
rvItems.layoutManager = GridLayoutManager(mContext, headerList.size - 1)
}
}
fun setRowData(rowDataList: List<List<String>>) {
if (rowDataList.isEmpty()) {
// 清空数据
firstColumnList.clear()
itemList.clear()
firstColumnAdapter.setItemList(emptyList())
itemAdapter.setItemList(emptyList())
return
}
// 确定列数:取第一行数据除去第一列后的列数
val columnCount = rowDataList[0].size - 1
// 设置GridLayoutManager的列数
if (::rvItems.isInitialized) {
rvItems.layoutManager = GridLayoutManager(mContext, columnCount)
}
// 处理数据
firstColumnList.clear()
itemList.clear()
addRowData(rowDataList)
}
fun addRowData(rowDataList: List<List<String>>) {
val list = ArrayList(rowDataList)
for (rowData in list) {
val row = ArrayList(rowData)
if (row.isNotEmpty()) {
firstColumnList.add(row[0])
row.removeAt(0)
itemList.addAll(row)
}
}
firstColumnAdapter.setItemList(firstColumnList)
itemAdapter.setItemList(itemList)
if (::rvFirstColumn.isInitialized && ::rvItems.isInitialized) {
rvFirstColumn.scrollTo(0, scrollY)
rvItems.scrollTo(scrollX, scrollY)
}
calculateColumnWidths()
}
private fun calculateColumnWidths() {
// 计算第一列宽度
val firstColData = firstColumnList.toMutableList()
if (headerVisible) {
firstColData.add(tvFirstHeader.text.toString())
}
val firstColWidth = calculateColumnWidth(firstColData, firstColumnMinWidth)
// 计算其他列宽度
val otherColWidth = if (equalColumnWidth) {
val maxOtherWidth = if (headerVisible) {
maxOf(
calculateColumnWidth(headerList, otherColumnMinWidth),
calculateColumnWidth(itemList, otherColumnMinWidth)
)
} else {
calculateColumnWidth(itemList, otherColumnMinWidth)
}
maxOf(maxOtherWidth, otherColumnMinWidth)
} else {
if (headerVisible) {
maxOf(
calculateColumnWidth(headerList, otherColumnMinWidth),
calculateColumnWidth(itemList, otherColumnMinWidth)
)
} else {
calculateColumnWidth(itemList, otherColumnMinWidth)
}
}
// 设置宽度
if (::tvFirstHeader.isInitialized) {
tvFirstHeader.width = firstColWidth
}
firstColumnAdapter.setItemWidth(firstColWidth)
headerAdapter.setItemWidth(otherColWidth)
itemAdapter.setItemWidth(otherColWidth)
}
private fun calculateColumnWidth(data: List<String>, minWidth: Int): Int {
if (data.isEmpty()) return minWidth
var maxWidth = minWidth
val paint = Paint()
paint.textSize = spToPx(14)
for (text in data) {
val textWidth = paint.measureText(text).toInt()
val cellWidth = textWidth + dpToPx(20) // 加上内边距
if (cellWidth > maxWidth) {
maxWidth = cellWidth
}
}
return maxWidth
}
fun getItemCount(): Int = firstColumnList.size * (if (headerVisible) headerList.size - 1 else 0)
// 样式设置方法
fun setHeaderTextColor(@ColorInt color: Int) {
headerTextColor = color
if (::tvFirstHeader.isInitialized) {
tvFirstHeader.setTextColor(color)
}
headerAdapter.setTextColor(color)
}
fun setHeaderBackgroundColor(@ColorInt color: Int) {
headerBackgroundColor = color
if (::tvFirstHeader.isInitialized) {
tvFirstHeader.setHeaderBackgroundColor(color)
}
headerAdapter.setBackgroundColor(color)
}
fun setFirstColumnTextColor(@ColorInt color: Int) {
firstColumnTextColor = color
firstColumnAdapter.setTextColor(color)
}
fun setFirstColumnBackgroundColor(@ColorInt color: Int) {
firstColumnBackgroundColor = color
firstColumnAdapter.setBackgroundColor(color)
}
fun setContentTextColor(@ColorInt color: Int) {
contentTextColor = color
itemAdapter.setTextColor(color)
}
fun setContentBackgroundColor(@ColorInt color: Int) {
contentBackgroundColor = color
itemAdapter.setBackgroundColor(color)
}
fun setGridLineColor(@ColorInt color: Int) {
gridLineColor = color
if (::tvFirstHeader.isInitialized) {
tvFirstHeader.setGridLineColor(color)
}
headerAdapter.setGridLineColor(color)
firstColumnAdapter.setGridLineColor(color)
itemAdapter.setGridLineColor(color)
}
// 宽度设置方法
fun setEqualColumnWidth(enabled: Boolean) {
equalColumnWidth = enabled
calculateColumnWidths()
}
fun setFirstColumnMinWidth(minWidthDp: Int) {
firstColumnMinWidth = dpToPx(minWidthDp)
calculateColumnWidths()
}
fun setOtherColumnMinWidth(minWidthDp: Int) {
otherColumnMinWidth = dpToPx(minWidthDp)
calculateColumnWidths()
}
// 滚动模式设置
fun setScrollMode(mode: ScrollMode) {
if (scrollMode != mode) {
// 保存当前滚动位置
scrollX = 0
scrollY = 0
scrollMode = mode
initView()
// 重新应用数据
if (headerList.isNotEmpty()) {
setHeaderData(headerList)
}
if (firstColumnList.isNotEmpty()) {
setRowData(firstColumnList.map { listOf(it) })
}
}
}
fun getScrollMode(): ScrollMode = scrollMode
// 单位转换工具
private fun dpToPx(dp: Int): Int {
return (dp * context.resources.displayMetrics.density).toInt()
}
private fun spToPx(sp: Int): Float {
return sp * context.resources.displayMetrics.scaledDensity
}
}
TableAdapter
Kotlin
@SuppressLint("NotifyDataSetChanged")
class TableAdapter(private val mContext: Context) : RecyclerView.Adapter<TableAdapter.MyViewHolder>() {
private var mItemList: List<String> = ArrayList()
private var itemWidth = 0
private var isHeader = false
private var textColor = Color.BLACK
private var backgroundColor = Color.WHITE
private var gridLineColor = Color.GRAY
fun setItemWidth(width: Int) {
itemWidth = width
notifyDataSetChanged()
}
fun setTextColor(color: Int) {
textColor = color
notifyDataSetChanged()
}
fun setBackgroundColor(color: Int) {
backgroundColor = color
notifyDataSetChanged()
}
fun setGridLineColor(color: Int) {
gridLineColor = color
notifyDataSetChanged()
}
fun setItemList(itemList: List<String>) {
mItemList = itemList
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val cell = TableCell(mContext, itemWidth)
cell.setHeader(isHeader)
cell.setGridLineColor(gridLineColor)
return MyViewHolder(cell)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val item = mItemList[position]
val tv = holder.itemView as TableCell
tv.text = item
tv.setTextColor(textColor)
tv.setCellBackgroundColor(backgroundColor)
// 如果是表头行,应用特殊样式
if (isHeader) {
tv.setTextColor(textColor)
tv.setHeaderBackgroundColor(backgroundColor)
}
}
override fun getItemCount(): Int = mItemList.size
fun isHeader(isHeader: Boolean) {
this.isHeader = isHeader
}
inner class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
}
TableCell
Kotlin
class TableCell @JvmOverloads constructor(
context: Context,
private var width: Int = ViewGroup.LayoutParams.WRAP_CONTENT
) : AppCompatTextView(context) {
private var isHeader = false
private var gridLineColor = Color.GRAY
private var headerBackgroundColor = Color.parseColor("#3F51B5")
private var cellBackgroundColor = Color.WHITE
init {
initView()
}
private fun initView() {
setBackgroundColor(cellBackgroundColor)
val params = LinearLayout.LayoutParams(width - 2, ViewGroup.LayoutParams.WRAP_CONTENT)
params.setMargins(0, 0, 2, 2)
layoutParams = params
textSize = 14f
gravity = Gravity.CENTER
setPadding(dpToPx(10), dpToPx(10), dpToPx(10), dpToPx(10))
}
fun setHeader(isHeader: Boolean) {
this.isHeader = isHeader
setBackgroundColor(if (isHeader) headerBackgroundColor else cellBackgroundColor)
setPadding(
dpToPx(10),
dpToPx(if (isHeader) 15 else 10),
dpToPx(10),
dpToPx(if (isHeader) 15 else 10)
)
}
fun setHeaderBackgroundColor(color: Int) {
headerBackgroundColor = color
if (isHeader) {
setBackgroundColor(color)
}
}
fun setCellBackgroundColor(color: Int) {
cellBackgroundColor = color
if (!isHeader) {
setBackgroundColor(color)
}
}
fun setGridLineColor(color: Int) {
gridLineColor = color
invalidate()
}
override fun setWidth(pixels: Int) {
width = pixels
refreshWidth()
}
private fun refreshWidth() {
val params = layoutParams as LinearLayout.LayoutParams
params.width = width - 2
params.setMargins(0, 0, 2, 2)
layoutParams = params
}
override fun onDraw(canvas: Canvas) {
// 绘制网格线
val paint = Paint()
paint.color = gridLineColor
paint.strokeWidth = 1f
// 绘制右边框
canvas.drawLine(width.toFloat() - 2, 0f, width.toFloat() - 2, height.toFloat(), paint)
// 绘制下边框
canvas.drawLine(0f, height.toFloat() - 2, width.toFloat(), height.toFloat() - 2, paint)
// 表头特殊样式
if (isHeader) {
paint.style = Paint.Style.FILL_AND_STROKE
paint.strokeWidth = 1.5f
}
super.onDraw(canvas)
}
private fun dpToPx(dp: Int): Int {
return (dp * context.resources.displayMetrics.density).toInt()
}
}
在布局文件中添加表格视图
Kotlin
<com.yourpackage.FlexibleTableView
android:id="@+id/scrollTableView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />