├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── gapo │ │ └── treeview │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── gapo │ │ │ └── treeview │ │ │ ├── MainActivity.kt │ │ │ ├── MultiChoiceActivity.kt │ │ │ ├── SampleModel.kt │ │ │ └── SingleChoiceActivity.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── ic_arrowhead_right.xml │ │ ├── ic_folder.xml │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── activity_multi_choice.xml │ │ ├── activity_single_choice.xml │ │ ├── multi_node_view_item.xml │ │ └── single_node_view_item.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── values-night │ │ └── themes.xml │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ └── test │ └── java │ └── com │ └── gapo │ └── treeview │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── screenshots ├── multi.gif └── single.gif ├── settings.gradle └── treeview ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src ├── androidTest └── java │ └── com │ └── gg │ └── gapo │ └── treeviewlib │ └── ExampleInstrumentedTest.kt ├── main ├── AndroidManifest.xml ├── java │ └── com │ │ └── gg │ │ └── gapo │ │ └── treeviewlib │ │ ├── DefaultTreeItemDecoration.kt │ │ ├── GapoTreeView.kt │ │ ├── TreeViewBuilder.kt │ │ ├── adapter │ │ └── TreeViewAdapter.kt │ │ └── model │ │ ├── NodeData.kt │ │ ├── NodeState.kt │ │ └── NodeViewData.kt └── res │ └── values │ └── dimens.xml └── test └── java └── com └── gg └── gapo └── treeviewlib └── ExampleUnitTest.kt /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/android,intellij 2 | # Edit at https://www.gitignore.io/?templates=android,intellij 3 | 4 | ### Android ### 5 | # Built application files 6 | *.apk 7 | *.ap_ 8 | *.aab 9 | 10 | # Files for the ART/Dalvik VM 11 | *.dex 12 | 13 | # Java class files 14 | *.class 15 | 16 | # Generated files 17 | bin/ 18 | gen/ 19 | out/ 20 | 21 | # Gradle files 22 | .gradle/ 23 | build/ 24 | 25 | # Local configuration file (sdk path, etc) 26 | local.properties 27 | 28 | # Proguard folder generated by Eclipse 29 | proguard/ 30 | 31 | # Log Files 32 | *.log 33 | 34 | # Android Studio Navigation editor temp files 35 | .navigation/ 36 | 37 | # Android Studio captures folder 38 | captures/ 39 | 40 | # IntelliJ 41 | *.iml 42 | .idea 43 | .idea/workspace.xml 44 | .idea/tasks.xml 45 | .idea/gradle.xml 46 | .idea/assetWizardSettings.xml 47 | .idea/dictionaries 48 | .idea/libraries 49 | .idea/caches 50 | # Android Studio 3 in .gitignore file. 51 | .idea/caches/build_file_checksums.ser 52 | .idea/modules.xml 53 | 54 | # Keystore files 55 | # Uncomment the following lines if you do not want to check your keystore files in. 56 | #*.jks 57 | #*.keystore 58 | 59 | # External native build folder generated in Android Studio 2.2 and later 60 | .externalNativeBuild 61 | 62 | # Google Services (e.g. APIs or Firebase) 63 | # google-services.json 64 | 65 | # Freeline 66 | freeline.py 67 | freeline/ 68 | freeline_project_description.json 69 | 70 | # fastlane 71 | fastlane/report.xml 72 | fastlane/Preview.html 73 | fastlane/screenshots 74 | fastlane/test_output 75 | fastlane/readme.md 76 | 77 | # Version control 78 | vcs.xml 79 | 80 | # lint 81 | lint/intermediates/ 82 | lint/generated/ 83 | lint/outputs/ 84 | lint/tmp/ 85 | # lint/reports/ 86 | 87 | ### Android Patch ### 88 | gen-external-apklibs 89 | output.json 90 | 91 | ### Intellij ### 92 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 93 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 94 | 95 | # User-specific stuff 96 | .idea/**/workspace.xml 97 | .idea/**/tasks.xml 98 | .idea/**/usage.statistics.xml 99 | .idea/**/dictionaries 100 | .idea/**/shelf 101 | 102 | # Generated files 103 | .idea/**/contentModel.xml 104 | 105 | # Sensitive or high-churn files 106 | .idea/**/dataSources/ 107 | .idea/**/dataSources.ids 108 | .idea/**/dataSources.local.xml 109 | .idea/**/sqlDataSources.xml 110 | .idea/**/dynamic.xml 111 | .idea/**/uiDesigner.xml 112 | .idea/**/dbnavigator.xml 113 | 114 | # Gradle 115 | .idea/**/gradle.xml 116 | .idea/**/libraries 117 | 118 | # Gradle and Maven with auto-import 119 | # When using Gradle or Maven with auto-import, you should exclude module files, 120 | # since they will be recreated, and may cause churn. Uncomment if using 121 | # auto-import. 122 | # .idea/modules.xml 123 | # .idea/*.iml 124 | # .idea/modules 125 | 126 | # CMake 127 | cmake-build-*/ 128 | 129 | # Mongo Explorer plugin 130 | .idea/**/mongoSettings.xml 131 | 132 | # File-based project format 133 | *.iws 134 | 135 | # IntelliJ 136 | 137 | # mpeltonen/sbt-idea plugin 138 | .idea_modules/ 139 | 140 | # JIRA plugin 141 | atlassian-ide-plugin.xml 142 | 143 | # Cursive Clojure plugin 144 | .idea/replstate.xml 145 | 146 | # Crashlytics plugin (for Android Studio and IntelliJ) 147 | com_crashlytics_export_strings.xml 148 | crashlytics.properties 149 | crashlytics-build.properties 150 | fabric.properties 151 | 152 | # Editor-based Rest Client 153 | .idea/httpRequests 154 | 155 | # Android studio 3.1+ serialized cache file 156 | 157 | # JetBrains templates 158 | **___jb_tmp___ 159 | 160 | ### Intellij Patch ### 161 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 162 | 163 | # *.iml 164 | # modules.xml 165 | # .idea/misc.xml 166 | # *.ipr 167 | 168 | # Sonarlint plugin 169 | .idea/sonarlint 170 | 171 | # End of https://www.gitignore.io/api/android,intellij 172 | 173 | # Other 174 | /local.properties 175 | .DS_Store 176 | /captures 177 | /state.logs 178 | .externalNativeBuild 179 | keystore.properties 180 | sentry.properties 181 | *.jks 182 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021, Gapo Technology JSC 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of Gapo Technology JSC nor the names of its contributors 12 | may be used to endorse or promote products derived from this software 13 | without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL GAPO TECHNOLOGY JSC BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gapo Tree View 2 | Support TreeView for RecyclerView with highly customizable 3 | 4 | ## Demo 5 |

