dingyi222666 / TreeView

An Android TreeView with RecyclerView
Apache License 2.0
109 stars 10 forks source link

Dynamic data not working. #14

Open simphonydeveloper opened 3 days ago

simphonydeveloper commented 3 days ago

class FileActivity : AppCompatActivity() {

    private lateinit var binding: ActivityFileBinding

    //private lateinit var tree: Tree<TreeNode2>
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityFileBinding.inflate(layoutInflater)
        setContentView(binding.root)

        val trees = createTree()

        (binding.treeview as TreeView<TreeNode2>).apply {
            bindCoroutineScope(lifecycleScope)
            this.tree = trees
            binder = ViewBinder()
            nodeEventListener = binder as TreeNodeEventListener<TreeNode2>
        }

        lifecycleScope.launch {
            binding.treeview.refresh()
        }
    }

    private fun createTree(): Tree<TreeNode2> {

        val rootNode1 = TreeNode2("1", "Root 1")
        val child1 =
            TreeNode2("1.1", "Child 1", parent = rootNode1)
        val child2 =
            TreeNode2("1.2", "Child 2", parent = rootNode1)
        val grandChild1 =
            TreeNode2("1.2.1", "Grandchild 1", parent = child2)
        child2.children.add(grandChild1)
        rootNode1.children.addAll(listOf(child1, child2))
        return Tree.createTree<TreeNode2>().apply {
            generator = ItemTreeNodeGenerator(rootNode1)
            initTree()
        }
    }

}

 class ViewBinder : TreeViewBinder<TreeNode2>(),
    TreeNodeEventListener<TreeNode2> {

    override fun createView(parent: ViewGroup, viewType: Int): View {
        val layoutInflater = LayoutInflater.from(parent.context)
        return if (viewType == 1) {
            ItemDirBinding.inflate(layoutInflater, parent, false).root
        } else {
            ItemFileBinding.inflate(layoutInflater, parent, false).root
        }
    }

    override fun getItemViewType(node: TreeNode<TreeNode2>): Int {
        if (node.data!!.children.isNotEmpty()) {
            return 1
        }
        return 0
    }

    override fun bindView(
        holder: TreeView.ViewHolder,
        node: TreeNode<TreeNode2>,
        listener: TreeNodeEventListener<TreeNode2>
    ) {
        if (node.data!!.children.isNotEmpty()) {
            applyDir(holder, node)
        } else {
            applyFile(holder, node)
        }

        val itemView = holder.itemView.findViewById<Space>(R.id.space)

        itemView.updateLayoutParams<ViewGroup.MarginLayoutParams> {
            width = node.depth * 22
        }

    }

    private fun applyFile(holder: TreeView.ViewHolder, node: TreeNode<TreeNode2>) {
        val binding = ItemFileBinding.bind(holder.itemView)
        binding.tvName.text = node.data!!.name.toString()
    }

    private fun applyDir(holder: TreeView.ViewHolder, node: TreeNode<TreeNode2>) {
        val binding = ItemDirBinding.bind(holder.itemView)
        binding.tvName.text = node.data!!.name.toString()

        binding
            .ivArrow
            .animate()
            .rotation(if (node.data!!.isExpanded) 90f else 0f)
            .setDuration(200)
            .start()
    }

    override fun onClick(node: TreeNode<TreeNode2>, holder: TreeView.ViewHolder) {
        if (node.data!!.children.isNotEmpty()) {
            applyDir(holder, node)
        } else {
            //Toast.makeText(this@MainActivity, "Clicked ${node.name}", Toast.LENGTH_LONG).show()
        }
    }

    override fun onToggle(
        node: TreeNode<TreeNode2>,
        isExpand: Boolean,
        holder: TreeView.ViewHolder
    ) {
        applyDir(holder, node)
    }
}

 class ItemTreeNodeGenerator(
    private val rootItem: TreeNode2
) : TreeNodeGenerator<TreeNode2> {
     suspend fun fetchNodeChildData(targetNode: TreeNode<TreeNode2>): Set<TreeNode2> {
         return targetNode.requireData().children.toSet()
     }

    override fun createNode(
        parentNode: TreeNode<TreeNode2>,
        currentData: TreeNode2,
        tree: AbstractTree<TreeNode2>
    ): TreeNode<TreeNode2> {
        return TreeNode(
            data = currentData,
            depth = parentNode.depth + 1,
            name = currentData.name,
            id = tree.generateId(),
            hasChild = currentData.children.isNotEmpty(),
            // It should be taken from the Item
            isChild = currentData.children.isNotEmpty(),
            expand = false
        )
    }

    override fun createRootNode(): TreeNode<TreeNode2> {
        return TreeNode(
            data = rootItem,
            // Set to -1 to not show the root node
            depth = -1,
            name = rootItem.name,
            id = Tree.ROOT_NODE_ID,
            hasChild = true,
            isChild = true
        )
    }

     override suspend fun fetchChildData(targetNode: TreeNode<TreeNode2>): Set<TreeNode2> {
         return targetNode.requireData().children.toSet()
     }

 }

