【扩展】树形recycleView

mainActivity

class MainActivity : AppCompatActivity() {

    private lateinit var recyclerView: RecyclerView
    private lateinit var adapter: TreeAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        recyclerView = findViewById(R.id.recyclerView)
        recyclerView.layoutManager = LinearLayoutManager(this)

        // 初始化树形数据
        val roots = createInitialTreeData()
        Log.d("tree" , "roots.size = ${roots.size}")

        // 初始化所有可见节点
        val allVisibleNodes = initializeVisibleNodes(roots)

        Log.d("tree" , "allVisibleNodes.size = ${allVisibleNodes.size}")

        // 传递可见节点到适配器
        adapter = TreeAdapter(allVisibleNodes)
        recyclerView.adapter = adapter

    }

    // 初始化数据
    private fun createInitialTreeData(): MutableList<TreeNode> {
        // 创建根节点
        val rootNode1 = TreeNode(1, null, "Root 1", 0, false)
        val rootNode2 = TreeNode(2, null, "Root 2", 0, false)

        // 创建子节点
        val child1 = TreeNode(3, 1, "Child 1-1", 1, true)
        val child2 = TreeNode(4, 1, "Child 1-2", 1, true)
        val child3 = TreeNode(5, 2, "Child 2-1", 1, true)
        val grandChild1 = TreeNode(6, 3, "GrandChild 1-1", 2, true)
        val greatGrandChild1 = TreeNode(7, 6, "GreatGrandChild 1-1", 3, true)
        val greatGreatGrandChild1 = TreeNode(8, 7, "GreatGreatGrandChild 1-1", 4, true)
        val deepChild = TreeNode(9, 8, "DeepChild 1-1", 5, true)  // 超过5级的节点
        val deepChild1 = TreeNode(10, 9, "DeepChild 1-1-1", 6, true)  // 超过5级的节点

        // 构建树结构
        rootNode1.children.add(child1)
        rootNode1.children.add(child2)
        rootNode2.children.add(child3)
        child1.children.add(grandChild1)
        grandChild1.children.add(greatGrandChild1)
        greatGrandChild1.children.add(greatGreatGrandChild1)
        greatGreatGrandChild1.children.add(deepChild)
        deepChild.children.add(deepChild1)

        // 返回根节点列表
        val roots = mutableListOf(rootNode1, rootNode2)

        // 设置初始可见性
        for (root in roots) {
            setInitialVisibility(root)
        }

        return roots
    }

    // 递归设置初始可见性和展开状态
    private fun setInitialVisibility(node: TreeNode) {
        if (node.level < 5) {
            node.isVisible = true
            node.isExpanded = true
        } else {
            node.isVisible = false
            node.isExpanded = false
        }
        Log.d("TreeNode", "NodeName = ${node.name}, isVisable = ${node.isVisible}")
        Log.d("TreeNode", "NodeName = ${node.name}, isExpand = ${node.isExpanded}")

        // 递归设置子节点
        for (child in node.children) {
            setInitialVisibility(child)
        }
    }
    private fun initializeVisibleNodes(nodes: MutableList<TreeNode>): MutableList<TreeNode> {
        val allNodes = mutableListOf<TreeNode>()
        for (node in nodes) {

            allNodes.add(node)
            Log.d("allNode", "allNode add ${node.id}")
//            if (node.level >= 4) {
////                node.isCollapsed = true // 折叠 5 层及以上的节点
////            } else {
////                node.isCollapsed = false
////                if (node.isExpanded) {
////                    addVisibleChildren(allNodes, node)
////                }
////            }
            addVisibleChildren(allNodes, node)
        }
        Log.d("treeNode", "allNode size = ${allNodes.size}")
        return allNodes
    }


    // 递归添加子节点
    private fun addVisibleChildren(allNodes: MutableList<TreeNode>, node: TreeNode) {
        for (child in node.children) {
            if(child.level <= 4){
                allNodes.add(child)
                Log.d("allNode", "allNode add ${child.id}")
                if (child.isExpanded) {
                    addVisibleChildren(allNodes, child) // 递归添加展开的子节点
                }
            }

        }
    }


}