6 | 7 | 8 |

9 | 10 | ## Features 11 | - Expand/Collapse nodes 12 | - Select/Unselect/Get Selected nodes 13 | - Highly customizable UI with NodeState (example below) 14 | - Support margin by node's level 15 | - Support concat adapters 16 | - Show/Hide Tree: Highly effective when used with concat adapters 17 | 18 | ## Installation 19 | Gradle 20 | ```kotlin 21 | implementation 'vn.gapowork.android:tree-view:1.0.0-alpha01' 22 | ``` 23 | 24 | ## Usage 25 | ### Prepare input data, the model need extends NodeData, example: 26 | ```kotlin 27 | data class Example( 28 | //required 29 | override val nodeViewId: String, 30 | val child: List, 31 | //your custom field 32 | val exampleId: String, 33 | val exampleName: String, 34 | ) : NodeData { 35 | 36 | //list child 37 | override fun getNodeChild(): List> { 38 | return child 39 | } 40 | 41 | //for diff util 42 | override fun areItemsTheSame(item: NodeData): Boolean { 43 | ... 44 | } 45 | 46 | //for diff util 47 | override fun areContentsTheSame(item: NodeData): Boolean { 48 | ... 49 | } 50 | 51 | //for diff util 52 | override fun getChangePayload(item: NodeData): Bundle { 53 | return Bundle() 54 | } 55 | } 56 | ``` 57 | 58 | ### Create Tree 59 | ```kotlin 60 | val treeView = GapoTreeView.Builder.plant(Context) 61 | .withRecyclerView(binding.treeViewDepartment) //RecyclerView will be supported tree view 62 | .withLayoutRes(R.layout.department_node_view_item) //item layout 63 | .setListener(GapoTreeView.Listener) //listener of tree view 64 | .setData(List) //list NodeData 65 | .itemMargin(Int) //optional: margin by node's level. default = 24dp 66 | .addItemDecoration() //optional: item decoration of RecyclerView. If use this will disable feature itemMargin 67 | .showAllNodes(Boolean) //optional: show all nodes or just show parent node. default = false 68 | .addAdapters(config: ConcatAdapter.Config, adapters: List>) //optional: the adapters to concat 69 | .build() 70 | ``` 71 | 72 | ### Basic functions 73 | ```kotlin 74 | //expand node 75 | fun expandNode(nodeId: String) 76 | 77 | //collapse node 78 | fun collapseNode(nodeId: String) 79 | 80 | //select a node by id. This function only updates data, not UI and will trigger [onNodeSelected] for further processing 81 | fun selectNode(nodeId: String, isSelected: Boolean) 82 | 83 | //select multiple nodes. This function only updates data, not UI 84 | fun setSelectedNode(nodes: List>, isSelected: Boolean) 85 | 86 | //unselect all nodes. This function only updates data, not UI 87 | fun clearNodesSelected() 88 | 89 | //get all selected nodes 90 | fun getSelectedNodes(): List 91 | 92 | //update layout 93 | fun requestUpdateTree() 94 | ``` 95 | 96 | ### Highly customizable with NodeState 97 | The main purpose of NodeState is to customize the UI. Set NodeState as you need and then update at onBind() (see onBind() of GapoTreeView.Listener below for more details) 98 |
You can optionally create NodeStates like: 99 | ```kotlin 100 | object DisabledNodeState: NodeState 101 | class SpecialNodeState(val label: String): NodeState 102 | 103 | //set NodeState for nodes. This function only updates data, not UI 104 | fun setNodesState(nodeIds: List, nodeState: NodeState?) 105 | 106 | //remove state for all nodes. This function only updates data, not UI 107 | fun clearNodesState() 108 | 109 | //get nodes by state 110 | fun getNodesByState(nodeState: NodeState) 111 | ``` 112 | 113 | ### GapoTreeView.Listener 114 | ```kotlin 115 | /** 116 | * @param [holder] view holder of Recyclerview Adapter 117 | * @param [position] current item position 118 | * @param [item] node item info 119 | * @param [bundle] change payload 120 | */ 121 | override fun onBind( 122 | holder: View, 123 | position: Int, 124 | item: NodeViewData, 125 | bundle: Bundle? 126 | ){ 127 | //find view by id 128 | val rbCheck = holder.findViewById(R.id.rb_check) 129 | val button = holder.findViewById