data class TreeNode2(
    val id: String,
    val name: String,
    val children: MutableList<TreeNode2> = mutableListOf(),
    var isExpanded: Boolean = false,
    var level: Int = 0,
    var isSwitchOn: Boolean = false,
    var parent: TreeNode2? = null 
)

Part of my inspiration comes from here. https://github.com/dingyi222666/TreeView/issues/4 It seems to have entered an infinite loop problem.

this is my version io.github.dingyi222666:treeview:1.3.1

Thanks ERROR:

AndroidRuntime          com.example.myapplicationtest        E      at com.example.myapplicationtest.model.TreeNode2.hashCode(Unknown Source:61)
                                                                                                        at java.util.ArrayList.hashCodeRange(ArrayList.java:684)
                                                                                                        at java.util.ArrayList.hashCode(ArrayList.java:671)
                                                                                                        at com.example.myapplicationtest.model.TreeNode2.hashCode(Unknown Source:19)
                                                                                                        at com.example.myapplicationtest.model.TreeNode2.hashCode(Unknown Source:61)
                                                                                                        at java.util.ArrayList.hashCodeRange(ArrayList.java:684)
                                                                                                        at java.util.ArrayList.hashCode(ArrayList.java:671)
                                                                                                        at com.example.myapplicationtest.model.TreeNode2.hashCode(Unknown Source:19)
                                                                                                        at com.example.myapplicationtest.model.TreeNode2.hashCode(Unknown Source:61)
                                                                                                        at java.util.ArrayList.hashCodeRange(ArrayList.java:684)
                                                                                                        at java.util.ArrayList.hashCode(ArrayList.java:671)
                                                                                                        at com.example.myapplicationtest.model.TreeNode2.hashCode(Unknown Source:19)
                                                                                                        at com.example.myapplicationtest.model.TreeNode2.hashCode(Unknown Source:61)
                                                                                                        at java.util.ArrayList.hashCodeRange(ArrayList.java:684)
                                                                                                        at java.util.ArrayList.hashCode(ArrayList.java:671)
                                                                                                        at com.example.myapplicationtest.model.TreeNode2.hashCode(Unknown Source:19)
                                                                                                        at com.example.myapplicationtest.model.TreeNode2.hashCode(Unknown Source:61)
                                                                                                        at java.util.ArrayList.hashCodeRange(ArrayList.java:684)
                                                                                                        at java.util.ArrayList.hashCode(ArrayList.java:671)
                                                                                                        at com.example.myapplicationtest.model.TreeNode2.hashCode(Unknown Source:19)
                                                                                                        at com.example.myapplicationtest.model.TreeNode2.hashCode(Unknown Source:61)
                                                                                                        at java.util.ArrayList.hashCodeRange(ArrayList.java:684)
                                                                                                        at java.util.ArrayList.hashCode(ArrayList.java:671)
                                                                                                        at com.example.myapplicationtest.model.TreeNode2.hashCode(Unknown Source:19)
                                                                                                        at com.example.myapplicationtest.model.TreeNode2.hashCode(Unknown Source:61)
                                                                                                        at java.util.ArrayList.hashCodeRange(ArrayList.java:684)
                                                                                                        at java.util.ArrayList.hashCode(ArrayList.java:671)
                                                                                                        at com.example.myapplicationtest.model.TreeNode2.hashCode(Unknown Source:19)
                                                                                                        at com.example.myapplicationtest.model.TreeNode2.hashCode(Unknown Source:61)
                                                                                                        at java.util.ArrayList.hashCodeRange(ArrayList.java:684)
                                                                                                        at java.util.ArrayList.hashCode(ArrayList.java:671)
                                                                                                        at com.example.myapplicationtest.model.TreeNode2.hashCode(Unknown Source:19)
                                                                                                        at com.example.myapplicationtest.model.TreeNode2.hashCode(Unknown Source:61)
                                                                                                        at java.util.HashMap.hash(HashMap.java:337)
                                                                                                        at java.util.HashMap.put(HashMap.java:617)
                                                                                                        at java.util.HashSet.add(HashSet.java:229)
                                                                                                        at kotlin.collections.CollectionsKt___CollectionsKt.toCollection(_Collections.kt:1296)
                                                                                                        at kotlin.collections.CollectionsKt___CollectionsKt.toSet(_Collections.kt:1348)
                                                                                                        at com.example.myapplicationtest.ItemTreeNodeGenerator.fetchChildData(FileActivity.kt:197)
                                                                                                        at io.github.dingyi222666.view.treeview.Tree.refreshInternal(trees.kt:206)
                                                                                                        at io.github.dingyi222666.view.treeview.Tree.refresh(trees.kt:231)
                                                                                                        at io.github.dingyi222666.view.treeview.Tree.getChildNodesInternal(trees.kt:103)
                                                                                                        at io.github.dingyi222666.view.treeview.Tree.visitInternal(trees.kt:338)
                                                                                                        at io.github.dingyi222666.view.treeview.Tree.visit(trees.kt:297)
                                                                                                        at io.github.dingyi222666.view.treeview.AbstractTreeKt.toSortedList(AbstractTree.kt:408)
                                                                                                        at io.github.dingyi222666.view.treeview.AbstractTreeKt.toSortedList$default(AbstractTree.kt:383)
                                                                                                        at io.github.dingyi222666.view.treeview.TreeView.refresh(TreeView.kt:173)
                                                                                                        at io.github.dingyi222666.view.treeview.TreeView.refresh$default(TreeView.kt:157)
                                                                                                        at com.example.myapplicationtest.FileActivity$onCreate$2.invokeSuspend(FileActivity.kt:63)
                                                                                                        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
                                                                                                        at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:367)
                                                                                                        at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:30)
                                                                                                        at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable$default(Cancellable.kt:25)
                                                                                                        at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:110)
                                                                                                        at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:126)