item

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="8dp">

    <TextView
        android:id="@+id/node_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

    <Button
        android:id="@+id/show_more"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Show More"
        android:background="@color/white"
        android:visibility="gone" /> <!-- 初始状态设为隐藏 -->
</LinearLayout>

Adapter

class TreeAdapter(
    private val nodes: MutableList<TreeNode>
) : RecyclerView.Adapter<TreeAdapter.TreeViewHolder>() {

    inner class TreeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val textView: TextView = itemView.findViewById(R.id.node_name)
        val showMore: TextView = itemView.findViewById(R.id.show_more)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TreeViewHolder {
        val view =
            LayoutInflater.from(parent.context).inflate(R.layout.tree_node_item, parent, false)

        return TreeViewHolder(view)
    }

    override fun onBindViewHolder(holder: TreeViewHolder, position: Int) {
        val node = nodes[position]

        // 控制节点的可见性,GONE 时不占位
        holder.itemView.visibility = if (node.isVisible) View.VISIBLE else View.GONE
        holder.textView.text = node.name

        // 设置节点缩进
        val paddingLeft = node.level * 50
        holder.textView.setPadding(paddingLeft, holder.textView.paddingTop, holder.textView.paddingRight, holder.textView.paddingBottom)


        // "Show More" 按钮显示逻辑 (在第 4 层显示)
        if (node.level == 4 && node.children.isNotEmpty()) {
            holder.showMore.visibility = View.VISIBLE
            holder.showMore.setOnClickListener {
                // 展开所有 5 层及以上的子节点
                expandAllChildrenAndNotify(node, position)
                holder.showMore.visibility = View.GONE
            }
        } else {
            holder.showMore.visibility = View.GONE
        }

    }

    private fun expandAllChildrenAndNotify(node: TreeNode, position: Int) {
        node.isExpanded = true
        val startPosition = position + 1 // 子节点插入的位置
        val children = expandAllChildrenRecursively(node)

        // 更新数据源,插入所有展开的子节点
        nodes.addAll(startPosition, children)

        // 通知 RecyclerView 插入了这些子节点
        notifyItemRangeInserted(startPosition, children.size)
    }

    // 递归展开所有子节点并返回子节点列表
    private fun expandAllChildrenRecursively(node: TreeNode): List<TreeNode> {
        val expandedNodes = mutableListOf<TreeNode>()
        for (child in node.children) {
            child.isVisible = true // 使子节点可见
            expandedNodes.add(child)
            if (child.children.isNotEmpty()) {
                expandedNodes.addAll(expandAllChildrenRecursively(child)) // 递归展开所有子节点
            }
        }
        return expandedNodes
    }


    override fun getItemCount(): Int {
        val res = nodes.count { it.isVisible }
        Log.d("Adapter", "total node Size: ${nodes.size }")
        Log.d("Adapter", "visiable node Size: ${res}")
        return nodes.size // 返回所有可见节点的数量
//        return res
    }


}

TreeNode

data class TreeNode(
    val id: Int,
    val parentId: Int? = null,
    val name: String,
    val level: Int,
    val isLeaf: Boolean,
    var isExpanded: Boolean = false,
    var isVisible: Boolean = false,
    var isCollapsed: Boolean = false, // 对于 5 层及以上的节点,默认折叠
    val children: MutableList<TreeNode> = mutableListOf()

)

activity_main

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp">

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Tree Structure Example"
        android:textSize="20sp"
        android:paddingBottom="16dp"
        android:layout_gravity="center"/>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="vertical"/>
</LinearLayout>

添加搜索框:

val searchEditText = findViewById<EditText>(R.id.search_edit_text)
        searchEditText.addTextChangedListener(object : TextWatcher {
            override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}

            override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
                filterNodes(s.toString())

            }
            override fun afterTextChanged(s: Editable?) {}
        })

过滤条件:

private fun filterNodes(query: String) {
        // 清空当前的过滤结果
        filteredNodes.clear()

        if (query.isEmpty()) {
            // 如果搜索框为空,显示全部节点
            filteredNodes.addAll(allNodes)
        } else {
            // 遍历所有节点,查找与搜索词匹配的节点(不分层级显示)
            for (node in allNodes) {
                if (node.name.contains(query, ignoreCase = true)) {
                    filteredNodes.add(node)
                }
                // 如果节点有子节点,递归遍历查找符合条件的子节点
                filteredNodes.addAll(searchChildrenRecursively(node.children, query))
            }
        }
        // 通知适配器数据已经更新
        adapter.notifyDataSetChanged()
    }

    // 递归搜索子节点
    fun searchChildrenRecursively(children: List<TreeNode>, query: String): List<TreeNode> {
        val result = mutableListOf<TreeNode>()
        for (child in children) {
            if (child.name.contains(query, ignoreCase = true)) {
                result.add(child)
            }
            if (child.children.isNotEmpty()) {
                result.addAll(searchChildrenRecursively(child.children, query))
            }
        }
        return result
    }

想想搜索结果是重新建个recycleView还是就用现在的
现在showmore 和点击文字都能展开了,要改

holder.textView.setOnClickListener {
            if (node.isExpanded) {
                // 收起当前节点的子节点
                collapseNode(node, position)
            } else {
                // 展开当前节点的子节点
                expandNode(node, position)
            }
        }
    private fun expandNode(node: TreeNode, position: Int) {
        node.isExpanded = true
        val startPosition = position + 1
        val children = expandAllChildrenRecursively(node)

        // 更新数据源并通知 RecyclerView 插入子节点
        nodes.addAll(startPosition, children)
        notifyItemRangeInserted(startPosition, children.size)
    }

    private fun collapseNode(node: TreeNode, position: Int) {
        node.isExpanded = false
        val children = collapseAllChildrenRecursively(node)

        // 更新数据源并通知 RecyclerView 删除子节点
        nodes.removeAll(children)
        notifyItemRangeRemoved(position + 1, children.size)
    }

    private fun expandAllChildrenRecursively(node: TreeNode): List<TreeNode> {
        val expandedNodes = mutableListOf<TreeNode>()
        for (child in node.children) {
            child.isVisible = true
            expandedNodes.add(child)
            if (child.isExpanded && child.children.isNotEmpty()) {
                expandedNodes.addAll(expandAllChildrenRecursively(child))
            }
        }
        return expandedNodes
    }
private fun collapseAllChildrenRecursively(node: TreeNode): List<TreeNode> {
    val collapsedNodes = mutableListOf<TreeNode>()
    for (child in node.children) {
        child.isVisible = false
        collapsedNodes.add(child)
        if (child.children.isNotEmpty()) {
            collapsedNodes.addAll(collapseAllChildrenRecursively(child))
        }
    }
    return collapsedNodes
}

搜索更新:

        searchEditText.addTextChangedListener(object : TextWatcher {
        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
            filterNodes(s.toString())
            Log.d("filter", "query ${s.toString()}")
        }
        override fun afterTextChanged(s: Editable?) { }
    })

    searchEditText.setOnEditorActionListener { v, actionId, event ->
        if (actionId == EditorInfo.IME_ACTION_SEARCH || actionId == EditorInfo.IME_ACTION_DONE || event != null && event.keyCode == KeyEvent.KEYCODE_ENTER) {
            filterNodes(searchEditText.text.toString())
            true
        } else {
            false
        }
    }
