package com.lphtsccft.zhangle.market.ranking.view
import androidx.recyclerview.widget.RecyclerView
class FloatingHeaderAndColumnLayoutManager(val columnCount: Int = 10) : RecyclerView.LayoutManager() {
private var horizontalScrollOffset = 0
private var verticalScrollOffset = 0
private var currentScrollDirection: ScrollDirection? = null
private var totalWidth = 0
private var totalHeight = 0
private var mRowCount = 0
private enum class ScrollDirection { HORIZONTAL, VERTICAL }
override fun generateDefaultLayoutParams(): RecyclerView.LayoutParams {
return RecyclerView.LayoutParams(
RecyclerView.LayoutParams.WRAP_CONTENT,
RecyclerView.LayoutParams.WRAP_CONTENT
)
}
override fun canScrollHorizontally(): Boolean = true
override fun canScrollVertically(): Boolean = true
override fun scrollHorizontallyBy(dx: Int, recycler: RecyclerView.Recycler, state: RecyclerView.State): Int {
if (currentScrollDirection == null) currentScrollDirection = ScrollDirection.HORIZONTAL
if (currentScrollDirection != ScrollDirection.HORIZONTAL || mWidthDataList.maxOf { it.value } <= width) return 0
val scrollBy = calculateHorizontalScroll(dx)
offsetChildrenHorizontal(-scrollBy)
detachAndScrapAttachedViews(recycler)
layoutVisibleCells(recycler)
return scrollBy
}
override fun scrollVerticallyBy(dy: Int, recycler: RecyclerView.Recycler, state: RecyclerView.State): Int {
if (currentScrollDirection == null) currentScrollDirection = ScrollDirection.VERTICAL
if (currentScrollDirection != ScrollDirection.VERTICAL || mHeightDataList.maxOf { it.value } <= height) return 0
val scrollBy = calculateVerticalScroll(dy)
detachAndScrapAttachedViews(recycler)
offsetChildrenVertical(-scrollBy)
layoutVisibleCells(recycler)
return scrollBy
}
override fun onLayoutChildren(recycler: RecyclerView.Recycler, state: RecyclerView.State) {
if (itemCount == 0 || state.isPreLayout) return
detachAndScrapAttachedViews(recycler)
// 计算内容宽高
calculateDimensions(recycler)
// 布局可见单元格
layoutVisibleCells(recycler)
}
override fun onScrollStateChanged(state: Int) {
super.onScrollStateChanged(state)
if (state == RecyclerView.SCROLL_STATE_IDLE) {
currentScrollDirection = null // 滚动停止后重置方向锁
}
}
private fun calculateDimensions(recycler: RecyclerView.Recycler) {
mHeightDataList.clear()
mWidthDataList.clear()
mRowCount = if (itemCount % columnCount == 0) itemCount / columnCount else (itemCount / columnCount) + 1
for (i in 0 until columnCount) {
val view = recycler.getViewForPosition(i)
measureChildWithMargins(view, 0, 0)
totalWidth += getDecoratedMeasuredWidth(view)
mWidthDataList.add(Bean(i, totalWidth))
}
for (i in 0 until mRowCount) {
val view = recycler.getViewForPosition(i * columnCount)
measureChildWithMargins(view, 0, 0)
totalHeight += getDecoratedMeasuredHeight(view)
mHeightDataList.add(Bean(i, totalHeight))
}
}
private val mWidthDataList: MutableList<Bean> = mutableListOf()
private val mHeightDataList: MutableList<Bean> = mutableListOf()
data class Bean(val index: Int, val value: Int)
private fun layoutVisibleCells(recycler: RecyclerView.Recycler) {
val visibleLeft = horizontalScrollOffset
val visibleTop = verticalScrollOffset
val visibleRight = visibleLeft + width
val visibleBottom = visibleTop + height
val startColumn = mWidthDataList.find { it.value >= visibleLeft }?.index ?: 0
val endColumn = Math.min((mWidthDataList.find { it.value >= visibleRight }?.index ?: columnCount) + 1, columnCount)
val startRow = mHeightDataList.find { it.value >= visibleTop }?.index ?: 0
val endRow = Math.min((mHeightDataList.find { it.value >= visibleBottom }?.index ?: mRowCount) + 1, mRowCount)
// 布局其他单元格
for (row in startRow until endRow) {
for (column in startColumn until endColumn) {
val position = row * columnCount + column
if (position >= itemCount) continue
if (row == 0 || column == 0) continue // 跳过表头和左列
val view = recycler.getViewForPosition(position)
addView(view)
measureChildWithMargins(view, 0, 0)
val height = getDecoratedMeasuredHeight(view)
val width = getDecoratedMeasuredWidth(view)
val left = mWidthDataList[column - 1].value - horizontalScrollOffset
val top = mHeightDataList[row - 1].value - verticalScrollOffset
layoutDecorated(view, left, top, left + width, top + height)
}
}
var cStart = 0
// 布局悬浮表头(第一行)
for (column in startColumn until endColumn) {
if (column >= itemCount) continue
val view = recycler.getViewForPosition(column)
addView(view)
measureChildWithMargins(view, 0, 0)
val height = getDecoratedMeasuredHeight(view)
val width = getDecoratedMeasuredWidth(view)
val left = cStart - horizontalScrollOffset
layoutDecorated(view, left, 0, left + width, height)
cStart = mWidthDataList[column].value
}
var rStart = 0
// 布局悬浮左列(第一列)
for (row in startRow until endRow) {
val position = row * columnCount // 每行的第一列单元格位置
if (position >= itemCount) continue
val view = recycler.getViewForPosition(position)
addView(view)
measureChildWithMargins(view, 0, 0)
val height = getDecoratedMeasuredHeight(view)
val width = getDecoratedMeasuredWidth(view)
val top = rStart - verticalScrollOffset
layoutDecorated(view, 0, top, width, top + height)
rStart = mHeightDataList[row].value
}
// 处理坐标(0,0)
val view = recycler.getViewForPosition(0)
addView(view)
measureChildWithMargins(view, 0, 0)
val height = getDecoratedMeasuredHeight(view)
val width = getDecoratedMeasuredWidth(view)
layoutDecorated(view, 0, 0, width, height)
}
private fun calculateHorizontalScroll(dx: Int): Int {
val maxScroll = totalWidth - width
val scrollBy = when {
horizontalScrollOffset + dx < 0 -> -horizontalScrollOffset
horizontalScrollOffset + dx > maxScroll -> maxScroll - horizontalScrollOffset
else -> dx
}
horizontalScrollOffset += scrollBy
return scrollBy
}
private fun calculateVerticalScroll(dy: Int): Int {
val maxScroll = totalHeight - height
val scrollBy = when {
verticalScrollOffset + dy < 0 -> -verticalScrollOffset
verticalScrollOffset + dy > maxScroll -> maxScroll - verticalScrollOffset
else -> dy
}
verticalScrollOffset += scrollBy
return scrollBy
}
}
表格