2024-11-27 13:28:55.527 10530-10530 AndroidRuntime          com.example.myapplicationtest        E      at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch(Builders.common.kt:56)
                                                                                                        at kotlinx.coroutines.BuildersKt.launch(Unknown Source:1)
                                                                                                        at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch$default(Builders.common.kt:47)
                                                                                                        at kotlinx.coroutines.BuildersKt.launch$default(Unknown Source:1)
                                                                                                        at com.example.myapplicationtest.FileActivity.onCreate(FileActivity.kt:62)
                                                                                                        at android.app.Activity.performCreate(Activity.java:9002)
                                                                                                        at android.app.Activity.performCreate(Activity.java:8980)
                                                                                                        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1526)
                                                                                                        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:4030)
                                                                                                        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:4235)
                                                                                                        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:112)
                                                                                                        at android.app.servertransaction.TransactionExecutor.executeNonLifecycleItem(TransactionExecutor.java:174)
                                                                                                        at android.app.servertransaction.TransactionExecutor.executeTransactionItems(TransactionExecutor.java:109)
                                                                                                        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:81)
                                                                                                        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2636)
                                                                                                        at android.os.Handler.dispatchMessage(Handler.java:107)
                                                                                                        at android.os.Looper.loopOnce(Looper.java:232)
                                                                                                        at android.os.Looper.loop(Looper.java:317)
                                                                                                        at android.app.ActivityThread.main(ActivityThread.java:8705)
                                                                                                        at java.lang.reflect.Method.invoke(Native Method)
                                                                                                        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:580)
                                                                                                        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:886)
