关键词:自定义View、表格、合并单元格、固定吸附、粘性吸附
背景:
表格展示作为常见需求,从 GridView,到 RecyclerView + GridLayoutManager,都是作为基本控件出现的。但是,产品要的从来不止系统能够支持的,比如合并单元格、行列吸附等效果。而网络上存在的仿excel表格控件,功能复杂,不便使用。因此我们来愉快的造轮子吧!
效果示意:
124_1705071364
第二行、第二列、第五行、第五列都是固定吸附
第三行、第三列都是粘性吸附
功能简介:
- 同时支持横纵方向合并单元格展示
- 支持任意数量的任意行、列的固定吸附效果
- 数据量少时支持均分,数据量多时支持滑动
- 行宽、列高支持自适应
设计思路:
首先,表格只负责单元格位置,不应该负责单元格内具体展示内容,否则业务迭代时需要深入表格控件中修改,非常缺乏通用型。因此引入 adapter,表格控件只需要根据adapter返回的视图View,去计算位置。
其次,如果要嵌套到滑动布局中,那么表格就不知道自己什么时候应该吸附了,就需要自己处理下滑动效果,记录每次滑动距离,onLayout 的时候把距离左边和上边的距离减掉横纵滑动的距离就OK
然后,对于行列的两种吸附效果,实际上就是在 onLayout 的时候再加一点判断,判断下实际展示位置是否满足吸附条件,满足就要按照吸附时坐标展示了。
最后,为了保证吸附行列在吸附的时候不能被其他单元格覆盖,就需要简单控制下 childView 的顺序。哦,吸附行列的交叉点的展示优先级和非交叉点也不一样,这点需要注意。
哦,差点忘了,为了自适应,onMeasure时需要计算两遍。
那么,这个表格控件就可以开撸了,不复杂,对吧~
当然还有点小坑没能解决
- 每个单元格需要设计点击监听,不然 ZrTableView 拿不到 onTouch 事件
- 每个单元格需要单独设置背景,不然会透视
- 吸附信息和合并单元格信息设置完后需要手动 adapter.notifyDataSetChanged(),不然展示时上下层级错乱。
直接上代码:
package com.elee.uidemo
import android.content.Context
import android.graphics.Canvas
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
import android.view.ViewGroup
import androidx.core.view.children
import kotlin.math.absoluteValue
/**
* 表格view
*/
class ZrTableView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ViewGroup(context, attrs, defStyleAttr) {
companion object {
const val SCROLL_DIRECTION_VERTICAL = 0
const val SCROLL_DIRECTION_HORIZONTAL = 1
}
init {
setWillNotDraw(false)
isNestedScrollingEnabled = true
// isFocusable = true
}
private val mTouchSlop: Int by lazy { ViewConfiguration.get(context).scaledTouchSlop }
private val mManager = CellsManager()
private val mHelper = LayoutHelper()
private var horizontalScrollEnable = false
private var verticalScrollEnable = false
private var horizontalScrollSpace: Int = 0
private var verticalScrollSpace: Int = 0
private var mIsBeingDragged: Boolean = false
private val mScrollConsumed = IntArray(2)
private val mWindowOffSet = IntArray(2)
var adapter: Adapter? = null
set(value) {
field = value
updateAllView()
field?.apply {
updateListener = { column, row ->
if (column == -1 || row == -1) {
// 全部更新
updateAllView()
} else {
// 单个 item 更新,不改变宽度
getCellView(row, column)?.let {
bindData(
it,
row,
column,
getCellType(column, row)
)
}
}
}
}
}
/**
* 添加单个合并单元格信息
*/
fun addSpanInfo(spanInfo: SpanCellItemInfo) {
mHelper.spanCellInfo.add(spanInfo)
}
/**
* 设置合并单元格信息
*/
fun setSpanList(spanList: MutableList<SpanCellItemInfo>) {
mHelper.spanCellInfo.clear()
mHelper.spanCellInfo.addAll(spanList)
}
/**
* 设置固定列
*/
fun setStableColumnList(list: MutableList<Int>) {
mHelper.stableColumnIndex.clear()
mHelper.stableColumnIndex.addAll(list)
mHelper.stableColumnIndex.sort()
}
/**
* 设置固定行
*/
fun setStableRowList(list: MutableList<Int>) {
mHelper.stableRowIndex.clear()
mHelper.stableRowIndex.addAll(list)
mHelper.stableRowIndex.sort()
}
/**
* 设置粘性吸附列
*/
fun setStickyColumnList(list: MutableList<Int>) {
mHelper.stickyColumnIndex.clear()
mHelper.stickyColumnIndex.addAll(list)
mHelper.stickyColumnIndex.sort()
}
/**
* 设置粘性吸附行
*/
fun setStickyRowList(list: MutableList<Int>) {
mHelper.stickyRowIndex.clear()
mHelper.stickyRowIndex.addAll(list)
mHelper.stickyRowIndex.sort()
}
fun scrollStable(column: Int = -1, row: Int = -1) {
if (column in 0 until mManager.columnCount && horizontalScrollEnable) {
horizontalScrollSpace = getLeftSpicalSpace(column) - mManager.getLeftSpace(column)
}
if (row in 0 until mManager.rowCount && verticalScrollEnable) {
verticalScrollSpace = getTopSpicalSpace(row) - mManager.getTopSpace(row)
}
requestLayout()
}
private fun updateAllView() {
adapter?.let {
removeAllViews()
mManager.reset()
mManager.columnCount = it.getColumnCount()
mManager.rowCount = it.getRowCount()
// 第一层添加普通view
for (r in 0 until mManager.rowCount) {
for (c in 0 until mManager.columnCount) {
if (mHelper.showCell(c, r)) {
if (!mHelper.isStickyCell(c, r) && !mHelper.isStableCell(c, r)) {
addView(it.createCellView(this, c, r, it.getCellType(c, r)).apply {
layoutParams = LayoutParams().apply {
column = c
row = r
}
})
}
}
}
}
// 第二层添加粘性吸附view非交叉点
for (r in 0 until mManager.rowCount) {
for (c in 0 until mManager.columnCount) {
if (mHelper.showCell(c, r) && mHelper.isStickyCell(c, r)) {
if ((!mHelper.stickyRowIndex.contains(r) || !mHelper.stickyColumnIndex.contains(
c
) && !mHelper.stableRowIndex.contains(r) && !mHelper.stableColumnIndex.contains(
c
))
) {
addView(it.createCellView(this, c, r, it.getCellType(c, r)).apply {
layoutParams = LayoutParams().apply {
column = c
row = r
}
})
}
}
}
}
// 第三层添加粘性吸附view交叉点
for (r in mHelper.stickyRowIndex) {
for (c in mHelper.stickyColumnIndex) {
if (mHelper.showCell(c, r) && mHelper.isStickyCell(c, r)) {
if (mHelper.stickyRowIndex.contains(r) && mHelper.stickyColumnIndex.contains(
c
)
) {
addView(it.createCellView(this, c, r, it.getCellType(c, r)).apply {
layoutParams = LayoutParams().apply {
column = c
row = r
}
})
}
}
}
}
// 第四层添加固定吸附view非交叉点
for (r in 0 until mManager.rowCount) {
for (c in 0 until mManager.columnCount) {
if (mHelper.showCell(c, r) && mHelper.isStableCell(c, r)) {
if (!mHelper.stableRowIndex.contains(r) || !mHelper.stableColumnIndex.contains(
c
)
) {
addView(it.createCellView(this, c, r, it.getCellType(c, r)).apply {
layoutParams = LayoutParams().apply {
column = c
row = r
}
})
}
}
}
}
// 第五层添加固定吸附与粘性吸附view交叉点
for (r in 0 until mManager.rowCount) {
for (c in 0 until mManager.columnCount) {
if (mHelper.showCell(c, r)) {
if (mHelper.stableRowIndex.contains(r) && mHelper.stickyColumnIndex.contains(
c
)
) {
addView(it.createCellView(this, c, r, it.getCellType(c, r)).apply {
layoutParams = LayoutParams().apply {
column = c
row = r
}
})
}
if (mHelper.stickyRowIndex.contains(r) && mHelper.stableColumnIndex.contains(
c
)
) {
addView(it.createCellView(this, c, r, it.getCellType(c, r)).apply {
layoutParams = LayoutParams().apply {
column = c
row = r
}
})
}
}
}
}
// 第六层添加固定吸附view交叉点
for (r in mHelper.stableRowIndex) {
for (c in mHelper.stableColumnIndex) {
if (mHelper.showCell(c, r) && mHelper.isStableCell(c, r)) {
if (mHelper.stableRowIndex.contains(r) && mHelper.stableColumnIndex.contains(
c
)
) {
addView(it.createCellView(this, c, r, it.getCellType(c, r)).apply {
layoutParams = LayoutParams().apply {
column = c
row = r
}
})
}
}
}
}
children.forEach { v ->
if (v.layoutParams is LayoutParams) {
(v.layoutParams as LayoutParams)?.apply {
it.bindData(v, column, row, it.getCellType(column, row))
}
}
}
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
// measure 计算默认宽高
children.forEach {
it.measure(
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
)
}
if (mManager.columnCount == 0 || mManager.rowCount == 0) {
setMeasuredDimension(0, 0)
return
}
// 确认每行最大高度和每列最大宽度
var maxSize: Int = 0
mManager.rowHeightSizeList.clear()
mManager.columnWidthSizeList.clear()
for (c in 0 until mManager.columnCount) {
maxSize = 0
for (r in 0 until mManager.rowCount) {
if (!mHelper.isSpanCell(c, r)) {
getCellView(r, c)?.let {
maxSize = maxSize.coerceAtLeast(it.measuredWidth)
}
}
}
mManager.columnWidthSizeList.add(maxSize)
}
for (r in 0 until mManager.rowCount) {
maxSize = 0
for (c in 0 until mManager.columnCount) {
if (!mHelper.isSpanCell(c, r)) {
getCellView(r, c)?.let {
maxSize = maxSize.coerceAtLeast(it.measuredHeight)
}
}
}
mManager.rowHeightSizeList.add(maxSize)
}
// 处理跨列宽度
// 自适应规则:如果跨行列单元格宽高大于所跨行列的宽高,那么多出部分均分到所跨行列
var spanColumnWidth: Int = 0
var spanRowHeight: Int = 0
mHelper.spanCellInfo.forEach {
spanColumnWidth = 0
spanRowHeight = 0
for (c in it.cellColumnIndex until it.cellColumnIndex + it.columnSpanSize) {
spanColumnWidth += mManager.columnWidthSizeList[c]
}
for (r in it.cellRowIndex until it.cellRowIndex + it.rowSpanSize) {
spanRowHeight += mManager.rowHeightSizeList[r]
}
val view = getCellView(it.cellRowIndex, it.cellColumnIndex)
view?.let { v ->
if (v.measuredWidth > spanColumnWidth) {
for (c in it.cellColumnIndex until it.cellColumnIndex + it.columnSpanSize) {
mManager.columnWidthSizeList[c] += (v.measuredWidth - spanColumnWidth) / it.columnSpanSize
}
}
if (v.measuredHeight > spanRowHeight) {
for (r in it.cellRowIndex until it.cellRowIndex + it.rowSpanSize) {
mManager.rowHeightSizeList[r] += (v.measuredHeight - spanRowHeight) / it.rowSpanSize
}
}
}
}
// 根据父布局宽度,计算应该均分还是横向铺长
val widthSize = MeasureSpec.getSize(widthMeasureSpec)
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
val heightSize = MeasureSpec.getSize(heightMeasureSpec)
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
var childrenHeight: Int = 0
var childrenWidth: Int = 0
mManager.rowHeightSizeList.forEach { childrenHeight += it }
mManager.columnWidthSizeList.forEach { childrenWidth += it }
var widthDimension = 0
var heightDimension = 0
when (widthMode) {
MeasureSpec.EXACTLY -> {
widthDimension = widthSize
horizontalScrollEnable = widthSize < childrenWidth
if (childrenWidth < widthDimension) {
// 不足一屏均分剩余宽度
val sprideWidth = (widthDimension - childrenWidth) / mManager.columnCount
for (i in mManager.columnWidthSizeList.indices) {
mManager.columnWidthSizeList[i] =
mManager.columnWidthSizeList[i] + sprideWidth
}
}
}
MeasureSpec.UNSPECIFIED -> {
widthDimension = childrenWidth
horizontalScrollEnable = false
}
MeasureSpec.AT_MOST -> {
widthDimension = widthSize.coerceAtMost(childrenWidth)
horizontalScrollEnable = widthSize < childrenWidth
}
}
when (heightMode) {
MeasureSpec.EXACTLY -> {
heightDimension = heightSize
verticalScrollEnable = heightSize < childrenHeight
}
MeasureSpec.UNSPECIFIED -> {
heightDimension = childrenHeight
verticalScrollEnable = false
}
MeasureSpec.AT_MOST -> {
heightDimension = heightSize.coerceAtMost(childrenHeight)
verticalScrollEnable = heightSize < childrenHeight
}
}
setMeasuredDimension(widthDimension, heightDimension)
// 第二次 measure 给 child 具体宽高
children.forEach {
if (it.layoutParams is LayoutParams) {
(it.layoutParams as LayoutParams).let { lp ->
val spanInfo = mHelper.getSpanInfo(row = lp.row, column = lp.column)
spanInfo?.let { span ->
// spanInfo 不为空,需要通过跨过的行、列计算宽度
var width: Int = 0
for (c in span.cellColumnIndex until span.cellColumnIndex + span.columnSpanSize) {
width += mManager.columnWidthSizeList[c]
}
var height: Int = 0
for (r in span.cellRowIndex until span.cellRowIndex + span.rowSpanSize) {
height += mManager.rowHeightSizeList[r]
}
lp.width = width
lp.height = height
it.measure(
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
)
} ?: kotlin.run {
// spanInfo 为空,证明非跨行/列的单元格
lp.width = mManager.columnWidthSizeList[lp.column]
lp.height = mManager.rowHeightSizeList[lp.row]
it.measure(
MeasureSpec.makeMeasureSpec(
mManager.columnWidthSizeList[lp.column],
MeasureSpec.EXACTLY
),
MeasureSpec.makeMeasureSpec(
mManager.rowHeightSizeList[lp.row],
MeasureSpec.EXACTLY
)
)
}
}
}
}
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
// layout common first
children.forEach { it ->
val lp: LayoutParams? =
if (it.layoutParams is LayoutParams) it.layoutParams as LayoutParams else null
lp?.let { lp ->
var childLeft = (mManager.getLeftSpace(lp.column) + horizontalScrollSpace)
if (mHelper.stableColumnIndex.contains(lp.column) || mHelper.stickyColumnIndex.contains(
lp.column
)
) {
childLeft = childLeft.coerceAtLeast(getLeftStableSpace(lp.column))
}
if (mHelper.stickyColumnIndex.contains(lp.column)) {
childLeft = childLeft.coerceAtMost(
mManager.getLeftSpace(mHelper.getRightSpecialIndex(lp.column)) + horizontalScrollSpace - mManager.columnWidthSizeList[lp.column]
)
}
var childTop = (mManager.getTopSpace(lp.row) + verticalScrollSpace)
if (mHelper.stableRowIndex.contains(lp.row) || mHelper.stickyRowIndex.contains(lp.row)) {
childTop = childTop.coerceAtLeast(getTopStableSpace(lp.row))
}
if (mHelper.stickyRowIndex.contains(lp.row)) {
childTop = childTop.coerceAtMost(
mManager.getTopSpace(mHelper.getBottomSpecialIndex(lp.row)) + verticalScrollSpace - mManager.rowHeightSizeList[lp.row]
)
}
it.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height)
}
}
}
private fun getLeftStableSpace(column: Int): Int {
var space = 0
mHelper.stableColumnIndex.forEach {
if (it < column) {
if (mManager.columnWidthSizeList.size > it){
space += mManager.columnWidthSizeList[it]
}
}
}
return space
}
private fun getLeftSpicalSpace(column: Int): Int {
var space = 0
var leftStableIndex = 0
mHelper.stableColumnIndex.forEach {
if (it < column) {
if (mManager.columnWidthSizeList.size > it) {
space += mManager.columnWidthSizeList[it]
}
leftStableIndex = leftStableIndex.coerceAtLeast(it)
}
}
mHelper.stickyColumnIndex.forEach {
if (it in leftStableIndex until column) {
if (mManager.columnWidthSizeList.size > it) {
space += mManager.columnWidthSizeList[it]
}
}
}
return space
}
private fun getTopStableSpace(row: Int): Int {
var space = 0
mHelper.stableRowIndex.forEach {
if (it < row) {
if (mManager.rowHeightSizeList.size > it) {
space += mManager.rowHeightSizeList[it]
}
}
}
return space
}
private fun getTopSpicalSpace(row: Int): Int {
var space = 0
var topStableIndex = 0
mHelper.stableRowIndex.forEach {
if (it < row) {
if (mManager.rowHeightSizeList.size > it) {
space += mManager.rowHeightSizeList[it]
}
topStableIndex = topStableIndex.coerceAtLeast(it)
}
}
mHelper.stickyRowIndex.forEach {
if (it in topStableIndex until row) {
if (mManager.rowHeightSizeList.size > it) {
space += mManager.rowHeightSizeList[it]
}
}
}
return space
}
override fun dispatchDraw(canvas: Canvas?) {
super.dispatchDraw(canvas)
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
}
private var pressX: Float = 0F
private var pressY: Float = 0F
private var lastX: Float = 0F
private var lastY: Float = 0F
private var scrollDirecton: Int = SCROLL_DIRECTION_VERTICAL
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
val result = super.onInterceptTouchEvent(ev)
if (ev?.action == MotionEvent.ACTION_MOVE && mIsBeingDragged) {
return true
}
when (ev?.action) {
MotionEvent.ACTION_MOVE -> {
if (!mIsBeingDragged) {
val dx = ev.x - pressX
val dy = ev.y - pressY
if (dx.absoluteValue > mTouchSlop) {
mIsBeingDragged = true
lastX = ev.x
lastY = ev.y
}
if (dy.absoluteValue > mTouchSlop) {
mIsBeingDragged = true
lastX = ev.x
lastY = ev.y
}
scrollDirecton =
if (dx.absoluteValue > dy.absoluteValue && horizontalScrollEnable) {
SCROLL_DIRECTION_HORIZONTAL
} else {
SCROLL_DIRECTION_VERTICAL
}
val parent = parent
parent?.requestDisallowInterceptTouchEvent(true)
}
}
MotionEvent.ACTION_DOWN -> {
startNestedScroll(SCROLL_AXIS_VERTICAL)
startNestedScroll(SCROLL_AXIS_HORIZONTAL)
pressX = ev.x
pressY = ev.y
lastX = ev.x
lastY = ev.y
mIsBeingDragged = false
}
MotionEvent.ACTION_CANCEL -> {
stopNestedScroll()
mIsBeingDragged = false
}
MotionEvent.ACTION_UP -> {
stopNestedScroll()
mIsBeingDragged = false
}
}
return result || mIsBeingDragged
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
val result = super.onTouchEvent(event)
event?.let {
when (it.action) {
MotionEvent.ACTION_DOWN -> {
}
MotionEvent.ACTION_MOVE -> {
if (mIsBeingDragged) {
var dx = it.x - lastX
var dy = it.y - lastY
var unConsumeX = 0F
var unConsumeY = 0F
lastX = it.x
lastY = it.y
dispatchNestedPreScroll(-dx.toInt(), -dy.toInt(), mScrollConsumed, null)
dx += mScrollConsumed[0]
dy += mScrollConsumed[1]
lastX += mScrollConsumed[0]
lastY += mScrollConsumed[1]
if (scrollDirecton == SCROLL_DIRECTION_VERTICAL) {
if (verticalScrollSpace + dy > 0) {
unConsumeY = dy - verticalScrollSpace
verticalScrollSpace = 0
} else if (verticalScrollSpace + dy < measuredHeight - mManager.getChildrenHeight()) {
unConsumeY =
dy - (measuredHeight - mManager.getChildrenHeight() - verticalScrollSpace)
verticalScrollSpace = measuredHeight - mManager.getChildrenHeight()
} else {
verticalScrollSpace += dy.toInt()
}
unConsumeX = 0F
} else {
if (horizontalScrollSpace + dx > 0) {
unConsumeX = dx - horizontalScrollSpace
horizontalScrollSpace = 0
} else if (horizontalScrollSpace + dx < measuredWidth - mManager.getChildrenWidth()) {
unConsumeX =
dx - (measuredWidth - mManager.getChildrenWidth() - horizontalScrollSpace)
horizontalScrollSpace = measuredWidth - mManager.getChildrenWidth()
} else {
horizontalScrollSpace += dx.toInt()
}
unConsumeY = 0F
}
requestLayout()
dispatchNestedScroll(
0,
0,
-unConsumeX.toInt(),
-unConsumeY.toInt(),
mWindowOffSet
)
lastX -= mWindowOffSet[0]
lastY -= mWindowOffSet[1]
}
}
else -> {}
}
}
return true
}
private fun getCellView(row: Int, column: Int): View? {
children.forEach {
if (it.layoutParams is LayoutParams) {
(it.layoutParams as LayoutParams).let { lp ->
if (lp.row == row && lp.column == column) {
return it
}
}
}
}
return null
}
/**
* 单元格Adapter
*
* cell 单元格
*/
abstract class Adapter() {
var updateListener: ((column: Int, row: Int) -> Unit)? = null
/**
* 创建view
*/
abstract fun createCellView(parent: View, column: Int, row: Int, cellType: Int): View
/**
* 绑定数据
*
* @param cellView 单元格view
* @param column 列
* @param row 行
* @param cellType 单元格类型
*/
abstract fun bindData(cellView: View, column: Int, row: Int, cellType: Int)
/**
* 获取列数
*/
abstract fun getColumnCount(): Int
/**
* 获取行数
*/
abstract fun getRowCount(): Int
/**
* 单元类型
*/
open fun getCellType(column: Int, row: Int): Int = 1
fun notifyDataSetChanged() {
updateListener?.invoke(-1, -1)
}
fun notifyItemChanged(column: Int, row: Int) {
updateListener?.invoke(column, row)
}
}
class LayoutParams : ViewGroup.LayoutParams(0, 0) {
var row: Int = 0
var column: Int = 0
}
/**
* 单元格管理类
*/
private class CellsManager {
var rowCount: Int = 0
var columnCount: Int = 0
/**
* 每行行高
*/
val rowHeightSizeList: MutableList<Int> = mutableListOf()
/**
* 每列列宽
*/
val columnWidthSizeList: MutableList<Int> = mutableListOf()
/**
* 重置
*/
fun reset() {
rowCount = 0
columnCount = 0
rowHeightSizeList.clear()
columnWidthSizeList.clear()
}
fun getLeftSpace(column: Int): Int {
var space = 0
for (i in 0 until column.coerceAtMost(columnCount)) {
if (columnWidthSizeList.size > i) {
space += columnWidthSizeList[i]
}
}
return space
}
fun getTopSpace(row: Int): Int {
var space = 0
for (i in 0 until row.coerceAtMost(rowCount)) {
if (rowHeightSizeList.size > i) {
space += rowHeightSizeList[i]
}
}
return space
}
fun getChildrenWidth(): Int {
var space = 0
columnWidthSizeList.forEach {
space += it
}
return space
}
fun getChildrenHeight(): Int {
var space = 0
rowHeightSizeList.forEach {
space += it
}
return space
}
}
/**
* 表格布局管理类
*
* 记录 跨行列、固定展示、粘性吸附属性信息
*/
private class LayoutHelper {
/**
* 跨行列单元格信息
*/
val spanCellInfo: MutableList<SpanCellItemInfo> = mutableListOf()
/**
* 固定展示行信息
*/
val stableRowIndex: MutableList<Int> = mutableListOf()
/**
* 固定展示列信息
*/
val stableColumnIndex: MutableList<Int> = mutableListOf()
/**
* 粘性吸附行信息
*/
val stickyRowIndex: MutableList<Int> = mutableListOf()
/**
* 粘性吸附列信息
*/
val stickyColumnIndex: MutableList<Int> = mutableListOf()
/**
* 判断单元格是否展示
*/
fun showCell(column: Int, row: Int): Boolean {
spanCellInfo.forEach {
if (column == it.cellColumnIndex && row == it.cellRowIndex) {
return true
}
if (it.cellColumnIndex <= column
&& it.cellColumnIndex + it.columnSpanSize > column
&& it.cellRowIndex <= row
&& it.cellRowIndex + it.rowSpanSize > row
) {
return false
}
}
return true
}
fun isSpanCell(column: Int, row: Int): Boolean {
spanCellInfo.forEach {
if (it.cellColumnIndex == column && it.cellRowIndex == row) {
return true
}
}
return false
}
/**
* 获取跨行列信息
*
* @return 如果单元格不存在跨行列信息,返回null
*/
fun getSpanInfo(column: Int, row: Int): SpanCellItemInfo? {
spanCellInfo.forEach {
if (it.cellRowIndex == row && it.cellColumnIndex == column) {
return it
}
}
return null
}
fun isStableCell(column: Int, row: Int): Boolean {
stableColumnIndex.forEach {
if (column == it) {
return true
}
}
stableRowIndex.forEach {
if (row == it) {
return true
}
}
return false
}
fun isStickyCell(column: Int, row: Int): Boolean {
stickyColumnIndex.forEach {
if (column == it) {
return true
}
}
stickyRowIndex.forEach {
if (row == it) {
return true
}
}
return false
}
fun getRightSpecialIndex(column: Int): Int {
var index = Int.MAX_VALUE
stableColumnIndex.forEach {
if (it > column) {
index = index.coerceAtMost(it)
}
}
stickyColumnIndex.forEach {
if (it > column) {
index = index.coerceAtMost(it)
}
}
return index
}
fun getBottomSpecialIndex(row: Int): Int {
var index = Int.MAX_VALUE
stableRowIndex.forEach {
if (it > row) {
index = index.coerceAtMost(it)
}
}
stickyRowIndex.forEach {
if (it > row) {
index = index.coerceAtMost(it)
}
}
return index
}
}
}
data class SpanCellItemInfo(
val cellRowIndex: Int = 0,
val cellColumnIndex: Int = 0,
val rowSpanSize: Int = 1,
val columnSpanSize: Int = 1
)
package com.elee.uidemo
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
class TableDemoActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_table_demo)
findViewById<ZrTableView>(R.id.view_table).apply {
// 合并单元格
// setSpanList(mutableListOf(SpanCellItemInfo(3, 3, 2, 2)))
// 吸附行列
// setStableColumnList(mutableListOf(1,4))
// setStableRowList(mutableListOf(1,4))
// setStickyColumnList(mutableListOf(2))
// setStickyRowList(mutableListOf(2))
adapter = object : ZrTableView.Adapter() {
override fun createCellView(
parent: View,
column: Int,
row: Int,
cellType: Int
): View = LayoutInflater.from(parent.context)
.inflate(R.layout.layout_table_cell_item, parent as ViewGroup, false)
override fun bindData(cellView: View, column: Int, row: Int, cellType: Int) {
cellView.findViewById<TextView>(R.id.tv_item).text =
"row: $row\ncolumn: $column"
cellView.findViewById<TextView>(R.id.tv_item).setOnClickListener { }
}
override fun getColumnCount(): Int = 10
override fun getRowCount(): Int = 20
}
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.elee.uidemo.ZrTableView
android:id="@+id/view_table"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_item"
android:layout_width="150dp"
android:layout_height="80dp"
android:background="@color/white"
android:gravity="center"
android:text="row:1\ncolumn: 1"
android:textSize="15sp" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#EEE" />
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:background="#EEE" />
</FrameLayout>