private fun filterNodes(query: String) {
        // 清空当前的过滤结果
        allVisibleNodes.clear()
        Log.d("filter", "filterNode.size before = ${allVisibleNodes.size}")
        if (query.isEmpty()) {
            // 如果搜索框为空,显示全部节点
            allVisibleNodes.addAll(roots)
        } else {
            // 遍历所有节点,查找与搜索词匹配的节点(不分层级显示)
            for (node in roots) {
                if (node.name.contains(query, ignoreCase = true)) {
                    allVisibleNodes.add(node.copy(level = -1))
                }
                // 如果节点有子节点,递归遍历查找符合条件的子节点
                allVisibleNodes.addAll(searchChildrenRecursively(node.children, query))
            }
        }
        Log.d("filter", "filterNode.size after = ${allVisibleNodes.size}")
        // 通知适配器数据已经更新
        adapter.notifyDataSetChanged()
    }

    // 递归搜索子节点
    fun searchChildrenRecursively(children: List<TreeNode>, query: String): List<TreeNode> {
        val result = mutableListOf<TreeNode>()
        for (child in children) {
            if (child.name.contains(query, ignoreCase = true)) {
                result.add(child.copy(level = -1))
            }
            if (child.children.isNotEmpty()) {
                result.addAll(searchChildrenRecursively(child.children, query))
            }
        }
        return result
    }

结点缩进

if(node.level >= 0){
            val paddingLeft = node.level * 50
            holder.textView.setPadding(paddingLeft, holder.textView.paddingTop, holder.textView.paddingRight, holder.textView.paddingBottom)
            holder.textView.setOnClickListener {
                if (node.isExpanded) {
                    // 收起当前节点的子节点
                    collapseNode(node, position)
                } else {
                    // 展开当前节点的子节点
                    expandNode(node, position)
                }
            }
        } else {
            holder.textView.setOnClickListener(null)
            holder.textView.setPadding(0, holder.textView.paddingTop, holder.textView.paddingRight, holder.textView.paddingBottom)
        }

构建树

data class Node(
    val id: Int,
    val parentId: Int?,
    val name: String,
    val level: Int,
    val children: MutableList<Node> = mutableListOf()
)

fun buildTree(nodes: List<Node>): List<Node> {
    val nodeMap = mutableMapOf<Int, Node>()
    val rootNodes = mutableListOf<Node>()

    nodes.forEach { node ->
        nodeMap[node.id] = node
        if (node.parentId == null) {
            rootNodes.add(node)
        } else {
            val parentNode = nodeMap.getOrPut(node.parentId) {
                Node(node.parentId, null, "Placeholder for ${node.parentId}", node.level - 1)
            }
            parentNode.children.add(node)
        }
    }

    // 遍历所有节点,清理占位符节点(如果有)
    rootNodes.removeIf { it.name.startsWith("Placeholder for") }
    rootNodes.forEach { node ->
        removePlaceholders(node)
    }

    return rootNodes
}

fun removePlaceholders(node: Node) {
    node.children.removeIf { it.name.startsWith("Placeholder for") }
    node.children.forEach { removePlaceholders(it) }
}

fun main() {
    val nodeList = listOf(
        Node(id = 1, parentId = null, name = "Root", level = 0),
        Node(id = 2, parentId = 1, name = "Child 1", level = 1),
        Node(id = 3, parentId = 1, name = "Child 2", level = 1),
        Node(id = 4, parentId = 2, name = "Child 1.1", level = 2),
        Node(id = 5, parentId = 6, name = "Orphan Child", level = 2), // This node's parent will be created as placeholder
        Node(id = 6, parentId = 1, name = "Child 3", level = 1)
    )

    val tree = buildTree(nodeList)
    println(tree)
}

使用 nodeMap 来存储所有节点,包括占位符节点。

在遍历节点时,如果 parentId 不为空且父节点还不存在,则创建一个占位符节点并将其存储在 nodeMap 中。

最后,清理根节点中的占位符节点,确保只有实际节点存在。

为了确保树中没有占位符节点,我们递归地清理每个节点的 children 列表。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值