dingyi222666 commented 3 days ago

Your TreeNode2 class should not contain the parent field. Remove it and try again.

simphonydeveloper commented 3 days ago

thank you, it's working. i want to know,Can this component work well with Switch? Thanks again.

dingyi222666 commented 3 days ago

It's okay. Check out the code https://github.com/dingyi222666/TreeView/blob/1706aca5112f2760c20a0f3f5ad2f3b73a4def42/treeview/src/main/kotlin/io/github/dingyi222666/view/treeview/TreeView.kt#L912 that supports components that implement the Checkable interface.

simphonydeveloper commented 3 days ago

微信截图_20241127154717 I haven't implemented getCheckableView. I want to update all child nodes below when changing a parent node, but I couldn't find a parent class in ViewBinding that can operate on all data properties or methods. Perhaps what I did was wrong.

Thanks

dingyi222666 commented 3 days ago

First implement getCheckableView.

https://github.com/dingyi222666/TreeView/blob/1706aca5112f2760c20a0f3f5ad2f3b73a4def42/app/src/main/kotlin/com/dingyi/treeview/MainActivity.kt#L342-L350

Set the selection mode to MULTIPLE_WITH_CHILDREN, then operate on the treeView by calling selectNode to select the corresponding parent node. When this happens, the child nodes will also be updated.

https://github.com/dingyi222666/TreeView/blob/1706aca5112f2760c20a0f3f5ad2f3b73a4def42/app/src/main/kotlin/com/dingyi/treeview/MainActivity.kt#L152-L156

simphonydeveloper commented 3 days ago

Sorry,I have reviewed your example, and you executed it through the menu item. How do I perform this operation on the switch of a certain node?

