compose 树形列表

0 阅读1分钟

Compose Tree List

compose 树形列表,支持上万数据

Screenshot_20260212_145526.png

Screenshot_20260212_145310.png


@Composable
fun <T> TreeLazyColumn(
    modifier: Modifier = Modifier,
    state: TreeState<T>,
    currentId: String? = null,
    indentWidth: Dp = 16.dp,
    itemContent: @Composable (
        node: TreeNode<T>,
        level: Int,
        isExpanded: Boolean,
        isCurrent: Boolean,
        toggle: () -> Unit
    ) -> Unit
) {
    /**
     * 派生状态
     * expandedIds 改变时自动重新计算
     */
    val visibleNodes by remember { derivedStateOf { state.visibleNodes() } }

    LazyColumn(modifier) {
        items(
            items = visibleNodes,
            key = { it.node.id } // key 防止列表错乱
        ) { flatNode ->

            val node = flatNode.node
            val level = flatNode.level
            val isExpanded = state.isExpanded(node.id)
            val isCurrent = currentId == node.id

            Row(
                modifier = Modifier
                    .fillMaxWidth()
                    // 根据层级缩进
                    .padding(start = indentWidth * level)
            ) {
                itemContent(
                    node,
                    level,
                    isExpanded,
                    isCurrent
                ) {
                    // 点击箭头时切换展开
                    state.toggle(node.id)
                }
            }
        }
    }
}
class TreeState<T>(
    nodes: List<TreeNode<T>>
) {

    /** 根据 id 快速查找节点 */
    private val nodeMap = nodes.associateBy { it.id }.toMutableMap()

    /** parentId -> children 列表 */
    private val childrenMap = nodes.groupBy { it.parentId }

    /**
     * 当前展开的节点ID集合
     * mutableStateListOf 才能触发 Compose 刷新
     */
    var expandedIds = mutableStateListOf<String>()
        private set

    /** 展开/收起节点 */
    fun toggle(id: String) {
        if (expandedIds.contains(id)) expandedIds.remove(id)
        else expandedIds.add(id)
    }

    /** 判断节点是否展开 */
    fun isExpanded(id: String) = expandedIds.contains(id)

    /**
     * ⭐核心算法:生成可见节点
     * 把树 -> 扁平列表
     *
     * LazyColumn 只能高效渲染 List
     * 所以必须把树结构拍平成 List
     */
    fun visibleNodes(): List<FlatTreeNode<T>> {
        val result = mutableListOf<FlatTreeNode<T>>()

        /**
         * 递归添加子节点
         * @param parentId 父节点ID
         * @param level 当前层级
         */
        fun addChildren(parentId: String?, level: Int) {
            val children = childrenMap[parentId] ?: return

            children.forEach { node ->
                // 添加当前节点
                result.add(FlatTreeNode(node, level))

                // 如果节点展开 -> 递归添加子节点
                if (isExpanded(node.id)) {
                    addChildren(node.id, level + 1)
                }
            }
        }
        // 从 root 开始递归
        addChildren(null, 0)
        return result
    }
}

Usage

 val treeState = remember { TreeState(nodes) }
var currentId by remember { mutableStateOf<String?>(null) }
TreeLazyColumn(
    modifier = modifier
        .fillMaxSize(),
    state = treeState,
    currentId = currentId
) { node, level, expanded, isCurrent, toggle ->
    val hasChildren = remember(node.id) {
        nodes.any { it.parentId == node.id }
    }
    DefaultTreeItem(
        title = node.data,
        level = level,
        isExpanded = expanded,
        isCurrent = isCurrent,
        hasChildren = hasChildren,
        onToggle = toggle,
        onClick = { currentId = node.id },
    )
}

github 地址

喜欢的朋友可以点个星星