diff --git a/expandable-recyclerview/build.gradle b/expandable-recyclerview/build.gradle index 44749ad..04d0d67 100644 --- a/expandable-recyclerview/build.gradle +++ b/expandable-recyclerview/build.gradle @@ -15,7 +15,7 @@ android { minSdkVersion 23 targetSdkVersion 29 versionCode 1 - versionName "0.0.1-alpha02" + versionName "0.0.1-beta01" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles 'consumer-rules.pro' diff --git a/expandable-recyclerview/src/main/java/com/github/adriankuta/expandable_recyclerview/ExpandableTreeNode.kt b/expandable-recyclerview/src/main/java/com/github/adriankuta/expandable_recyclerview/ExpandableTreeNode.kt index e45ae54..f321d1b 100644 --- a/expandable-recyclerview/src/main/java/com/github/adriankuta/expandable_recyclerview/ExpandableTreeNode.kt +++ b/expandable-recyclerview/src/main/java/com/github/adriankuta/expandable_recyclerview/ExpandableTreeNode.kt @@ -6,33 +6,77 @@ import com.github.adriankuta.datastructure.tree.TreeNode class ExpandableTreeNode(value: T) : TreeNode(value) { var expanded: Boolean = true - set(value) { - field = value - children.forEach { - (it as ExpandableTreeNode).expanded = value - } - } - override fun child(value: T, childDeclaration: ChildDeclaration?) { - val newChild = ExpandableTreeNode(value) + override fun child(child: T, childDeclaration: ChildDeclaration?) { + val newChild = ExpandableTreeNode(child) if (childDeclaration != null) newChild.childDeclaration() addChild(newChild) } - fun getVisibleNodeCount(): Int { - return onlyVisibleItems() + /** + * @param skipRootNode If `True`, then root node won't be counted. + */ + internal fun getVisibleNodeCount(skipRootNode: Boolean): Int { + var size = onlyVisibleItems() .size + + if (skipRootNode && size > 0) + size-- + + return size } - fun getVisibleNode(position: Int): ExpandableTreeNode { - return onlyVisibleItems()[position] + /** + * This function use Pre-order iteration to go through tree: + * ``` + * e.g. + * 1 + * / | \ + * / | \ + * 2 3 4 + * / \ / | \ + * 5 6 7 8 9 + * / / | \ + * 10 11 12 13 + * + * Output (skipRootNode = false): 1 2 5 10 6 11 12 13 3 4 7 8 9 + * Output (skipRootNode = true): 2 5 10 6 11 12 13 3 4 7 8 9 + * ``` + * @param skipRootNode If `True` root element will be omitted, and position 0 will be for first left child. + */ + internal fun getVisibleNode(position: Int, skipRootNode: Boolean): ExpandableTreeNode { + val nodePosition = if (skipRootNode) position + 1 else position + return onlyVisibleItems()[nodePosition] } + /** + * @return List of nodes which parent or higher ancestor aren't collapsed. + */ private fun onlyVisibleItems(): List> { //Visible if parent of node is expanded. return map { it as ExpandableTreeNode } - .filter { (it.parent as? ExpandableTreeNode)?.expanded ?: true } + .filter { allAncestorsAreExpanded(it) } + } + + /** + * `Ancestor` is a node reachable by repeated proceeding from child to parent. + * @return `True` if parent, and all parent's ancestors are expanded. + */ + private fun allAncestorsAreExpanded(node: ExpandableTreeNode): Boolean { + var ancestor = parent as? ExpandableTreeNode + var ancestorsAreExpanded = isAncestorExpanded(ancestor) + + while (ancestorsAreExpanded && ancestor != null) { + ancestor = ancestor.parent as? ExpandableTreeNode + ancestorsAreExpanded = ancestorsAreExpanded.and(isAncestorExpanded(ancestor)) + } + + return ancestorsAreExpanded + } + + private fun isAncestorExpanded(ancestor: ExpandableTreeNode?): Boolean { + return ancestor?.expanded ?: true } } \ No newline at end of file diff --git a/expandable-recyclerview/src/main/java/com/github/adriankuta/expandable_recyclerview/MultilevelRecyclerViewAdapter.kt b/expandable-recyclerview/src/main/java/com/github/adriankuta/expandable_recyclerview/MultilevelRecyclerViewAdapter.kt index 9a37046..0e80119 100644 --- a/expandable-recyclerview/src/main/java/com/github/adriankuta/expandable_recyclerview/MultilevelRecyclerViewAdapter.kt +++ b/expandable-recyclerview/src/main/java/com/github/adriankuta/expandable_recyclerview/MultilevelRecyclerViewAdapter.kt @@ -12,25 +12,35 @@ abstract class MultilevelRecyclerViewAdapter : abstract override fun onCreateViewHolder(parent: ViewGroup, nestLevel: Int): VH final override fun onBindViewHolder(holder: VH, position: Int) { - onBindViewHolder(holder, treeNodes.getVisibleNode(position)) + val visibleNode = treeNodes.getVisibleNode(position, true) + onBindViewHolder(holder, visibleNode, visibleNode.depth()) } - abstract fun onBindViewHolder(holder: VH, treeNode: ExpandableTreeNode) + abstract fun onBindViewHolder(holder: VH, treeNode: ExpandableTreeNode, nestLevel: Int) abstract fun getTreeNodes(): ExpandableTreeNode final override fun getItemCount(): Int { treeNodes = getTreeNodes() - return treeNodes.getVisibleNodeCount() + return treeNodes.getVisibleNodeCount(true) //We don't want to show root element. } final override fun getItemViewType(position: Int): Int { - - return treeNodes.getVisibleNode(position).depth() + return treeNodes.getVisibleNode(position, true).depth() } fun toggleGroup(expandableTreeNode: ExpandableTreeNode) { expandableTreeNode.expanded = !expandableTreeNode.expanded notifyDataSetChanged() } + + fun expand(expandableTreeNode: ExpandableTreeNode) { + expandableTreeNode.expanded = true + notifyDataSetChanged() + } + + fun collapse(expandableTreeNode: ExpandableTreeNode) { + expandableTreeNode.expanded = false + notifyDataSetChanged() + } } \ No newline at end of file