Thanks


 class ViewBinder : TreeViewBinder<TreeNode2>(),
    TreeNodeEventListener<TreeNode2> {

    override fun createView(parent: ViewGroup, viewType: Int): View {
        val layoutInflater = LayoutInflater.from(parent.context)
        return if (viewType == 1) {
            ItemDirBinding.inflate(layoutInflater, parent, false).root
        } else {
            ItemFileBinding.inflate(layoutInflater, parent, false).root
        }
    }

    override fun getItemViewType(node: TreeNode<TreeNode2>): Int {
        if (node.data!!.children.isNotEmpty()) {
            return 1
        }
        return 0
    }

    override fun bindView(
        holder: TreeView.ViewHolder,
        node: TreeNode<TreeNode2>,
        listener: TreeNodeEventListener<TreeNode2>
    ) {
        if (node.data!!.children.isNotEmpty()) {
            applyDir(holder, node)
        } else {
            applyFile(holder, node)
        }

        val itemView = holder.itemView.findViewById<Space>(R.id.space)

//        (getCheckableView(node, holder) as Switch).apply {
//            isVisible = node.selected
//            isSelected = node.selected
//        }

        itemView.updateLayoutParams<ViewGroup.MarginLayoutParams> {
            width = node.depth * 22
        }

    }

     override fun getCheckableView(
         node: TreeNode<TreeNode2>,
         holder: TreeView.ViewHolder
     ): Checkable? {

         return if (node.isChild) {
             ItemDirBinding.bind(holder.itemView).switchOn
         } else {
             ItemFileBinding.bind(holder.itemView).switchOn
         }
         //return  super.getCheckableView(node, holder)
     }

    private fun applyFile(holder: TreeView.ViewHolder, node: TreeNode<TreeNode2>) {
        val binding = ItemFileBinding.bind(holder.itemView)
        binding.tvName.text = node.data!!.name.toString()
    }

     private fun applyDir(holder: TreeView.ViewHolder, node: TreeNode<TreeNode2>) {
         val binding = ItemDirBinding.bind(holder.itemView)
         binding.tvName.text = node.data!!.name.toString()
         binding.switchOn.isChecked =  node.data!!.isSwitchOn
         binding.switchOn.setOnCheckedChangeListener(object :
             CompoundButton.OnCheckedChangeListener {
             override fun onCheckedChanged(p0: CompoundButton?, p1: Boolean) {
//                 node.data!!.isSwitchOn = p1
//                 if (node.hasChild) {
//                     flattenTree(node.data!!.children, p1)
//                 }
/// bad code  
                 lifecycleScope.launch {
                     binding.treeview.apply {
                         // select node and it's children
                         selectionMode = TreeView.SelectionMode.MULTIPLE_WITH_CHILDREN
                         selectNode(binding.treeview.tree.rootNode, true)
                         expandAll()
                         selectionMode = TreeView.SelectionMode.MULTIPLE_WITH_CHILDREN
                     }
                 }
             }
         })

         binding
             .ivArrow
             .animate()
             .rotation(if (node.expand) 180f else 0f)
             .setDuration(200)
             .start()
     }

     fun flattenTree(treeNodes: List<TreeNode2>, isSwitchOn: Boolean): List<TreeNode2> {
         val result = mutableListOf<TreeNode2>()
         for (node in treeNodes) {
             node.isSwitchOn = isSwitchOn
             result.add(node)
             if (node.children.isNotEmpty()) {
                 val flattenedChildren = flattenTree(node.children, isSwitchOn)
                 result.addAll(flattenedChildren)
             }
         }
         return result
     }

//     fun flattenTree(): List<TreeNode2> {
//         val result = mutableListOf<TreeNode2>()
//         for (node in treeNodes) {
//             node.isSwitchOn = isSwitchOn
//             result.add(node)
//             if (node.children.isNotEmpty()) {
//                 val flattenedChildren = flattenTree(node.children, isSwitchOn)
//                 result.addAll(flattenedChildren)
//             }
//         }
//         return result
//     }

    override fun onClick(node: TreeNode<TreeNode2>, holder: TreeView.ViewHolder) {
        if (node.data!!.children.isNotEmpty()) {
            applyDir(holder, node)
        } else {
            //Toast.makeText(this@MainActivity, "Clicked ${node.name}", Toast.LENGTH_LONG).show()
        }
    }

    override fun onToggle(
        node: TreeNode<TreeNode2>,
        isExpand: Boolean,
        holder: TreeView.ViewHolder
    ) {
        applyDir(holder, node)
    }
}

 class ItemTreeNodeGenerator(
    private val rootItem: TreeNode2
) : TreeNodeGenerator<TreeNode2> {
     suspend fun fetchNodeChildData(targetNode: TreeNode<TreeNode2>): Set<TreeNode2> {
         return targetNode.requireData().children.toSet()
     }

    override fun createNode(
        parentNode: TreeNode<TreeNode2>,
        currentData: TreeNode2,
        tree: AbstractTree<TreeNode2>
    ): TreeNode<TreeNode2> {
        return TreeNode(
            data = currentData,
            depth = parentNode.depth + 1,
            name = currentData.name,
            id = tree.generateId(),
            hasChild = currentData.children.isNotEmpty(),
            // It should be taken from the Item
            isChild = currentData.children.isNotEmpty(),
            expand = false
        )
    }

    override fun createRootNode(): TreeNode<TreeNode2> {
        return TreeNode(
            data = rootItem,
            // Set to -1 to not show the root node
            depth = -1,
            name = rootItem.name,
            id = Tree.ROOT_NODE_ID,
            hasChild = true,
            isChild = true
        )
    }

     override suspend fun fetchChildData(targetNode: TreeNode<TreeNode2>): Set<TreeNode2> {
         return targetNode.requireData().children.toSet()
     }

 }
dingyi222666 commented 3 days ago

Upload all your example code to github as a repository, I'll read and modify it later.

simphonydeveloper commented 3 days ago

repository: https://github.com/simphonydeveloper/AndroidTreeViewSample.git

Thanks

dingyi222666 commented 2 days ago

Okay. You can check out my code example: https://github.com/dingyi222666/AndroidTreeViewSample

simphonydeveloper commented 1 day ago

Thank you! Great code, I have implemented the simultaneous selection and closure of Switch according to your code. When all child nodes are selected, the parent node is also selected. This operation needs to be completed in the class DataSource, right? ⭐

dingyi222666 commented 7 hours ago

You need to change on both the DataSource and the Tree.