├── .gitignore ├── .idea ├── .gitignore ├── .name ├── codeStyles │ └── Project.xml ├── compiler.xml ├── jarRepositories.xml ├── material_theme_project_new.xml └── misc.xml ├── LICENSE ├── README.md ├── README_CN.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images ├── add.gif ├── base1.jpg ├── base2.jpg ├── click.gif ├── dragEdit.gif ├── fit.gif ├── fs.png ├── new.jpg └── remove.gif ├── library ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── gyso │ │ └── treeview │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── gyso │ │ │ └── treeview │ │ │ ├── GysoTreeView.java │ │ │ ├── TreeViewContainer.java │ │ │ ├── TreeViewEditor.java │ │ │ ├── adapter │ │ │ ├── DrawInfo.java │ │ │ ├── TreeViewAdapter.java │ │ │ └── TreeViewHolder.java │ │ │ ├── algorithm │ │ │ ├── force │ │ │ │ ├── FLink.java │ │ │ │ ├── FNode.java │ │ │ │ ├── Force.java │ │ │ │ ├── ForceListener.java │ │ │ │ ├── ForceView.java │ │ │ │ └── QuadTree.java │ │ │ ├── ring │ │ │ │ ├── Ring.java │ │ │ │ ├── RingForCompact.java │ │ │ │ └── RingForSimple.java │ │ │ ├── table │ │ │ │ ├── Table.java │ │ │ │ └── TableKey.java │ │ │ └── tight_table │ │ │ │ └── TightTable.java │ │ │ ├── cache_pool │ │ │ ├── HolderPool.java │ │ │ └── PointPool.java │ │ │ ├── layout │ │ │ ├── BoxDownTreeLayoutManager.java │ │ │ ├── BoxHorizonLeftAndRightLayoutManager.java │ │ │ ├── BoxLeftTreeLayoutManager.java │ │ │ ├── BoxRightTreeLayoutManager.java │ │ │ ├── BoxUpTreeLayoutManager.java │ │ │ ├── BoxVerticalUpAndDownLayoutManager.java │ │ │ ├── CompactDownTreeLayoutManager.java │ │ │ ├── CompactHorizonLeftAndRightLayoutManager.java │ │ │ ├── CompactLeftTreeLayoutManager.java │ │ │ ├── CompactRightTreeLayoutManager.java │ │ │ ├── CompactRingTreeLayoutManager.java │ │ │ ├── CompactUpTreeLayoutManager.java │ │ │ ├── CompactVerticalUpAndDownLayoutManager.java │ │ │ ├── ForceDirectedTreeLayoutManager.java │ │ │ ├── SimpleRingTreeLayoutManager.java │ │ │ ├── TableDownTreeLayoutManager.java │ │ │ ├── TableHorizonLeftAndRightLayoutManager.java │ │ │ ├── TableLeftTreeLayoutManager.java │ │ │ ├── TableRightTreeLayoutManager.java │ │ │ ├── TableUpTreeLayoutManagerTable.java │ │ │ ├── TableVerticalUpAndTableDownLayoutManager.java │ │ │ └── TreeLayoutManager.java │ │ │ ├── line │ │ │ ├── AngledLine.java │ │ │ ├── BaseLine.java │ │ │ ├── DashLine.java │ │ │ ├── SmoothLine.java │ │ │ └── StraightLine.java │ │ │ ├── listener │ │ │ ├── TreeViewControlListener.java │ │ │ ├── TreeViewItemClick.java │ │ │ ├── TreeViewItemLongClick.java │ │ │ └── TreeViewNotifier.java │ │ │ ├── model │ │ │ ├── ITraversal.java │ │ │ ├── NodeModel.java │ │ │ └── TreeModel.java │ │ │ ├── touch │ │ │ ├── DragBlock.java │ │ │ └── TouchEventHandler.java │ │ │ └── util │ │ │ ├── DensityUtils.java │ │ │ ├── Interpolators.java │ │ │ ├── TreeViewLog.java │ │ │ └── ViewBox.java │ └── res │ │ └── values │ │ └── values.xml │ └── test │ └── java │ └── com │ └── gyso │ └── treeview │ └── ExampleUnitTest.java ├── local.properties ├── sample ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── gyso │ │ └── gysotreeviewapplication │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── gyso │ │ │ └── gysotreeviewapplication │ │ │ ├── MainActivity.java │ │ │ └── base │ │ │ ├── Animal.java │ │ │ └── AnimalTreeViewAdapter.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── card_background.xml │ │ ├── drag_mode_bg.xml │ │ ├── ic_01.xml │ │ ├── ic_02.xml │ │ ├── ic_03.xml │ │ ├── ic_04.xml │ │ ├── ic_05.xml │ │ ├── ic_06.xml │ │ ├── ic_07.xml │ │ ├── ic_08.xml │ │ ├── ic_09.xml │ │ ├── ic_10.xml │ │ ├── ic_11.xml │ │ ├── ic_12.xml │ │ ├── ic_13.xml │ │ ├── ic_14.xml │ │ ├── ic_15.xml │ │ ├── ic_16.xml │ │ ├── ic_17.xml │ │ ├── ic_18.xml │ │ ├── ic_19.xml │ │ ├── ic_20.xml │ │ ├── ic_launcher_background.xml │ │ ├── ic_launcher_tree.xml │ │ └── percent_bg.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ └── node_base_layout.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── gyso │ └── gysotreeviewapplication │ └── ExampleUnitTest.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.aar 4 | *.ap_ 5 | *.aab 6 | 7 | # Files for the ART/Dalvik VM 8 | *.dex 9 | 10 | # Java class files 11 | *.class 12 | 13 | # Generated files 14 | bin/ 15 | gen/ 16 | out/ 17 | # Uncomment the following line in case you need and you don't have the release build type files in your app 18 | # release/ 19 | ./idea/ 20 | # Gradle files 21 | .gradle/ 22 | build/ 23 | 24 | # Local configuration file (sdk path, etc) 25 | local.properties 26 | 27 | # Proguard folder generated by Eclipse 28 | proguard/ 29 | 30 | # Log Files 31 | *.log 32 | 33 | # Android Studio Navigation editor temp files 34 | .navigation/ 35 | 36 | # Android Studio captures folder 37 | captures/ 38 | 39 | # IntelliJ 40 | *.iml 41 | .idea/workspace.xml 42 | .idea/tasks.xml 43 | .idea/gradle.xml 44 | .idea/assetWizardSettings.xml 45 | .idea/dictionaries 46 | .idea/libraries 47 | # Android Studio 3 in .gitignore file. 48 | .idea/caches 49 | .idea/modules.xml 50 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 51 | .idea/navEditor.xml 52 | 53 | # Keystore files 54 | # Uncomment the following lines if you do not want to check your keystore files in. 55 | #*.jks 56 | #*.keystore 57 | 58 | # External native build folder generated in Android Studio 2.2 and later 59 | .externalNativeBuild 60 | .cxx/ 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 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /workspace.xml -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | GysoTreeViewApplication -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 20 | 21 | 22 | 23 | 25 | 26 | 27 |
28 | 29 | 30 | 31 | xmlns:android 32 | 33 | ^$ 34 | 35 | 36 | 37 |
38 |
39 | 40 | 41 | 42 | xmlns:.* 43 | 44 | ^$ 45 | 46 | 47 | BY_NAME 48 | 49 |
50 |
51 | 52 | 53 | 54 | .*:id 55 | 56 | http://schemas.android.com/apk/res/android 57 | 58 | 59 | 60 |
61 |
62 | 63 | 64 | 65 | .*:name 66 | 67 | http://schemas.android.com/apk/res/android 68 | 69 | 70 | 71 |
72 |
73 | 74 | 75 | 76 | name 77 | 78 | ^$ 79 | 80 | 81 | 82 |
83 |
84 | 85 | 86 | 87 | style 88 | 89 | ^$ 90 | 91 | 92 | 93 |
94 |
95 | 96 | 97 | 98 | .* 99 | 100 | ^$ 101 | 102 | 103 | BY_NAME 104 | 105 |
106 |
107 | 108 | 109 | 110 | .* 111 | 112 | http://schemas.android.com/apk/res/android 113 | 114 | 115 | ANDROID_ATTRIBUTE_ORDER 116 | 117 |
118 |
119 | 120 | 121 | 122 | .* 123 | 124 | .* 125 | 126 | 127 | BY_NAME 128 | 129 |
130 |
131 |
132 |
133 |
134 |
-------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | -------------------------------------------------------------------------------- /.idea/material_theme_project_new.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | <<<<<<< Updated upstream 4 | 5 | ======= 6 | 7 | >>>>>>> Stashed changes 8 | 9 | 10 | 11 | 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 怪兽N 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GysoTreeView 2 | 3 | 【[中文](./README_CN.md)】【[English](./README.md)】 4 | **⭐If ok, give me a star⭐** 5 | 6 | ⭐⭐⭐⭐⭐Tree View; Mind map; Think map; tree map; 树状图;思维导图;组织机构图;层次图;树型图 7 | 8 | A custom tree view for Android, designed for easy drawing some tree nodes (e.g. thind mind and tree nodes). Includes smoothly zoom, move, limit and center fix animation support, and allows easy extension so you can add your own child node's customs view and touch event detection. 9 | ```groovy 10 | dependencies { 11 | //请直接使用lib中的代码,1.0.0这个引用库很久没有更新了 12 | implementation 'androidx.dynamicanimation:dynamicanimation:1.0.0' 13 | implementation 'io.github.guaishoun:gyso-treeview:1.0.1' 14 | } 15 | ``` 16 | 17 | ### Funtions 18 | 19 | - 🍇Smoothly zoom, move 20 | - 🍈Fix your window view port 21 | - 🍉Custom your subview for special node 22 | - 🍊Custom lines between nodes 23 | - 🍋Dynamic remove nodes 24 | - 🍌Dynamic add nodes 25 | - 🥭Drag to rebuild the nodes' relationship 26 | 27 | [Releases & downloads](https://github.com/guaishouN/android-tree-view.git) 28 | 29 | > **Base--Line, LayoutManger, Custom node view** 30 | 31 | 32 | 33 | 34 | 35 | > **Add** 36 | 37 | 38 | 39 | > **Remove** 40 | 41 | 42 | 43 | > **Drag Edit Mode** 44 | 45 | 46 | 47 | > **Click** 48 | 49 | 50 | 51 | > **Zoom and Fit Window** 52 | 53 | 54 | 55 | #### Steps for use 56 | 57 | 58 | ```xml 59 | 64 | 65 | ``` 66 | 67 | Before presentation, Animal class means you own bean class, like this: 68 | 69 | ```java 70 | public class Animal { 71 | public int headId; 72 | public String name; 73 | } 74 | ``` 75 | 76 | 77 | To use a tree view, you should do **5 steps** as follows: 78 | 79 | 80 | 81 | 1. Customs adapter by extends TreeViewAdapter. 82 | 83 | ```java 84 | public class AnimalTreeViewAdapter extends TreeViewAdapter { 85 | private DashLine dashLine = new DashLine(Color.parseColor("#F06292"),6); 86 | @Override 87 | public TreeViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, NodeModel node) { 88 | //TODO in inflate item view 89 | NodeBaseLayoutBinding nodeBinding = NodeBaseLayoutBinding.inflate(LayoutInflater.from(viewGroup.getContext()),viewGroup,false); 90 | return new TreeViewHolder<>(nodeBinding.getRoot(),node); 91 | } 92 | 93 | @Override 94 | public void onBindViewHolder(@NonNull TreeViewHolder holder) { 95 | //TODO get view and node from holder, and then control your item view 96 | View itemView = holder.getView(); 97 | NodeModel node = holder.getNode(); 98 | ... 99 | } 100 | 101 | @Override 102 | public Baseline onDrawLine(DrawInfo drawInfo) { 103 | // TODO If you return an BaseLine, line will be draw by the return one instead of TreeViewLayoutManager's 104 | // if(...){ 105 | // ... 106 | // return dashLine; 107 | // } 108 | return null; 109 | } 110 | } 111 | ``` 112 | 113 | 2. configure layout manager. Space unit is dp. You can custom you line by extends {@link com.gyso.treeview.line.BaseLine} 114 | 115 | ```java 116 | int space_50dp = 50; 117 | int space_20dp = 20; 118 | //choose a demo line or a customs line. StraightLine, PointedLine, DashLine, SmoothLine are available. 119 | Baseline line = new DashLine(Color.parseColor("#4DB6AC"),8); 120 | //choose layoout manager. VerticalTreeLayoutManager,RightTreeLayoutManager are available. 121 | TreeLayoutManager treeLayoutManager = new BoxRightTreeLayoutManager(this,space_50dp,space_20dp,line); 122 | 123 | // TODO Other stable layout managers like below: 124 | // new BoxDownTreeLayoutManager(this,space_50dp,space_20dp,line); 125 | // new BoxLeftTreeLayoutManager(this,space_50dp,space_20dp,line); 126 | // new BoxUpTreeLayoutManager(this,space_50dp,space_20dp,line); 127 | // new BoxHorizonLeftAndRightLayoutManager(this,space_50dp,space_20dp,line); 128 | // new BoxVerticalUpAndDownLayoutManager(this,space_50dp,space_20dp,line); 129 | ``` 130 | 131 | 3. setting adapter and layout manager for your tree view. 132 | 133 | ```java 134 | ... 135 | treeView = findViewById(R.id.tree_view); 136 | TreeViewAdapter adapter = new AnimlTreeViewAdapter(); 137 | treeView.setAdapter(adapter); 138 | treeView.setTreeLayoutManager(treeLayoutManager); 139 | ... 140 | ``` 141 | 142 | 4. nodes data setting 143 | ```java 144 | //Create a TreeModel by using a root node. 145 | NodeModel node0 = new NodeModel<>(new Animal(R.drawable.ic_01,"root")); 146 | TreeModel treeModel = new TreeModel<>(node0); 147 | 148 | //Other nodes. 149 | NodeModel node1 = new NodeModel<>(new Animal(R.drawable.ic_02,"sub0")); 150 | NodeModel node2 = new NodeModel<>(new Animal(R.drawable.ic_03,"sub1")); 151 | NodeModel node3 = new NodeModel<>(new Animal(R.drawable.ic_04,"sub2")); 152 | NodeModel node4 = new NodeModel<>(new Animal(R.drawable.ic_05,"sub3")); 153 | NodeModel node5 = new NodeModel<>(new Animal(R.drawable.ic_06,"sub4")); 154 | 155 | 156 | //Build the relationship between parent node and childs,like: 157 | //treeModel.add(parent, child1, child2, ...., childN); 158 | treeModel.add(node0, node1, node2); 159 | treeModel.add(node1, node3, node4); 160 | treeModel.add(node2, node5); 161 | 162 | //finally set this treeModel to the adapter 163 | adapter.setTreeModel(treeModel); 164 | ``` 165 | 166 | 5. If your want to edit your tree view, please use an editor. 167 | 168 | ```java 169 | final TreeViewEditor editor = binding.baseTreeView.getEditor(); 170 | 171 | //add nodes 172 | NodeModel a = new NodeModel<>(new Animal(R.drawable.ic_13,"add-" + atomicInteger.getAndIncrement())); 173 | NodeModel b = new NodeModel<>(new Animal(R.drawable.ic_10,"add-" + atomicInteger.getAndIncrement())); 174 | NodeModel c = new NodeModel<>(new Animal(R.drawable.ic_11,"add-" + atomicInteger.getAndIncrement())); 175 | editor.addChildNodes(targetNode,a,b,c); 176 | 177 | //remove node 178 | editor.removeNode(toRemoveNode); 179 | 180 | //view center in window viewport 181 | editor.focusMidLocation() 182 | 183 | //drag to move and build new relationship 184 | editor.requestMoveNodeByDragging(isChecked); 185 | ``` 186 | 187 | 188 | 189 | 190 | #### Notes & limitations 191 | 192 | Firstly, only vertical-down derection layout and right direction layout is available, more layout style should be code. 193 | 194 | Secondly, customing lines by extends BaseLine may be a little complicate for Android beginner, and performance issues will happen if you using carefully. 195 | 196 | Finally, this custom view will be continuely improved, if you has some innovative ideas, please tell me. Thanks for you patience. 197 | 198 | I will tell you how it works on [my CSDN blogs](https://blog.csdn.net/guaisou/article/details/116611140).Thx. 199 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | # GysoTreeView 2 | 3 | 【[中文](./README_CN.md)】【[English](./README.md)】 4 | 5 | Tree View; Mind map; Think map; tree map; 树状图;思维导图; 6 | 7 | 目前没发现比较好的Android树状图开源控件,于是决定自己写一个开源控件,对比了一下市面上关于思维导图或者树状图显示(如xMind,mind master等)的app,本文开源框架并不逊色。 8 | 9 | ### 特点 10 | 11 | - 丝滑的跟随手指放缩,拖动,及惯性滑动 12 | 13 | - 自动动画回归屏幕中心 14 | 15 | - 支持子节点复杂布局自定义,并且节点布局点击事件与滑动不冲突 16 | 17 | - 节点间的连接线自定义 18 | 19 | - 可删除动态节点 20 | 21 | - 可动态添加节点 22 | 23 | - 支持拖动调整节点关系 24 | 25 | - 增删、移动结构添加动画效果 26 | 27 | 28 | 29 | [github控件连接](https://github.com/guaishouN/android-tree-view.git) 30 | 31 | > **基础--连接线, 布局, 自定义节点View** 32 | 33 | 34 | 35 | 36 | 37 | > **添加** 38 | 39 | 40 | 41 | > **删除** 42 | 43 | 44 | 45 | > **拖动节点编辑书树状图结构** 46 | 47 | 48 | 49 | > **放缩拖动不影响点击** 50 | 51 | 52 | 53 | > **放缩及适应窗口** 54 | 55 | 56 | 57 | #### 使用步骤: 58 | 59 | 添加依赖 60 | ```groovy 61 | dependencies { 62 | //请直接使用lib中的代码,1.0.0这个引用库很久没有更新了 63 | implementation 'androidx.dynamicanimation:dynamicanimation:1.0.0' 64 | implementation 'io.github.guaishoun:gyso-treeview:1.0.1' 65 | } 66 | ``` 67 | 68 | ```xml 69 | 74 | 75 | ``` 76 | 下面说明中Animal类是仅仅用于举例的bean 77 | 78 | ```java 79 | public class Animal { 80 | public int headId; 81 | public String name; 82 | } 83 | ``` 84 | 85 | 按照以下四个步骤使用该开源控件 86 | 87 | 1 通过继承 TreeViewAdapter实现节点数据与节点视图的绑定 88 | 89 | ```java 90 | public class AnimalTreeViewAdapter extends TreeViewAdapter { 91 | private DashLine dashLine = new DashLine(Color.parseColor("#F06292"),6); 92 | @Override 93 | public TreeViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, NodeModel node) { 94 | //TODO in inflate item view 95 | NodeBaseLayoutBinding nodeBinding = NodeBaseLayoutBinding.inflate(LayoutInflater.from(viewGroup.getContext()),viewGroup,false); 96 | return new TreeViewHolder<>(nodeBinding.getRoot(),node); 97 | } 98 | 99 | @Override 100 | public void onBindViewHolder(@NonNull TreeViewHolder holder) { 101 | //TODO get view and node from holder, and then control your item view 102 | View itemView = holder.getView(); 103 | NodeModel node = holder.getNode(); 104 | ... 105 | } 106 | 107 | @Override 108 | public Baseline onDrawLine(DrawInfo drawInfo) { 109 | // TODO If you return an BaseLine, line will be draw by the return one instead of TreeViewLayoutManager's 110 | // if(...){ 111 | // ... 112 | // return dashLine; 113 | // } 114 | return null; 115 | } 116 | } 117 | ``` 118 | 119 | 2 配置LayoutManager。主要设置布局风格(向右展开或垂直向下展开)、父节点与子节点的间隙、子节点间的间隙、节点间的连线(已经实现了直线、光滑曲线、虚线、根状线,也可通过BaseLine实现你自己的连线) 120 | 121 | ```java 122 | int space_50dp = 50; 123 | int space_20dp = 20; 124 | //choose a demo line or a customs line. StraightLine, PointedLine, DashLine, SmoothLine are available. 125 | Baseline line = new DashLine(Color.parseColor("#4DB6AC"),8); 126 | //choose layoout manager. VerticalTreeLayoutManager,RightTreeLayoutManager are available. 127 | TreeLayoutManager treeLayoutManager = new BoxRightTreeLayoutManager(this,space_50dp,space_20dp,line); 128 | 129 | // TODO Box[XXX]LayoutManager 系列的布局是稳定的,其他系列的布局还在优化中: 130 | // TODO Other stable layout managers like below: 131 | // new BoxDownTreeLayoutManager(this,space_50dp,space_20dp,line); 132 | // new BoxLeftTreeLayoutManager(this,space_50dp,space_20dp,line); 133 | // new BoxUpTreeLayoutManager(this,space_50dp,space_20dp,line); 134 | // new BoxHorizonLeftAndRightLayoutManager(this,space_50dp,space_20dp,line); 135 | // new BoxVerticalUpAndDownLayoutManager(this,space_50dp,space_20dp,line); 136 | ``` 137 | 138 | 3 把Adapter和LayoutManager设置到你的树状图 139 | 140 | ```java 141 | ... 142 | treeView = findViewById(R.id.tree_view); 143 | TreeViewAdapter adapter = new AnimlTreeViewAdapter(); 144 | treeView.setAdapter(adapter); 145 | treeView.setTreeLayoutManager(treeLayoutManager); 146 | ... 147 | ``` 148 | 149 | 4 设置节点数据 150 | 151 | ```java 152 | //Create a TreeModel by using a root node. 153 | NodeModel node0 = new NodeModel<>(new Animal(R.drawable.ic_01,"root")); 154 | TreeModel treeModel = new TreeModel<>(root); 155 | 156 | //Other nodes. 157 | NodeModel node1 = new NodeModel<>(new Animal(R.drawable.ic_02,"sub0")); 158 | NodeModel node2 = new NodeModel<>(new Animal(R.drawable.ic_03,"sub1")); 159 | NodeModel node3 = new NodeModel<>(new Animal(R.drawable.ic_04,"sub2")); 160 | NodeModel node4 = new NodeModel<>(new Animal(R.drawable.ic_05,"sub3")); 161 | NodeModel node5 = new NodeModel<>(new Animal(R.drawable.ic_06,"sub4")); 162 | 163 | 164 | //Build the relationship between parent node and childs,like: 165 | //treeModel.add(parent, child1, child2, ...., childN); 166 | treeModel.add(node0, node1, node2); 167 | treeModel.add(node1, node3, node4); 168 | treeModel.add(node2, node5); 169 | 170 | //finally set this treeModel to the adapter 171 | adapter.setTreeModel(treeModel); 172 | ``` 173 | 174 | 5 如果你想编辑这个树状图 175 | 176 | ```java 177 | final TreeViewEditor editor = binding.baseTreeView.getEditor(); 178 | 179 | //add nodes 180 | NodeModel a = new NodeModel<>(new Animal(R.drawable.ic_13,"add-" + atomicInteger.getAndIncrement())); 181 | NodeModel b = new NodeModel<>(new Animal(R.drawable.ic_10,"add-" + atomicInteger.getAndIncrement())); 182 | NodeModel c = new NodeModel<>(new Animal(R.drawable.ic_11,"add-" + atomicInteger.getAndIncrement())); 183 | editor.addChildNodes(targetNode,a,b,c); 184 | 185 | //remove node 186 | editor.removeNode(toRemoveNode); 187 | 188 | //view center in window viewport 189 | editor.focusMidLocation() 190 | 191 | //drag to move and build new relationship 192 | editor.requestMoveNodeByDragging(isChecked); 193 | ``` 194 | 195 | 196 | 197 | #### 写在最后 198 | 199 | 目前只写了垂直向下布局及向右展开布局,其他的还得花时间。对于线的实现只是实现了几种,也还得丰富。在自定义自己的BaseLine线时,Android初学者注意因为画线动作是在View的onDraw(canvas)中的,所以在里面不要new对象,不要任何耗时操作。 200 | 201 | 这个项目如果有人用就会持续更新下去。喜欢点个赞,谢谢。 202 | 203 | 关于实现,[我的CSDN](https://blog.csdn.net/guaisou/article/details/116611140)会逐渐更新。 204 | 205 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | buildscript { 3 | ext.kotlin_version = '1.4.10' 4 | repositories { 5 | google() 6 | mavenCentral() 7 | } 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:7.0.1' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | 12 | // NOTE: Do not place your application dependencies here; they belong 13 | // in the individual module build.gradle files 14 | } 15 | } 16 | 17 | allprojects { 18 | repositories { 19 | google() 20 | mavenCentral() 21 | } 22 | } 23 | 24 | task clean(type: Delete) { 25 | delete rootProject.buildDir 26 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guaishouN/android-thinkmap-treeview/285c2f3e2163abbe5644b080286b8030ac06409a/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Feb 14 14:53:19 CST 2025 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-7.2-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /images/add.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guaishouN/android-thinkmap-treeview/285c2f3e2163abbe5644b080286b8030ac06409a/images/add.gif -------------------------------------------------------------------------------- /images/base1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guaishouN/android-thinkmap-treeview/285c2f3e2163abbe5644b080286b8030ac06409a/images/base1.jpg -------------------------------------------------------------------------------- /images/base2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guaishouN/android-thinkmap-treeview/285c2f3e2163abbe5644b080286b8030ac06409a/images/base2.jpg -------------------------------------------------------------------------------- /images/click.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guaishouN/android-thinkmap-treeview/285c2f3e2163abbe5644b080286b8030ac06409a/images/click.gif -------------------------------------------------------------------------------- /images/dragEdit.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guaishouN/android-thinkmap-treeview/285c2f3e2163abbe5644b080286b8030ac06409a/images/dragEdit.gif -------------------------------------------------------------------------------- /images/fit.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guaishouN/android-thinkmap-treeview/285c2f3e2163abbe5644b080286b8030ac06409a/images/fit.gif -------------------------------------------------------------------------------- /images/fs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guaishouN/android-thinkmap-treeview/285c2f3e2163abbe5644b080286b8030ac06409a/images/fs.png -------------------------------------------------------------------------------- /images/new.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guaishouN/android-thinkmap-treeview/285c2f3e2163abbe5644b080286b8030ac06409a/images/new.jpg -------------------------------------------------------------------------------- /images/remove.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guaishouN/android-thinkmap-treeview/285c2f3e2163abbe5644b080286b8030ac06409a/images/remove.gif -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | //apply plugin: 'maven-publish' 3 | //apply plugin: 'signing' 4 | 5 | version = '1.0.1' 6 | android { 7 | compileSdkVersion 29 8 | buildToolsVersion "29.0.3" 9 | 10 | defaultConfig { 11 | minSdkVersion 21 12 | targetSdkVersion 30 13 | versionCode 1 14 | versionName "1.0" 15 | 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | consumerProguardFiles "consumer-rules.pro" 18 | } 19 | 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 24 | buildConfigField "boolean", "isDebug", "false" 25 | } 26 | debug { 27 | buildConfigField "boolean", "isDebug", "false" 28 | } 29 | } 30 | compileOptions { 31 | sourceCompatibility JavaVersion.VERSION_1_8 32 | targetCompatibility JavaVersion.VERSION_1_8 33 | } 34 | } 35 | 36 | dependencies { 37 | implementation fileTree(dir: "libs", include: ["*.jar"]) 38 | implementation 'androidx.appcompat:appcompat:1.2.0' 39 | testImplementation 'junit:junit:4.12' 40 | androidTestImplementation 'androidx.test.ext:junit:1.1.2' 41 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' 42 | implementation 'androidx.dynamicanimation:dynamicanimation:1.0.0' 43 | } 44 | 45 | //task androidSourcesJar(type: Jar) { 46 | // archiveClassifier.set("sources") 47 | // from android.sourceSets.main.java.source 48 | // exclude "**/R.class" 49 | // exclude "**/BuildConfig.class" 50 | //} 51 | 52 | //publishing { 53 | // publications { 54 | // release(MavenPublication) { 55 | // // group id,发布后引用的依赖的 group id 56 | // groupId 'io.github.guaishoun' 57 | // // 发布后引用的依赖的 artifact id 58 | // artifactId 'gyso-treeview' 59 | // // 发布的版本 60 | // version version 61 | // // 发布的 arr 的文件和源码文件 62 | // artifact("$buildDir/outputs/aar/${project.getName()}-release.aar") 63 | // artifact androidSourcesJar 64 | // pom { 65 | // // 构件名称,可以自定义 66 | // name = 'gyso-treeview' 67 | // // 构件描述 68 | // description = 'A custom tree view for Android, designed for easy drawing some tree nodes (e.g. think mind and tree nodes). Includes smoothly zoom, move, limit and center fix animation support, and allows easy extension so you can add your own child node\'s customs view and touch event detection.' 69 | // // 构件主页 70 | // url = 'https://github.com/guaishouN/android-thinkmap-treeview' 71 | // // 许可证名称和地址 72 | // licenses { 73 | // license { 74 | // name = 'MIT License' 75 | // url = 'http://www.apache.org/licenses/LICENSE-2.0.txt' 76 | // } 77 | // } 78 | // // 开发者信息 79 | // developers { 80 | // developer { 81 | // name = 'GuaishouN' 82 | // email = '674149099@qq.com' 83 | // } 84 | // } 85 | // // 版本控制仓库地址 86 | // scm { 87 | // url = 'https://github.com/guaishouN/android-thinkmap-treeview' 88 | // connection = 'scm:git@github.com:guaishouN/android-thinkmap-treeview.git' 89 | // developerConnection = 'scm:git@github.com:guaishouN/android-thinkmap-treeview.git' 90 | // } 91 | // } 92 | // } 93 | // } 94 | // repositories { 95 | // maven { 96 | // // 发布的位置,这里根据发布的版本区分了 SNAPSHOT 和最终版本两种情况 97 | // def releasesRepoUrl = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" 98 | // def snapshotsRepoUrl = "https://s01.oss.sonatype.org/content/repositories/snapshots/" 99 | // url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl 100 | // credentials { 101 | // // 这里就是之前在 issues.sonatype.org 注册的账号 102 | // username NEXUS_USERNAME 103 | // password NEXUS_PASSWORD 104 | // } 105 | // } 106 | // } 107 | //} 108 | // 109 | //signing { 110 | // sign publishing.publications 111 | //} -------------------------------------------------------------------------------- /library/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guaishouN/android-thinkmap-treeview/285c2f3e2163abbe5644b080286b8030ac06409a/library/consumer-rules.pro -------------------------------------------------------------------------------- /library/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /library/src/androidTest/java/com/gyso/treeview/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.gyso.treeview; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | assertEquals("com.gyso.treeview.test", appContext.getPackageName()); 25 | } 26 | } -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /library/src/main/java/com/gyso/treeview/GysoTreeView.java: -------------------------------------------------------------------------------- 1 | package com.gyso.treeview; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapFactory; 6 | import android.graphics.BlurMaskFilter; 7 | import android.graphics.Canvas; 8 | import android.graphics.Color; 9 | import android.graphics.Paint; 10 | import android.graphics.RenderNode; 11 | import android.renderscript.Allocation; 12 | import android.renderscript.Element; 13 | import android.renderscript.RenderScript; 14 | import android.renderscript.ScriptIntrinsicBlur; 15 | import android.util.AttributeSet; 16 | import android.util.Log; 17 | import android.view.MotionEvent; 18 | import android.view.View; 19 | import android.widget.FrameLayout; 20 | import androidx.annotation.NonNull; 21 | import androidx.annotation.Nullable; 22 | import androidx.core.graphics.BitmapCompat; 23 | 24 | import com.gyso.treeview.adapter.TreeViewAdapter; 25 | import com.gyso.treeview.cache_pool.PointPool; 26 | import com.gyso.treeview.layout.TreeLayoutManager; 27 | import com.gyso.treeview.listener.TreeViewControlListener; 28 | import com.gyso.treeview.touch.TouchEventHandler; 29 | import com.gyso.treeview.util.TreeViewLog; 30 | 31 | 32 | /** 33 | * @Author: 怪兽N 34 | * @Time: 2021/4/29 14:09 35 | * @Email: 674149099@qq.com 36 | * @WeChat: guaishouN 37 | * @Describe: 38 | * the main tree view. 39 | */ 40 | public class GysoTreeView extends FrameLayout { 41 | public static final String TAG = GysoTreeView.class.getSimpleName(); 42 | private final TreeViewContainer treeViewContainer; 43 | private final TouchEventHandler treeViewGestureHandler; 44 | private boolean disallowIntercept = false; 45 | 46 | public GysoTreeView(@NonNull Context context) { 47 | this(context, null,0); 48 | } 49 | 50 | public GysoTreeView(@NonNull Context context, @Nullable AttributeSet attrs) { 51 | this(context, attrs,0); 52 | } 53 | //private Paint paint = new Paint(); 54 | 55 | public GysoTreeView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 56 | super(context, attrs, defStyleAttr); 57 | LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT); 58 | setClipChildren(false); 59 | setClipToPadding(false); 60 | treeViewContainer = new TreeViewContainer(getContext()); 61 | treeViewContainer.setLayoutParams(layoutParams); 62 | addView(treeViewContainer); 63 | treeViewGestureHandler = new TouchEventHandler(getContext(), treeViewContainer); 64 | 65 | //Note: do not set setKeepInViewport(true), there still has bug 66 | treeViewGestureHandler.setKeepInViewport(false); 67 | 68 | //set animate default 69 | treeViewContainer.setAnimateAdd(true); 70 | treeViewContainer.setAnimateRemove(true); 71 | treeViewContainer.setAnimateMove(true); 72 | // paint.setColor(Color.RED); 73 | //paint.setStyle(Paint.Style.FILL); ; 74 | //setLayerType(LAYER_TYPE_HARDWARE,paint); 75 | } 76 | 77 | @Override 78 | public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { 79 | super.requestDisallowInterceptTouchEvent(disallowIntercept); 80 | this.disallowIntercept = disallowIntercept; 81 | TreeViewLog.e(TAG, "requestDisallowInterceptTouchEvent:"+disallowIntercept); 82 | } 83 | 84 | @Override 85 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 86 | super.onLayout(changed, left, top, right, bottom); 87 | } 88 | 89 | @Override 90 | public boolean onInterceptTouchEvent(MotionEvent event) { 91 | TreeViewLog.e(TAG, "onInterceptTouchEvent: "+MotionEvent.actionToString(event.getAction())); 92 | return (!disallowIntercept && treeViewGestureHandler.detectInterceptTouchEvent(event)) || super.onInterceptTouchEvent(event); 93 | } 94 | 95 | @Override 96 | public boolean onTouchEvent(MotionEvent event) { 97 | TreeViewLog.e(TAG, "onTouchEvent: "+MotionEvent.actionToString(event.getAction())); 98 | return !disallowIntercept && treeViewGestureHandler.onTouchEvent(event); 99 | } 100 | //Bitmap bitmap = null; 101 | @Override 102 | public void draw(Canvas canvas) { 103 | super.draw(canvas); 104 | } 105 | @Override 106 | protected void onDraw(Canvas canvas) { 107 | super.onDraw(canvas); 108 | } 109 | 110 | @Override 111 | protected void dispatchDraw(Canvas canvas) { 112 | //canvas.drawRect(100,100,500,500,paint); 113 | //bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888); 114 | //canvas.saveLayer(0,0,getWidth(),getHeight(),paint); 115 | super.dispatchDraw(canvas); 116 | //doBlur(bitmap); 117 | //canvas.drawBitmap(bitmap,0,0,new Paint()); 118 | //canvas.restore(); 119 | } 120 | 121 | private void doBlur(Bitmap bitmap){ 122 | Bitmap newmap; 123 | for (int i = 0; i < 10; i++) { 124 | newmap = goBlur(bitmap, 20f, getContext()); 125 | bitmap = newmap; 126 | } 127 | } 128 | 129 | public static Bitmap goBlur(Bitmap bitmap,float radius,Context mContext) { 130 | Log.d(TAG, "goBlur: "); 131 | int width = bitmap.getWidth(); 132 | int height = bitmap.getHeight(); 133 | Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 134 | 135 | //Instantiate a new Renderscript 136 | RenderScript rs = RenderScript.create(mContext); 137 | 138 | //Create an Intrinsic Blur Script using the Renderscript 139 | ScriptIntrinsicBlur blurScript = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs)); 140 | 141 | //Create the Allocations (in/out) with the Renderscript and the in/out bitmaps 142 | Allocation allIn = Allocation.createFromBitmap(rs, bitmap); 143 | Allocation allOut = Allocation.createFromBitmap(rs, result); 144 | 145 | //Set the radius of the blur: 0 < radius <= 25 146 | blurScript.setRadius(radius); 147 | 148 | //Perform the Renderscript 149 | blurScript.setInput(allIn); 150 | blurScript.forEach(allOut); 151 | 152 | //Copy the final bitmap created by the out Allocation to the outBitmap 153 | allOut.copyTo(result); 154 | 155 | //After finishing everything, we destroy the Renderscript. 156 | rs.destroy(); 157 | 158 | return result; 159 | } 160 | 161 | @Override 162 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 163 | treeViewGestureHandler.setViewport(w,h); 164 | } 165 | 166 | public void setAdapter(TreeViewAdapter adapter) { 167 | treeViewContainer.setAdapter(adapter); 168 | } 169 | 170 | public TreeViewAdapter getAdapter() { 171 | return treeViewContainer.getAdapter(); 172 | } 173 | 174 | public void setTreeLayoutManager(TreeLayoutManager TreeLayoutManager) { 175 | treeViewContainer.setTreeLayoutManager(TreeLayoutManager); 176 | } 177 | 178 | public TreeViewEditor getEditor(){ 179 | return new TreeViewEditor(treeViewContainer); 180 | } 181 | 182 | public void setTreeViewControlListener(TreeViewControlListener listener){ 183 | treeViewGestureHandler.setControlListener(listener); 184 | treeViewContainer.setControlListener(listener); 185 | } 186 | 187 | @Override 188 | protected void onDetachedFromWindow() { 189 | super.onDetachedFromWindow(); 190 | PointPool.freeAll(); 191 | TreeViewLog.d(TAG, "onDetachedFromWindow: "); 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /library/src/main/java/com/gyso/treeview/TreeViewEditor.java: -------------------------------------------------------------------------------- 1 | package com.gyso.treeview; 2 | 3 | import android.view.View; 4 | 5 | import com.gyso.treeview.adapter.TreeViewAdapter; 6 | import com.gyso.treeview.model.NodeModel; 7 | 8 | import java.io.File; 9 | import java.lang.ref.WeakReference; 10 | import java.util.List; 11 | 12 | /** 13 | * @Author: 怪兽N 14 | * @Time: 2021/6/9 15:31 15 | * @Email: 674149099@qq.com 16 | * @WeChat: guaishouN 17 | * @Describe: Android developer 18 | * 19 | * helper you edit your tree view. 20 | * Move node by dragging and remove node is support now. 21 | * 22 | * Note: 23 | * 1 An adapter must be set to GysoTreeView before you get an editor 24 | * 2 If you has set a new adapter, you should get an new editor 25 | */ 26 | public class TreeViewEditor { 27 | private final WeakReference> adapterWeakReference; 28 | private final WeakReference containerWeakReference; 29 | protected TreeViewEditor(TreeViewContainer container){ 30 | this.containerWeakReference = new WeakReference<>(container); 31 | this.adapterWeakReference = new WeakReference<>(container.getAdapter()); 32 | } 33 | 34 | private TreeViewContainer getContainer(){ 35 | return containerWeakReference.get(); 36 | } 37 | 38 | private TreeViewAdapter getAdapter(){ 39 | return adapterWeakReference.get(); 40 | } 41 | /** 42 | * let add node in window viewport 43 | */ 44 | public void focusMidLocation(){ 45 | TreeViewContainer container = getContainer(); 46 | if (container!=null)container.focusMidLocation(); 47 | } 48 | 49 | public View anchorNodeOnMidViewport(NodeModel targetNode){ 50 | //TODO move targetNode at center of viewport 51 | return null; 52 | } 53 | 54 | /** 55 | * change layout algorithm 56 | */ 57 | public void changeLayoutAlgorithm(){ 58 | 59 | } 60 | 61 | /** 62 | * get current relation ship, you can use when you change 63 | * @param traverse relations node 64 | */ 65 | public void getCurrentRelationships(TraverseRelationshipCallback traverse){ 66 | 67 | } 68 | 69 | /** 70 | * get current relation ship, you can use when you change 71 | * @param traverse relations node 72 | * @param jsonStringFilePath jsonStringFilePath 73 | */ 74 | public boolean save(String jsonStringFilePath, TraverseRelationshipCallback traverse){ 75 | return false; 76 | } 77 | 78 | public boolean save(File jsonStringFile, TraverseRelationshipCallback traverse){ 79 | return false; 80 | } 81 | 82 | /** 83 | * load data from json String 84 | * @param jsonString string 85 | */ 86 | public void load(String jsonString){ 87 | 88 | } 89 | 90 | /** 91 | * load data from file 92 | * @param jsonStringFile file 93 | */ 94 | public void load(File jsonStringFile){ 95 | 96 | } 97 | 98 | /** 99 | * expand by node 100 | * @param targetParentNode targetParentNode 101 | */ 102 | public void collapse(NodeModel targetParentNode){ 103 | 104 | } 105 | 106 | /** 107 | * expand by node 108 | * @param targetParentNode targetParentNode 109 | */ 110 | public void expand(NodeModel targetParentNode){ 111 | 112 | } 113 | 114 | /** 115 | * focus on node 116 | * @param targetNode targetNode 117 | */ 118 | public void focusOn(NodeModel targetNode){ 119 | 120 | } 121 | 122 | /** 123 | * un focus on node 124 | * @param targetNode targetNode 125 | */ 126 | public void unFocusOn(NodeModel targetNode){ 127 | 128 | } 129 | 130 | /** 131 | * for support scroll view 132 | */ 133 | public void lockDragDirection(){ 134 | 135 | } 136 | 137 | /** 138 | * save current state 139 | */ 140 | public void saveLastSate(){ 141 | 142 | } 143 | 144 | /** 145 | * keep last location and 146 | */ 147 | public void restoreLastSate(){ 148 | 149 | } 150 | 151 | /** 152 | * add on select listener 153 | */ 154 | public void addOnSelectedListener(){ 155 | 156 | } 157 | 158 | /** 159 | * default: auto restructure by dragging; 160 | * totally free drag; 161 | * @param status status 162 | */ 163 | public void setEditStatus(int status){ 164 | 165 | } 166 | 167 | /** 168 | * before you edit, requestMoveNodeByDragging(true), so than you can drag to move the node 169 | * @param wantEdit true for edit mode 170 | */ 171 | public void requestMoveNodeByDragging(boolean wantEdit){ 172 | TreeViewContainer container = getContainer(); 173 | if (container!=null)container.requestMoveNodeByDragging(wantEdit); 174 | } 175 | 176 | /** 177 | * add child nodes 178 | * @param parent parent node should has been in tree model 179 | * @param childNodes new nodes that will be add to tree model 180 | */ 181 | public void addChildNodes(NodeModel parent, NodeModel... childNodes) { 182 | TreeViewContainer container = getContainer(); 183 | if(container!=null){ 184 | container.onAddNodes(parent,childNodes); 185 | } 186 | } 187 | 188 | /** 189 | * remove node 190 | * @param nodeToRemove node to remove 191 | */ 192 | public void removeNode(NodeModel nodeToRemove){ 193 | TreeViewContainer container = getContainer(); 194 | if(container!=null){ 195 | container.onRemoveNode(nodeToRemove); 196 | } 197 | } 198 | 199 | /** 200 | * remove children nodes by parent node 201 | * @param parentNode parent node to remove children 202 | */ 203 | public void removeNodeChildren(NodeModel parentNode){ 204 | TreeViewContainer container = getContainer(); 205 | if(container!=null){ 206 | container.onRemoveChildNodes(parentNode); 207 | } 208 | } 209 | 210 | public interface TraverseRelationshipCallback{ 211 | void callback(T root, T parent , T child); 212 | default void callbackView(View rootView, View parentView , View childView){}; 213 | } 214 | 215 | public interface OnNodeSelectedCallback{ 216 | void callback(T clickNode, List selectedList); 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /library/src/main/java/com/gyso/treeview/adapter/DrawInfo.java: -------------------------------------------------------------------------------- 1 | package com.gyso.treeview.adapter; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Paint; 5 | import android.graphics.Path; 6 | import android.graphics.Point; 7 | import android.graphics.PointF; 8 | 9 | /** 10 | * @Author: 怪兽N 11 | * @Time: 2021/5/7 21:15 12 | * @Email: 674149099@qq.com 13 | * @WeChat: guaishouN 14 | * @Describe: 15 | * In order to draw a custom line, there are too many draw info should be passed. 16 | * So, this class is estimated to package the draw element that need to draw a colorful line between two nodes. 17 | */ 18 | public class DrawInfo { 19 | /** 20 | * canvas 21 | */ 22 | private Canvas canvas; 23 | /** 24 | * node from view 25 | */ 26 | private TreeViewHolder fromHolder; 27 | /** 28 | * node to view 29 | */ 30 | private TreeViewHolder toHolder; 31 | /** 32 | * paint, before use you should reset 33 | */ 34 | private Paint paint; 35 | /** 36 | * path, before use you should reset 37 | */ 38 | private Path path; 39 | /** 40 | * space between Peer and Peer, means child and child 41 | */ 42 | private int spacePeerToPeer; 43 | /** 44 | * space between parent and child 45 | */ 46 | private int spaceParentToChild; 47 | /** 48 | * viewport width 49 | */ 50 | private int windowWidth; 51 | /** 52 | * viewport height 53 | */ 54 | private int windowHeight; 55 | 56 | public Canvas getCanvas() { 57 | return canvas; 58 | } 59 | 60 | public void setCanvas(Canvas canvas) { 61 | this.canvas = canvas; 62 | } 63 | 64 | public TreeViewHolder getFromHolder() { 65 | return fromHolder; 66 | } 67 | 68 | public void setFromHolder(TreeViewHolder fromHolder) { 69 | this.fromHolder = fromHolder; 70 | } 71 | 72 | public TreeViewHolder getToHolder() { 73 | return toHolder; 74 | } 75 | 76 | public void setToHolder(TreeViewHolder toHolder) { 77 | this.toHolder = toHolder; 78 | } 79 | 80 | public Paint getPaint() { 81 | return paint; 82 | } 83 | 84 | public void setPaint(Paint paint) { 85 | this.paint = paint; 86 | } 87 | 88 | public Path getPath() { 89 | return path; 90 | } 91 | 92 | public void setPath(Path path) { 93 | this.path = path; 94 | } 95 | 96 | public void setSpace(int spacePeerToPeer,int spaceParentToChild) { 97 | this.spacePeerToPeer = spacePeerToPeer; 98 | this.spaceParentToChild = spaceParentToChild; 99 | } 100 | 101 | public int getSpacePeerToPeer() { 102 | return spacePeerToPeer; 103 | } 104 | 105 | public int getSpaceParentToChild() { 106 | return spaceParentToChild; 107 | } 108 | 109 | public int getWindowWidth() { 110 | return windowWidth; 111 | } 112 | 113 | public void setWindowWidth(int windowWidth) { 114 | this.windowWidth = windowWidth; 115 | } 116 | 117 | public int getWindowHeight() { 118 | return windowHeight; 119 | } 120 | 121 | public void setWindowHeight(int windowHeight) { 122 | this.windowHeight = windowHeight; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /library/src/main/java/com/gyso/treeview/adapter/TreeViewAdapter.java: -------------------------------------------------------------------------------- 1 | package com.gyso.treeview.adapter; 2 | 3 | import android.view.ViewGroup; 4 | 5 | import androidx.annotation.NonNull; 6 | 7 | import com.gyso.treeview.line.BaseLine; 8 | import com.gyso.treeview.listener.TreeViewNotifier; 9 | import com.gyso.treeview.model.NodeModel; 10 | import com.gyso.treeview.model.TreeModel; 11 | 12 | /** 13 | * @Author: 怪兽N 14 | * @Time: 2021/4/23 15:19 15 | * @Email: 674149099@qq.com 16 | * @WeChat: guaishouN 17 | * @Describe: 18 | * The view adapter for the {@link com.gyso.treeview.TreeViewContainer} 19 | */ 20 | public abstract class TreeViewAdapter { 21 | private TreeViewNotifier notifier; 22 | private TreeModel treeModel; 23 | 24 | public void setTreeModel(TreeModel treeModel) { 25 | this.treeModel = treeModel; 26 | notifyDataSetChange(); 27 | } 28 | 29 | /** 30 | * Get tree model 31 | * @return tree model 32 | */ 33 | public TreeModel getTreeModel(){ 34 | return treeModel; 35 | } 36 | 37 | /** 38 | * For create view holder by your self 39 | * @param viewGroup parent 40 | * @param model node 41 | * @return holder 42 | */ 43 | public abstract TreeViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, NodeModel model); 44 | 45 | /** 46 | * when bind the holder, set up you view 47 | * @param holder holder 48 | */ 49 | public abstract void onBindViewHolder(@NonNull TreeViewHolder holder); 50 | 51 | /** 52 | * Draw line between node and node by you decision. 53 | * If you return an BaseLine, line will be draw by the return one instead of TreeViewLayoutManager's. 54 | * @param drawInfo provides all you need to draw you line 55 | * @return the line draw you want to use for different nodes 56 | */ 57 | public abstract BaseLine onDrawLine(DrawInfo drawInfo); 58 | 59 | /** 60 | * for recycling holder, exactly for recycling views 61 | * @param node 62 | * @return 63 | */ 64 | public int getHolderType(NodeModel node){ 65 | return 0; 66 | } 67 | 68 | public void setNotifier(TreeViewNotifier notifier){ 69 | this.notifier = notifier; 70 | } 71 | 72 | public void notifyDataSetChange(){ 73 | if(notifier!=null){ 74 | notifier.onDataSetChange(); 75 | } 76 | } 77 | 78 | public void notifyItemViewChange(NodeModel node){ 79 | if(notifier!=null){ 80 | notifier.onItemViewChange(node); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /library/src/main/java/com/gyso/treeview/adapter/TreeViewHolder.java: -------------------------------------------------------------------------------- 1 | package com.gyso.treeview.adapter; 2 | 3 | import android.view.View; 4 | 5 | import androidx.annotation.NonNull; 6 | 7 | import com.gyso.treeview.layout.TreeLayoutManager; 8 | import com.gyso.treeview.model.NodeModel; 9 | 10 | /** 11 | * @Author: 怪兽N 12 | * @Time: 2021/4/23 15:20 13 | * @Email: 674149099@qq.com 14 | * @WeChat: guaishouN 15 | * @Describe: 16 | * View holder 17 | */ 18 | public class TreeViewHolder { 19 | private int holderLayoutType = TreeLayoutManager.LAYOUT_TYPE_NONE; 20 | private View view; 21 | private NodeModel node; 22 | public TreeViewHolder(View view, @NonNull NodeModel node) { 23 | this.view = view; 24 | this.node = node; 25 | } 26 | 27 | public NodeModel getNode() { 28 | return node; 29 | } 30 | 31 | public View getView() { 32 | return view; 33 | } 34 | 35 | public void setView(View view) { 36 | this.view = view; 37 | } 38 | 39 | public void setNode(NodeModel node) { 40 | this.node = (NodeModel)node; 41 | } 42 | 43 | public int getHolderLayoutType() { 44 | return holderLayoutType; 45 | } 46 | 47 | public void setHolderLayoutType(int holderLayoutType) { 48 | this.holderLayoutType = holderLayoutType; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /library/src/main/java/com/gyso/treeview/algorithm/force/FLink.java: -------------------------------------------------------------------------------- 1 | package com.gyso.treeview.algorithm.force; 2 | 3 | /** 4 | * Created by Z.Pan on 2016/10/9. 5 | */ 6 | 7 | public class FLink { 8 | 9 | FNode source; 10 | FNode target; 11 | private String text; 12 | 13 | public FLink(FNode source, FNode target) { 14 | this.source = source; 15 | this.target = target; 16 | } 17 | 18 | public String getText() { 19 | return text; 20 | } 21 | 22 | public void setText(String text) { 23 | this.text = text; 24 | } 25 | 26 | double getNodeDistance() { 27 | float dx = source.x - target.x; 28 | float dy = source.y - target.y; 29 | return Math.sqrt(dx * dx + dy * dy); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /library/src/main/java/com/gyso/treeview/algorithm/force/FNode.java: -------------------------------------------------------------------------------- 1 | package com.gyso.treeview.algorithm.force; 2 | 3 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE; 4 | import static java.lang.annotation.RetentionPolicy.SOURCE; 5 | 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.Target; 8 | 9 | /** 10 | * 力导向图中显示的节点。 11 | * 12 | * Created by Z.Pan on 2016/10/8. 13 | */ 14 | 15 | public class FNode { 16 | 17 | /** 根节点级别 */ 18 | public static final int ROOT_NODE_LEVEL = 0; 19 | 20 | static final short DRAG_START = 2; 21 | static final short DRAG = 4; 22 | static final short DRAG_END = 6; 23 | 24 | private String text; // 节点显示的内容 25 | private Object obj; // 用来携带其他数据,如:该节点对应的数据实体 Bean,或数据库中的 _id 26 | private int level; // 级别 27 | 28 | public float x, y; // 当前坐标 29 | float px, py; // 前一个状态的坐标 30 | int weight; // 根据子节点自动计算,weight 越大,该节点越不容易被拖动 31 | 32 | private float radius = 50f; // 节点半径 33 | private short state; // 节点状态,该状态决定了是否处于稳定状态 34 | 35 | public FNode(String text) { 36 | this(text, 50f, ROOT_NODE_LEVEL); 37 | } 38 | 39 | public FNode(String text, float radius, int level) { 40 | this.text = text; 41 | this.radius = radius; 42 | this.level = level; 43 | x=y=-1f; 44 | weight=1; 45 | } 46 | 47 | public void setObj(Object obj) { 48 | this.obj = obj; 49 | } 50 | 51 | public String getText() { 52 | return text; 53 | } 54 | 55 | public Object getObj() { 56 | return obj; 57 | } 58 | 59 | public int getLevel() { 60 | return level; 61 | } 62 | 63 | public void setLevel(int level) { 64 | this.level = level; 65 | } 66 | 67 | float getRadius() { 68 | return radius; 69 | } 70 | 71 | boolean isRootNode() { 72 | return level == ROOT_NODE_LEVEL; 73 | } 74 | 75 | /** 76 | * 给定一个坐标 (x, y),判断该坐标是否在节点所在范围内。用来判断是否点击了该节点。 77 | * @param x x坐标 78 | * @param y y坐标 79 | * @param scale 缩放比例 80 | * @return true 表示 (x, y) 在该节点内部 81 | */ 82 | boolean isInside(float x, float y, float scale) { 83 | float left = (this.x - radius) * scale; 84 | float top = (this.y - radius) * scale; 85 | float right = (this.x + radius) * scale; 86 | float bottom = (this.y + radius) * scale; 87 | return x >= left && x <= right && y >= top && y <= bottom; 88 | } 89 | 90 | boolean isStable() { 91 | return state != 0; 92 | } 93 | 94 | /** 95 | * 设置节点的状态是否正在被手指拖动。 96 | * @param state {@linkplain #DRAG_START} 开始拖动;{@linkplain #DRAG_END} 结束拖动。 97 | */ 98 | void setDragState(@State short state) { 99 | switch (state) { 100 | case DRAG_START: 101 | this.state |= state; 102 | break; 103 | case DRAG_END: 104 | this.state &= ~state; 105 | break; 106 | } 107 | } 108 | 109 | @ShortDef({DRAG_START, DRAG, DRAG_END}) 110 | @Retention(SOURCE) 111 | public @interface State {} 112 | 113 | @Retention(SOURCE) 114 | @Target({ANNOTATION_TYPE}) 115 | public @interface ShortDef { 116 | short[] value() default {}; 117 | boolean flag() default false; 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /library/src/main/java/com/gyso/treeview/algorithm/force/ForceListener.java: -------------------------------------------------------------------------------- 1 | package com.gyso.treeview.algorithm.force; 2 | 3 | /** 4 | * Created by Z.Pan on 2016/10/10. 5 | */ 6 | 7 | public interface ForceListener { 8 | void refresh(); 9 | } 10 | -------------------------------------------------------------------------------- /library/src/main/java/com/gyso/treeview/algorithm/force/QuadTree.java: -------------------------------------------------------------------------------- 1 | package com.gyso.treeview.algorithm.force; 2 | 3 | /** 4 | * Created by Z.Pan on 2016/10/10. 5 | */ 6 | class QuadTree { 7 | private static final float NULL = -1; 8 | 9 | static class Node { 10 | boolean isLeaf; 11 | Node[] children; 12 | FNode point; 13 | float x = NULL; 14 | float y = NULL; 15 | 16 | float charge; 17 | float pointCharge; 18 | float cx, cy; 19 | 20 | Node(boolean isLeaf, Node[] children, FNode point, float x, float y) { 21 | this.isLeaf = isLeaf; 22 | this.children = children; 23 | this.point = point; 24 | this.x = x; 25 | this.y = y; 26 | } 27 | } 28 | 29 | Node root; 30 | 31 | QuadTree() { 32 | root = generateNode(); 33 | } 34 | 35 | void insert(Node n, FNode node, float minX, float minY, float maxX, float maxY) { 36 | if (Float.isNaN(minX) || Float.isNaN(minY) || Float.isNaN(maxX) || Float.isNaN(maxY)) { 37 | return; 38 | } 39 | if (n.isLeaf) { 40 | float nx = n.x; 41 | float ny = n.y; 42 | 43 | if (nx != NULL) { 44 | if (Math.abs(nx - node.x) + Math.abs(ny - node.y) < 0.01) { 45 | insertChild(n, node, minX, minY, maxX, maxY); 46 | } else { 47 | FNode nPoint = n.point; 48 | n.x = n.y = NULL; 49 | n.point = null; 50 | insertChild(n, nPoint, minX, minY, maxX, maxY); 51 | insertChild(n, node, minX, minY, maxX, maxY); 52 | } 53 | } else { 54 | n.x = node.x; 55 | n.y = node.y; 56 | n.point = node; 57 | } 58 | } else { 59 | insertChild(n, node, minX, minY, maxX, maxY); 60 | } 61 | } 62 | 63 | private void insertChild(Node n, FNode node, float minX, float minY, float maxX, float maxY) { 64 | float sx = (minX + maxX) * 0.5f; 65 | float sy = (minY + maxY) * 0.5f; 66 | boolean isRight = node.x >= sx; 67 | boolean isBottom = node.y >= sy; 68 | int right = isRight ? 1 : 0; 69 | int bottom = isBottom ? 1 : 0; 70 | int i = (bottom << 1) + right; 71 | 72 | n.isLeaf = false; 73 | if (n.children[i] == null) { 74 | Node nod = generateNode(); 75 | n.children[i] = nod; 76 | n = nod; 77 | } else { 78 | n = n.children[i]; 79 | } 80 | 81 | if (isRight) { 82 | minX = sx; 83 | } else { 84 | maxX = sx; 85 | } 86 | 87 | if (isBottom) { 88 | minY = sy; 89 | } else { 90 | maxY = sy; 91 | } 92 | 93 | insert(n, node, minX, minY, maxX, maxY); 94 | } 95 | 96 | /** generate a leaf node for quad tree. */ 97 | private Node generateNode() { 98 | return new Node(true, new Node[4], null, NULL, NULL); 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /library/src/main/java/com/gyso/treeview/algorithm/ring/Ring.java: -------------------------------------------------------------------------------- 1 | package com.gyso.treeview.algorithm.ring; 2 | 3 | import android.graphics.PointF; 4 | import android.util.SparseIntArray; 5 | import com.gyso.treeview.model.ITraversal; 6 | import com.gyso.treeview.model.NodeModel; 7 | import com.gyso.treeview.model.TreeModel; 8 | import com.gyso.treeview.util.TreeViewLog; 9 | import java.util.HashMap; 10 | import java.util.LinkedList; 11 | import java.util.Map; 12 | import java.util.concurrent.atomic.AtomicBoolean; 13 | import java.util.concurrent.atomic.AtomicInteger; 14 | 15 | public class Ring { 16 | public static final String TAG = Ring.class.getSimpleName(); 17 | private final static Map, Ring> RING_MAP = new HashMap<>(); 18 | private final PointF center = new PointF(); 19 | private final TreeModel model; 20 | private final Map, PointF> nodeModelPointFMap = new HashMap<>(); 21 | private final Map, Double> nodeModelAngleMap = new HashMap<>(); 22 | private final Map, Double> piePartBaseAngleMap = new HashMap<>(); 23 | protected SparseIntArray floorStart = new SparseIntArray(200); 24 | private final AtomicBoolean isGenPositionOK = new AtomicBoolean(true); 25 | private final Map, AtomicInteger> multiMap = new HashMap<>(); 26 | private final AtomicInteger multi = new AtomicInteger(1); 27 | private Ring(TreeModel model) { 28 | this.model = model; 29 | } 30 | public static Ring getInstance(TreeModel model){ 31 | if(model==null){ 32 | return null; 33 | } 34 | Ring ring = RING_MAP.get(model); 35 | if(ring==null){ 36 | ring = new Ring(model); 37 | RING_MAP.put(model,ring); 38 | } 39 | return ring; 40 | } 41 | public Ring setCenter(float x, float y) { 42 | center.x = x; 43 | center.y = y; 44 | TreeViewLog.e(TAG,"center["+x+","+y+"]"); 45 | return this; 46 | } 47 | 48 | public Ring setFloorStart(SparseIntArray floorStart) { 49 | this.floorStart = floorStart; 50 | return this; 51 | } 52 | 53 | public boolean isGenPositionOK(){ 54 | return isGenPositionOK.get(); 55 | } 56 | 57 | public Map, PointF> genPositions() { 58 | if (model == null) { 59 | return null; 60 | } 61 | NodeModel rootNode = model.getRootNode(); 62 | nodeModelPointFMap.clear(); 63 | nodeModelPointFMap.put(rootNode, new PointF(center.y, center.x)); 64 | isGenPositionOK.set(true); 65 | int leafCount = rootNode.leafCount; 66 | LinkedList> rootNodeChildNodes = rootNode.getChildNodes(); 67 | if(leafCount == 0 || rootNodeChildNodes.isEmpty()){ 68 | return nodeModelPointFMap; 69 | } 70 | 71 | //keep in pie center 72 | double deltaAngle = 2f*Math.PI / (leafCount+multi.get()); 73 | double pieAngle = 2f*Math.PI / leafCount; 74 | double sumAngleDelta = 0; 75 | double sumAnglePie = 0; 76 | for (NodeModel rootChild : rootNodeChildNodes) { 77 | int count = rootChild.leafCount; 78 | double da ,dp; 79 | if(count<2){ 80 | da = sumAngleDelta+deltaAngle/2; 81 | dp = sumAnglePie +pieAngle/2; 82 | sumAngleDelta += deltaAngle; 83 | sumAnglePie += pieAngle; 84 | }else{ 85 | da = sumAngleDelta+deltaAngle*(count-1)/2f; 86 | dp = sumAnglePie + pieAngle*(count-1)/2f; 87 | sumAngleDelta += deltaAngle*(count-1); 88 | sumAnglePie += pieAngle*(count-1); 89 | } 90 | piePartBaseAngleMap.put(rootChild,dp-da); 91 | } 92 | 93 | //doTraversalNodes 94 | model.doTraversalNodes((ITraversal>) next -> { 95 | if(next.equals(rootNode)){ 96 | return; 97 | } 98 | TreeViewLog.e(TAG,next+" -gyso"); 99 | NodeModel nextParentNode = next.getParentNode(); 100 | if(nextParentNode !=null && !nextParentNode.equals(rootNode)){ 101 | piePartBaseAngleMap.put(next,piePartBaseAngleMap.get(nextParentNode)); 102 | } 103 | PointF pointF = nodeModelPointFMap.get(next); 104 | if (pointF == null) { 105 | pointF = new PointF(); 106 | int deep = next.deep; 107 | int floor = next.floor; 108 | double angle = deltaAngle * deep; 109 | LinkedList> childNodes = next.getChildNodes(); 110 | if(!childNodes.isEmpty()){ 111 | int count = next.leafCount; 112 | if(count>=2){ 113 | double d = deltaAngle*(count-1)/2f; 114 | angle += d; 115 | } 116 | } 117 | Double centerPieAngle = piePartBaseAngleMap.get(next); 118 | if(centerPieAngle!=null){ 119 | angle += centerPieAngle; 120 | } 121 | 122 | float radius = floorStart.get(floor); 123 | TreeViewLog.e(TAG,"radius["+radius+"]angle["+angle+"]"); 124 | pointF.x = (float) (radius * Math.sin(angle))+center.y; 125 | pointF.y = (float) (radius * Math.cos(angle))+center.x; 126 | nodeModelPointFMap.put(next, pointF); 127 | nodeModelAngleMap.put(next,angle); 128 | 129 | //keep acute angle for parent 130 | if(nextParentNode !=null && !nextParentNode.equals(rootNode)){ 131 | PointF parentPosition = nodeModelPointFMap.get(nextParentNode); 132 | PointF rootPosition = nodeModelPointFMap.get(rootNode); 133 | PointF currentPosition = nodeModelPointFMap.get(next); 134 | double p2r = Math.hypot(parentPosition.x-rootPosition.x,parentPosition.y-rootPosition.y); 135 | double c2r = Math.hypot(currentPosition.x-rootPosition.x,currentPosition.y-rootPosition.y); 136 | double dAngle = Math.abs(nodeModelAngleMap.get(nextParentNode)-nodeModelAngleMap.get(next)); 137 | double l1 = c2r*Math.abs(Math.cos(dAngle)); 138 | TreeViewLog.e(TAG,"p2r["+p2r+"]c2r["+c2r+"]dAngle["+dAngle+"]Math.sin(dAngle)["+Math.sin(dAngle)+"]l1["+l1+"]l1 <= p2r["+(l1 <= p2r)+"]"); 139 | //if(false){ 140 | if(l1 <= p2r){ 141 | //no good layout request layout 142 | isGenPositionOK.set(false); 143 | int d = leafCount/6; 144 | multi.addAndGet(d==0?1:d); 145 | TreeViewLog.e(TAG,"Calculate ring position false!!!"); 146 | } 147 | } 148 | } 149 | }); 150 | if(isGenPositionOK.get()){ 151 | multi.set(1); 152 | } 153 | TreeViewLog.e(TAG,"isGenPositionOK["+isGenPositionOK+"]multi["+multi.get()+"]deltaAngle["+deltaAngle+"]nodeModelPointFMap{"+nodeModelPointFMap+""); 154 | return nodeModelPointFMap; 155 | } 156 | } -------------------------------------------------------------------------------- /library/src/main/java/com/gyso/treeview/algorithm/ring/RingForSimple.java: -------------------------------------------------------------------------------- 1 | package com.gyso.treeview.algorithm.ring; 2 | 3 | import android.graphics.Point; 4 | import android.graphics.PointF; 5 | import android.util.SparseIntArray; 6 | import com.gyso.treeview.model.ITraversal; 7 | import com.gyso.treeview.model.NodeModel; 8 | import com.gyso.treeview.model.TreeModel; 9 | import com.gyso.treeview.util.TreeViewLog; 10 | 11 | import java.util.ArrayDeque; 12 | import java.util.Deque; 13 | import java.util.HashMap; 14 | import java.util.LinkedList; 15 | import java.util.Map; 16 | import java.util.concurrent.atomic.AtomicInteger; 17 | 18 | public class RingForSimple { 19 | public static final String TAG = RingForSimple.class.getSimpleName(); 20 | private final static Map, RingForSimple> RING_MAP = new HashMap<>(); 21 | private final PointF center = new PointF(); 22 | private final TreeModel model; 23 | private final Map, PointF> nodeModelPointFMap = new HashMap<>(); 24 | private final Map, Double> nodeModelAngleMap = new HashMap<>(); 25 | protected SparseIntArray floorStart = new SparseIntArray(200); 26 | private final AtomicInteger multi = new AtomicInteger(1); 27 | private int maxDeep; 28 | private final Map, Point> nodeWidthMap = new HashMap<>(); 29 | private RingForSimple(TreeModel model) { 30 | this.model = model; 31 | } 32 | public static RingForSimple getInstance(TreeModel model){ 33 | if(model==null){ 34 | return null; 35 | } 36 | RingForSimple ring = RING_MAP.get(model); 37 | if(ring==null){ 38 | ring = new RingForSimple(model); 39 | RING_MAP.put(model,ring); 40 | } 41 | return ring; 42 | } 43 | public RingForSimple setCenter(float x, float y) { 44 | center.x = x; 45 | center.y = y; 46 | TreeViewLog.e(TAG,"center["+x+","+y+"]"); 47 | return this; 48 | } 49 | 50 | public RingForSimple setFloorStart(SparseIntArray floorStart) { 51 | this.floorStart = floorStart; 52 | return this; 53 | } 54 | 55 | public void reconstruction(TreeModel mTreeModel) { 56 | TreeViewLog.e(TAG,"reconstruction start"); 57 | nodeWidthMap.clear(); 58 | Deque deque = new ArrayDeque<>(); 59 | NodeModel rootNode = mTreeModel.getRootNode(); 60 | deque.add(rootNode); 61 | SparseIntArray deepSum = new SparseIntArray(); 62 | //calculate base deep 63 | while (!deque.isEmpty()) { 64 | NodeModel cur = deque.poll(); 65 | if(cur==null){ 66 | return; 67 | } 68 | cur.deep = deepSum.get(cur.floor,0); 69 | record(cur); 70 | NodeModel tmp = cur.parentNode; 71 | while (!tmp.equals(rootNode)){ 72 | record(tmp); 73 | tmp = tmp.parentNode; 74 | } 75 | deepSum.put(cur.floor, cur.deep+1); 76 | LinkedList childNodes = cur.getChildNodes(); 77 | if (childNodes.size() > 0) { 78 | deque.addAll(childNodes); 79 | } 80 | } 81 | } 82 | 83 | private void record(NodeModel node) { 84 | if (node == null) { 85 | return; 86 | } 87 | Point point = nodeWidthMap.get(node); 88 | if(point==null){ 89 | point = new Point(); 90 | nodeWidthMap.put(node, point); 91 | } 92 | maxDeep = Math.max(node.deep, maxDeep); 93 | point.x= Math.max(node.deep, point.x); 94 | point.y = Math.min(node.deep, point.y); 95 | } 96 | 97 | public Map, PointF> genPositions() { 98 | if (model == null) { 99 | return null; 100 | } 101 | NodeModel rootNode = model.getRootNode(); 102 | nodeModelPointFMap.clear(); 103 | nodeModelPointFMap.put(rootNode, new PointF(center.y, center.x)); 104 | int leafCount = maxDeep; 105 | LinkedList> rootNodeChildNodes = rootNode.getChildNodes(); 106 | if(leafCount == 0 || rootNodeChildNodes.isEmpty()){ 107 | return nodeModelPointFMap; 108 | } 109 | 110 | //keep in pie center 111 | double pieAngle = 2f*Math.PI / leafCount; 112 | 113 | //doTraversalNodes 114 | model.doTraversalNodes((ITraversal>) next -> { 115 | if(next.equals(rootNode)){ 116 | return; 117 | } 118 | PointF pointF = new PointF(); 119 | int deep = next.deep; 120 | int floor = next.floor; 121 | double angle = pieAngle * deep; 122 | LinkedList> childNodes = next.getChildNodes(); 123 | if(!childNodes.isEmpty()){ 124 | Point point = nodeWidthMap.get(next); 125 | int count = point.x-point.y; 126 | if(count>=2){ 127 | double d = pieAngle*(count-1)/2f; 128 | angle += d; 129 | } 130 | } 131 | float radius = floorStart.get(floor); 132 | TreeViewLog.e(TAG,"radius["+radius+"]angle["+angle+"]"); 133 | pointF.x = (float) (radius * Math.sin(angle))+center.y; 134 | pointF.y = (float) (radius * Math.cos(angle))+center.x; 135 | nodeModelPointFMap.put(next, pointF); 136 | nodeModelAngleMap.put(next,angle); 137 | }); 138 | return nodeModelPointFMap; 139 | } 140 | } -------------------------------------------------------------------------------- /library/src/main/java/com/gyso/treeview/algorithm/table/TableKey.java: -------------------------------------------------------------------------------- 1 | package com.gyso.treeview.algorithm.table; 2 | 3 | import androidx.annotation.NonNull; 4 | import androidx.annotation.Nullable; 5 | 6 | public class TableKey { 7 | public int floor; 8 | public int deep; 9 | 10 | public TableKey(int floor, int deep) { 11 | this.floor = floor; 12 | this.deep = deep; 13 | } 14 | 15 | @Override 16 | public int hashCode() { 17 | return floor; 18 | } 19 | 20 | @Override 21 | public boolean equals(@Nullable Object obj) { 22 | if(obj instanceof TableKey){ 23 | TableKey o = (TableKey)obj; 24 | return floor == o.floor && deep==o.deep; 25 | } 26 | return false; 27 | } 28 | 29 | @NonNull 30 | @Override 31 | public String toString() { 32 | return "["+floor+","+deep+"]"; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /library/src/main/java/com/gyso/treeview/algorithm/tight_table/TightTable.java: -------------------------------------------------------------------------------- 1 | package com.gyso.treeview.algorithm.tight_table; 2 | 3 | public class TightTable { 4 | public void tightTable(){ 5 | 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /library/src/main/java/com/gyso/treeview/cache_pool/HolderPool.java: -------------------------------------------------------------------------------- 1 | package com.gyso.treeview.cache_pool; 2 | 3 | import android.graphics.PointF; 4 | 5 | import androidx.core.util.Pools; 6 | 7 | import com.gyso.treeview.adapter.TreeViewHolder; 8 | 9 | /** 10 | * @Author: 怪兽N 11 | * @Time: 2021/6/10 14:37 12 | * @Email: 674149099@qq.com 13 | * @WeChat: guaishouN 14 | * @Describe: 15 | * * Holder pool. {@link com.gyso.treeview.adapter.TreeViewHolder} 16 | * * NOTE: not safe pool, please just use in UI thread 17 | */ 18 | public class HolderPool extends Pools.SimplePool> { 19 | public static final int DEFAULT_SIZE = 30; 20 | private final static HolderPool POOL = new HolderPool(); 21 | /** 22 | * Creates a new instance. 23 | */ 24 | public HolderPool() { 25 | super(DEFAULT_SIZE); 26 | } 27 | 28 | public TreeViewHolder obtain(){ 29 | return POOL.acquire(); 30 | } 31 | 32 | public void free(TreeViewHolder holder){ 33 | try { 34 | POOL.release(holder); 35 | }catch (IllegalStateException e){ 36 | e.printStackTrace(); 37 | } 38 | 39 | } 40 | 41 | public static void freeAll(){ 42 | while (POOL.acquire()!=null); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /library/src/main/java/com/gyso/treeview/cache_pool/PointPool.java: -------------------------------------------------------------------------------- 1 | package com.gyso.treeview.cache_pool; 2 | 3 | import android.graphics.PointF; 4 | 5 | import androidx.core.util.Pools; 6 | 7 | /** 8 | * @Author: 怪兽N 9 | * @Time: 2021/5/8 9:56 10 | * @Email: 674149099@qq.com 11 | * @WeChat: guaishouN 12 | * @Describe: 13 | * Point pool. 14 | * NOTE: not safe pool, please just use in UI thread 15 | */ 16 | public class PointPool extends Pools.SimplePool { 17 | public static final int DEFAULT_SIZE = 20; 18 | private final static PointPool POOL = new PointPool(); 19 | /** 20 | * Creates a new instance. 21 | */ 22 | private PointPool() { 23 | super(DEFAULT_SIZE); 24 | } 25 | 26 | public static PointF obtain(){ 27 | PointF point = POOL.acquire(); 28 | if(point==null){ 29 | return new PointF(); 30 | } 31 | return point; 32 | } 33 | 34 | public static PointF obtain(float x, float y){ 35 | PointF point = POOL.acquire(); 36 | if(point==null){ 37 | return new PointF(x,y); 38 | } 39 | point.set(x,y); 40 | return point; 41 | } 42 | 43 | public static void free(PointF p){ 44 | POOL.release(p); 45 | } 46 | 47 | public static void freeAll(){ 48 | while (POOL.acquire()!=null); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /library/src/main/java/com/gyso/treeview/layout/BoxHorizonLeftAndRightLayoutManager.java: -------------------------------------------------------------------------------- 1 | package com.gyso.treeview.layout; 2 | 3 | import android.content.Context; 4 | import android.graphics.Point; 5 | import android.view.View; 6 | 7 | import com.gyso.treeview.TreeViewContainer; 8 | import com.gyso.treeview.adapter.TreeViewHolder; 9 | import com.gyso.treeview.line.BaseLine; 10 | import com.gyso.treeview.model.NodeModel; 11 | import com.gyso.treeview.model.TreeModel; 12 | import com.gyso.treeview.util.ViewBox; 13 | 14 | import java.util.LinkedList; 15 | 16 | public class BoxHorizonLeftAndRightLayoutManager extends BoxRightTreeLayoutManager { 17 | private static final String TAG = BoxHorizonLeftAndRightLayoutManager.class.getSimpleName(); 18 | private boolean isJustCalculate; 19 | public BoxHorizonLeftAndRightLayoutManager(Context context, int spaceParentToChild, int spacePeerToPeer, BaseLine baseline) { 20 | super(context, spaceParentToChild, spacePeerToPeer, baseline); 21 | } 22 | 23 | @Override 24 | public int getTreeLayoutType() { 25 | return LAYOUT_TYPE_HORIZON_LEFT_AND_RIGHT; 26 | } 27 | 28 | @Override 29 | public void onManagerFinishMeasureAllNodes(TreeViewContainer treeViewContainer) { 30 | getPadding(treeViewContainer); 31 | mContentViewBox.bottom += (paddingBox.bottom+paddingBox.top); 32 | extraDeltaX = mContentViewBox.right; 33 | mContentViewBox.right += (paddingBox.left+paddingBox.right+extraDeltaX); 34 | fixedViewBox.setValues(mContentViewBox); 35 | if(winHeight == 0 || winWidth==0){ 36 | return; 37 | } 38 | float scale = 1f*winWidth/winHeight; 39 | float wr = 1f* mContentViewBox.getWidth()/winWidth; 40 | float hr = 1f* mContentViewBox.getHeight()/winHeight; 41 | if(wr>=hr){ 42 | float bh = mContentViewBox.getWidth()/scale; 43 | fixedViewBox.bottom = (int)bh; 44 | }else{ 45 | float bw = mContentViewBox.getHeight()*scale; 46 | fixedViewBox.right = (int)bw; 47 | } 48 | mFixedDx = fixedViewBox.getWidth()/2; 49 | mFixedDy = (fixedViewBox.getHeight()-mContentViewBox.getHeight())/2; 50 | } 51 | 52 | @Override 53 | public void performLayout(final TreeViewContainer treeViewContainer) { 54 | isJustCalculate = true; 55 | super.performLayout(treeViewContainer); 56 | isJustCalculate = false; 57 | final TreeModel mTreeModel = treeViewContainer.getTreeModel(); 58 | if (mTreeModel != null) { 59 | NodeModel rootNode = mTreeModel.getRootNode(); 60 | TreeViewHolder rootNodeHolder = treeViewContainer.getTreeViewHolder(rootNode); 61 | View rootNodeView = rootNodeHolder == null ? null : rootNodeHolder.getView(); 62 | if (rootNodeView == null) { 63 | throw new NullPointerException(" rootNodeView can not be null"); 64 | } 65 | int rootCx = rootNodeView.getLeft()+rootNodeView.getMeasuredWidth()/2; 66 | int rootCy = rootNodeView.getTop()+rootNodeView.getMeasuredHeight()/2; 67 | //divide equally by two 68 | LinkedList> rootNodeChildNodes = rootNode.getChildNodes(); 69 | Point divideDy = getDivideDy(rootNode, treeViewContainer); 70 | int centerAy = divideDy.x; 71 | int centerBy = divideDy.y; 72 | int divider = rootNodeChildNodes.size()/2; 73 | int count = 0; 74 | for (NodeModel node : rootNodeChildNodes) { 75 | if(count moveDy(n,treeViewContainer, (rootCy - centerAy))); 78 | }else{ 79 | //move to other side 80 | node.traverseIncludeSelf(n -> mirrorByCxDy(n,treeViewContainer,rootCx, (rootCy - centerBy))); 81 | } 82 | count++; 83 | } 84 | onManagerFinishLayoutAllNodes(treeViewContainer); 85 | } 86 | } 87 | 88 | private Point getDivideDy(NodeModel rootNode, TreeViewContainer treeViewContainer){ 89 | LinkedList> rootNodeChildNodes = rootNode.getChildNodes(); 90 | int divider = rootNodeChildNodes.size()/2; 91 | int count = 0; 92 | int minA,maxA,minB,maxB; 93 | minA = minB = Integer.MAX_VALUE; 94 | maxA= maxB = Integer.MIN_VALUE; 95 | for (NodeModel currentNode : rootNodeChildNodes) { 96 | TreeViewHolder currentHolder = treeViewContainer.getTreeViewHolder(currentNode); 97 | View currentNodeView = currentHolder == null ? null : currentHolder.getView(); 98 | if (currentNodeView == null) { 99 | throw new NullPointerException(" currentNodeView can not be null"); 100 | } 101 | int left =currentNodeView.getLeft(); 102 | int top = currentNodeView.getTop(); 103 | int currentHeight = currentNodeView.getMeasuredHeight(); 104 | int currentWidth = currentNodeView.getMeasuredWidth(); 105 | if(count currentNode, TreeViewContainer treeViewContainer, int deltaY){ 118 | TreeViewHolder currentHolder = treeViewContainer.getTreeViewHolder(currentNode); 119 | View currentNodeView = currentHolder == null ? null : currentHolder.getView(); 120 | if (currentNodeView == null) { 121 | throw new NullPointerException(" currentNodeView can not be null"); 122 | } 123 | currentHolder.setHolderLayoutType(LAYOUT_TYPE_HORIZON_RIGHT); 124 | int currentWidth = currentNodeView.getMeasuredWidth(); 125 | int currentHeight = currentNodeView.getMeasuredHeight(); 126 | int left =currentNodeView.getLeft(); 127 | int right = left+currentWidth; 128 | int top = deltaY+ currentNodeView.getTop(); 129 | int bottom = top+currentHeight; 130 | ViewBox finalLocation = new ViewBox(top, left, bottom, right); 131 | onManagerLayoutNode(currentNode, currentNodeView, finalLocation, treeViewContainer); 132 | } 133 | 134 | private void mirrorByCxDy(NodeModel currentNode, TreeViewContainer treeViewContainer,int centerX, int deltaY){ 135 | TreeViewHolder currentHolder = treeViewContainer.getTreeViewHolder(currentNode); 136 | View currentNodeView = currentHolder == null ? null : currentHolder.getView(); 137 | if (currentNodeView == null) { 138 | throw new NullPointerException(" currentNodeView can not be null"); 139 | } 140 | currentHolder.setHolderLayoutType(LAYOUT_TYPE_HORIZON_LEFT); 141 | int right =centerX*2- currentNodeView.getLeft(); 142 | int left =centerX*2- currentNodeView.getRight(); 143 | int top = deltaY+currentNodeView.getTop(); 144 | int bottom = deltaY+currentNodeView.getBottom(); 145 | ViewBox finalLocation = new ViewBox(top, left, bottom, right); 146 | onManagerLayoutNode(currentNode, currentNodeView, finalLocation, treeViewContainer); 147 | } 148 | 149 | @Override 150 | public void onManagerLayoutNode(NodeModel currentNode, View currentNodeView, ViewBox finalLocation, TreeViewContainer treeViewContainer) { 151 | if(isJustCalculate){ 152 | currentNodeView.layout(finalLocation.left, finalLocation.top, finalLocation.right, finalLocation.bottom); 153 | return; 154 | } 155 | if (!layoutAnimatePrepare(currentNode, currentNodeView, finalLocation, treeViewContainer)) { 156 | currentNodeView.layout(finalLocation.left, finalLocation.top, finalLocation.right, finalLocation.bottom); 157 | } 158 | } 159 | 160 | @Override 161 | public void onManagerFinishLayoutAllNodes(TreeViewContainer treeViewContainer) { 162 | if(!isJustCalculate){ 163 | super.onManagerFinishLayoutAllNodes(treeViewContainer); 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /library/src/main/java/com/gyso/treeview/layout/BoxLeftTreeLayoutManager.java: -------------------------------------------------------------------------------- 1 | package com.gyso.treeview.layout; 2 | 3 | import android.content.Context; 4 | import android.view.View; 5 | 6 | import com.gyso.treeview.TreeViewContainer; 7 | import com.gyso.treeview.adapter.TreeViewHolder; 8 | import com.gyso.treeview.line.BaseLine; 9 | import com.gyso.treeview.model.ITraversal; 10 | import com.gyso.treeview.model.NodeModel; 11 | import com.gyso.treeview.model.TreeModel; 12 | import com.gyso.treeview.util.ViewBox; 13 | 14 | public class BoxLeftTreeLayoutManager extends BoxRightTreeLayoutManager{ 15 | private static final String TAG = BoxLeftTreeLayoutManager.class.getSimpleName(); 16 | private boolean isJustCalculate; 17 | public BoxLeftTreeLayoutManager(Context context, int spaceParentToChild, int spacePeerToPeer, BaseLine baseline) { 18 | super(context, spaceParentToChild, spacePeerToPeer, baseline); 19 | } 20 | 21 | @Override 22 | public int getTreeLayoutType() { 23 | return LAYOUT_TYPE_HORIZON_LEFT; 24 | } 25 | 26 | @Override 27 | public void performLayout(final TreeViewContainer treeViewContainer) { 28 | isJustCalculate = true; 29 | super.performLayout(treeViewContainer); 30 | isJustCalculate = false; 31 | final TreeModel mTreeModel = treeViewContainer.getTreeModel(); 32 | if (mTreeModel != null) { 33 | final int cx = fixedViewBox.getWidth()/2; 34 | mTreeModel.doTraversalNodes(new ITraversal>() { 35 | @Override 36 | public void next(NodeModel next) { 37 | mirrorByCx(next,treeViewContainer,cx); 38 | } 39 | @Override 40 | public void finish() { 41 | onManagerFinishLayoutAllNodes(treeViewContainer); 42 | } 43 | }); 44 | } 45 | } 46 | 47 | private void mirrorByCx(NodeModel currentNode, TreeViewContainer treeViewContainer,int centerX){ 48 | TreeViewHolder currentHolder = treeViewContainer.getTreeViewHolder(currentNode); 49 | View currentNodeView = currentHolder == null ? null : currentHolder.getView(); 50 | if (currentNodeView == null) { 51 | throw new NullPointerException(" currentNodeView can not be null"); 52 | } 53 | int left = centerX*2 - currentNodeView.getRight(); 54 | int right = centerX*2- currentNodeView.getLeft(); 55 | int top = currentNodeView.getTop(); 56 | int bottom =currentNodeView.getBottom(); 57 | ViewBox finalLocation = new ViewBox(top, left, bottom, right); 58 | onManagerLayoutNode(currentNode, currentNodeView, finalLocation, treeViewContainer); 59 | } 60 | 61 | @Override 62 | public void onManagerLayoutNode(NodeModel currentNode, View currentNodeView, ViewBox finalLocation, TreeViewContainer treeViewContainer) { 63 | if(isJustCalculate){ 64 | currentNodeView.layout(finalLocation.left, finalLocation.top, finalLocation.right, finalLocation.bottom); 65 | return; 66 | } 67 | if (!layoutAnimatePrepare(currentNode, currentNodeView, finalLocation, treeViewContainer)) { 68 | currentNodeView.layout(finalLocation.left, finalLocation.top, finalLocation.right, finalLocation.bottom); 69 | } 70 | } 71 | 72 | @Override 73 | public void onManagerFinishLayoutAllNodes(TreeViewContainer treeViewContainer) { 74 | if(!isJustCalculate){ 75 | super.onManagerFinishLayoutAllNodes(treeViewContainer); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /library/src/main/java/com/gyso/treeview/layout/BoxUpTreeLayoutManager.java: -------------------------------------------------------------------------------- 1 | package com.gyso.treeview.layout; 2 | 3 | import android.content.Context; 4 | import android.view.View; 5 | 6 | import com.gyso.treeview.TreeViewContainer; 7 | import com.gyso.treeview.adapter.TreeViewHolder; 8 | import com.gyso.treeview.line.BaseLine; 9 | import com.gyso.treeview.model.ITraversal; 10 | import com.gyso.treeview.model.NodeModel; 11 | import com.gyso.treeview.model.TreeModel; 12 | import com.gyso.treeview.util.ViewBox; 13 | 14 | public class BoxUpTreeLayoutManager extends BoxDownTreeLayoutManager { 15 | private static final String TAG = BoxUpTreeLayoutManager.class.getSimpleName(); 16 | private boolean isJustCalculate; 17 | public BoxUpTreeLayoutManager(Context context, int spaceParentToChild, int spacePeerToPeer, BaseLine baseline) { 18 | super(context, spaceParentToChild, spacePeerToPeer, baseline); 19 | } 20 | @Override 21 | public int getTreeLayoutType() { 22 | return LAYOUT_TYPE_VERTICAL_UP; 23 | } 24 | 25 | @Override 26 | public void performLayout(final TreeViewContainer treeViewContainer) { 27 | isJustCalculate = true; 28 | super.performLayout(treeViewContainer); 29 | isJustCalculate = false; 30 | final TreeModel mTreeModel = treeViewContainer.getTreeModel(); 31 | if (mTreeModel != null) { 32 | final int cy= fixedViewBox.getHeight()/2; 33 | mTreeModel.doTraversalNodes(new ITraversal>() { 34 | @Override 35 | public void next(NodeModel next) { 36 | mirrorByCy(next,treeViewContainer,cy); 37 | } 38 | @Override 39 | public void finish() { 40 | onManagerFinishLayoutAllNodes(treeViewContainer); 41 | } 42 | }); 43 | } 44 | } 45 | 46 | private void mirrorByCy(NodeModel currentNode, TreeViewContainer treeViewContainer,int centerY){ 47 | TreeViewHolder currentHolder = treeViewContainer.getTreeViewHolder(currentNode); 48 | View currentNodeView = currentHolder == null ? null : currentHolder.getView(); 49 | if (currentNodeView == null) { 50 | throw new NullPointerException(" currentNodeView can not be null"); 51 | } 52 | int left =currentNodeView.getLeft(); 53 | int right = currentNodeView.getRight(); 54 | int bottom= centerY*2- currentNodeView.getTop(); 55 | int top = centerY*2- currentNodeView.getBottom(); 56 | ViewBox finalLocation = new ViewBox(top, left, bottom, right); 57 | onManagerLayoutNode(currentNode, currentNodeView, finalLocation, treeViewContainer); 58 | } 59 | 60 | @Override 61 | public void onManagerLayoutNode(NodeModel currentNode, View currentNodeView, ViewBox finalLocation, TreeViewContainer treeViewContainer) { 62 | if(isJustCalculate){ 63 | currentNodeView.layout(finalLocation.left, finalLocation.top, finalLocation.right, finalLocation.bottom); 64 | return; 65 | } 66 | if (!layoutAnimatePrepare(currentNode, currentNodeView, finalLocation, treeViewContainer)) { 67 | currentNodeView.layout(finalLocation.left, finalLocation.top, finalLocation.right, finalLocation.bottom); 68 | } 69 | } 70 | 71 | @Override 72 | public void onManagerFinishLayoutAllNodes(TreeViewContainer treeViewContainer) { 73 | if(!isJustCalculate){ 74 | super.onManagerFinishLayoutAllNodes(treeViewContainer); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /library/src/main/java/com/gyso/treeview/layout/CompactLeftTreeLayoutManager.java: -------------------------------------------------------------------------------- 1 | package com.gyso.treeview.layout; 2 | 3 | import android.content.Context; 4 | import android.view.View; 5 | 6 | import com.gyso.treeview.TreeViewContainer; 7 | import com.gyso.treeview.adapter.TreeViewHolder; 8 | import com.gyso.treeview.line.BaseLine; 9 | import com.gyso.treeview.model.ITraversal; 10 | import com.gyso.treeview.model.NodeModel; 11 | import com.gyso.treeview.model.TreeModel; 12 | import com.gyso.treeview.util.ViewBox; 13 | 14 | public class CompactLeftTreeLayoutManager extends CompactRightTreeLayoutManager{ 15 | private static final String TAG = TableLeftTreeLayoutManager.class.getSimpleName(); 16 | private boolean isJustCalculate; 17 | public CompactLeftTreeLayoutManager(Context context, int spaceParentToChild, int spacePeerToPeer, BaseLine baseline) { 18 | super(context, spaceParentToChild, spacePeerToPeer, baseline); 19 | } 20 | 21 | @Override 22 | public int getTreeLayoutType() { 23 | return LAYOUT_TYPE_HORIZON_LEFT; 24 | } 25 | 26 | @Override 27 | public void performLayout(final TreeViewContainer treeViewContainer) { 28 | isJustCalculate = true; 29 | super.performLayout(treeViewContainer); 30 | isJustCalculate = false; 31 | final TreeModel mTreeModel = treeViewContainer.getTreeModel(); 32 | if (mTreeModel != null) { 33 | final int cx = fixedViewBox.getWidth()/2; 34 | mTreeModel.doTraversalNodes(new ITraversal>() { 35 | @Override 36 | public void next(NodeModel next) { 37 | mirrorByCx(next,treeViewContainer,cx); 38 | } 39 | @Override 40 | public void finish() { 41 | onManagerFinishLayoutAllNodes(treeViewContainer); 42 | } 43 | }); 44 | } 45 | } 46 | 47 | private void mirrorByCx(NodeModel currentNode, TreeViewContainer treeViewContainer,int centerX){ 48 | TreeViewHolder currentHolder = treeViewContainer.getTreeViewHolder(currentNode); 49 | View currentNodeView = currentHolder == null ? null : currentHolder.getView(); 50 | if (currentNodeView == null) { 51 | throw new NullPointerException(" currentNodeView can not be null"); 52 | } 53 | int left = centerX*2 - currentNodeView.getRight(); 54 | int right = centerX*2- currentNodeView.getLeft(); 55 | int top = currentNodeView.getTop(); 56 | int bottom =currentNodeView.getBottom(); 57 | ViewBox finalLocation = new ViewBox(top, left, bottom, right); 58 | onManagerLayoutNode(currentNode, currentNodeView, finalLocation, treeViewContainer); 59 | } 60 | 61 | @Override 62 | public void onManagerLayoutNode(NodeModel currentNode, View currentNodeView, ViewBox finalLocation, TreeViewContainer treeViewContainer) { 63 | if(isJustCalculate){ 64 | currentNodeView.layout(finalLocation.left, finalLocation.top, finalLocation.right, finalLocation.bottom); 65 | return; 66 | } 67 | if (!layoutAnimatePrepare(currentNode, currentNodeView, finalLocation, treeViewContainer)) { 68 | currentNodeView.layout(finalLocation.left, finalLocation.top, finalLocation.right, finalLocation.bottom); 69 | } 70 | } 71 | 72 | @Override 73 | public void onManagerFinishLayoutAllNodes(TreeViewContainer treeViewContainer) { 74 | if(!isJustCalculate){ 75 | super.onManagerFinishLayoutAllNodes(treeViewContainer); 76 | } 77 | } 78 | } 79 | 80 | -------------------------------------------------------------------------------- /library/src/main/java/com/gyso/treeview/layout/CompactRightTreeLayoutManager.java: -------------------------------------------------------------------------------- 1 | package com.gyso.treeview.layout; 2 | 3 | import android.content.Context; 4 | import android.view.View; 5 | 6 | import com.gyso.treeview.R; 7 | import com.gyso.treeview.TreeViewContainer; 8 | import com.gyso.treeview.adapter.TreeViewHolder; 9 | import com.gyso.treeview.algorithm.table.Table; 10 | import com.gyso.treeview.line.BaseLine; 11 | import com.gyso.treeview.model.ITraversal; 12 | import com.gyso.treeview.model.NodeModel; 13 | import com.gyso.treeview.model.TreeModel; 14 | import com.gyso.treeview.util.DensityUtils; 15 | import com.gyso.treeview.util.TreeViewLog; 16 | import com.gyso.treeview.util.ViewBox; 17 | 18 | public class CompactRightTreeLayoutManager extends TreeLayoutManager { 19 | private static final String TAG = CompactRightTreeLayoutManager.class.getSimpleName(); 20 | 21 | public CompactRightTreeLayoutManager(Context context, int spaceParentToChild, int spacePeerToPeer, BaseLine baseline) { 22 | super(context, spaceParentToChild, spacePeerToPeer, baseline); 23 | } 24 | 25 | @Override 26 | public void calculateByLayoutAlgorithm(TreeModel mTreeModel) { 27 | new Table().reconstruction(mTreeModel,Table.COMPACT_TABLE); 28 | } 29 | 30 | @Override 31 | public int getTreeLayoutType() { 32 | return LAYOUT_TYPE_HORIZON_RIGHT; 33 | } 34 | 35 | @Override 36 | public void performMeasure(TreeViewContainer treeViewContainer) { 37 | final TreeModel mTreeModel = treeViewContainer.getTreeModel(); 38 | if (mTreeModel != null) { 39 | mContentViewBox.clear(); 40 | floorMax.clear(); 41 | deepMax.clear(); 42 | ITraversal> traversal = new ITraversal>() { 43 | @Override 44 | public void next(NodeModel next) { 45 | measure(next, treeViewContainer); 46 | } 47 | 48 | @Override 49 | public void finish() { 50 | onManagerFinishMeasureAllNodes(treeViewContainer); 51 | } 52 | }; 53 | mTreeModel.doTraversalNodes(traversal); 54 | } 55 | } 56 | 57 | @Override 58 | public void onManagerFinishMeasureAllNodes(TreeViewContainer treeViewContainer) { 59 | getPadding(treeViewContainer); 60 | mContentViewBox.bottom += (paddingBox.bottom+paddingBox.top); 61 | mContentViewBox.right += (paddingBox.left+paddingBox.right); 62 | fixedViewBox.setValues(mContentViewBox); 63 | if(winHeight == 0 || winWidth==0){ 64 | return; 65 | } 66 | float scale = 1f*winWidth/winHeight; 67 | float wr = 1f* mContentViewBox.getWidth()/winWidth; 68 | float hr = 1f* mContentViewBox.getHeight()/winHeight; 69 | if(wr>=hr){ 70 | float bh = mContentViewBox.getWidth()/scale; 71 | fixedViewBox.bottom = (int)bh; 72 | }else{ 73 | float bw = mContentViewBox.getHeight()*scale; 74 | fixedViewBox.right = (int)bw; 75 | } 76 | mFixedDx = (fixedViewBox.getWidth()-mContentViewBox.getWidth())/2; 77 | mFixedDy = (fixedViewBox.getHeight()-mContentViewBox.getHeight())/2; 78 | 79 | //compute floor start position 80 | for (int i = 0; i <= floorMax.size(); i++) { 81 | int fn = (i == floorMax.size())?floorMax.size():floorMax.keyAt(i); 82 | int preStart = floorStart.get(fn - 1, 0); 83 | int preMax = floorMax.get(fn - 1, 0); 84 | int startPos = (fn==0?(mFixedDx + paddingBox.left):spaceParentToChild) + preStart + preMax; 85 | floorStart.put(fn,startPos); 86 | } 87 | 88 | //compute deep start position 89 | for (int i = 0; i <= deepMax.size(); i++) { 90 | int dn = (i == deepMax.size())?deepMax.size():deepMax.keyAt(i); 91 | int preStart = deepStart.get(dn - 1, 0); 92 | int preMax = deepMax.get(dn - 1, 0); 93 | int startPos = (dn==0?(mFixedDy + paddingBox.top):spacePeerToPeer) + preStart + preMax; 94 | deepStart.put(dn,startPos); 95 | } 96 | } 97 | 98 | private void measure(NodeModel node, TreeViewContainer treeViewContainer) { 99 | TreeViewHolder currentHolder = treeViewContainer.getTreeViewHolder(node); 100 | View currentNodeView = currentHolder==null?null:currentHolder.getView(); 101 | if(currentNodeView==null){ 102 | throw new NullPointerException(" currentNodeView can not be null"); 103 | } 104 | int preMaxW = floorMax.get(node.floor); 105 | int curW = currentNodeView.getMeasuredWidth(); 106 | if(preMaxW < curW){ 107 | floorMax.put(node.floor,curW); 108 | int delta = spaceParentToChild +curW-preMaxW; 109 | mContentViewBox.right += delta; 110 | } 111 | 112 | int preMaxH = deepMax.get(node.deep); 113 | int curH = currentNodeView.getMeasuredHeight(); 114 | if(preMaxH < curH){ 115 | deepMax.put(node.deep,curH); 116 | int delta = spacePeerToPeer +curH-preMaxH; 117 | mContentViewBox.bottom += delta; 118 | } 119 | } 120 | 121 | @Override 122 | public void performLayout(final TreeViewContainer treeViewContainer) { 123 | final TreeModel mTreeModel = treeViewContainer.getTreeModel(); 124 | if (mTreeModel != null) { 125 | mTreeModel.doTraversalNodes(new ITraversal>() { 126 | @Override 127 | public void next(NodeModel next) { 128 | layoutNodes(next, treeViewContainer); 129 | } 130 | 131 | @Override 132 | public void finish() { 133 | onManagerFinishLayoutAllNodes(treeViewContainer); 134 | } 135 | }); 136 | } 137 | } 138 | 139 | @Override 140 | public ViewBox getTreeLayoutBox() { 141 | return fixedViewBox; 142 | } 143 | 144 | private void layoutNodes(NodeModel currentNode, TreeViewContainer treeViewContainer){ 145 | TreeViewLog.e(TAG,"onLayout: "+currentNode); 146 | TreeViewHolder currentHolder = treeViewContainer.getTreeViewHolder(currentNode); 147 | View currentNodeView = currentHolder==null?null:currentHolder.getView(); 148 | int deep = currentNode.deep; 149 | int floor = currentNode.floor; 150 | if(currentNodeView==null){ 151 | throw new NullPointerException(" currentNodeView can not be null"); 152 | } 153 | 154 | int currentWidth = currentNodeView.getMeasuredWidth(); 155 | int currentHeight = currentNodeView.getMeasuredHeight(); 156 | int horizonCenterFix = Math.abs(currentHeight - deepMax.get(deep))/2; 157 | 158 | 159 | int top = deepStart.get(deep)+horizonCenterFix+extraDeltaY; 160 | int left = floorStart.get(floor)+extraDeltaX; 161 | int bottom = top+currentHeight; 162 | int right = left+currentWidth; 163 | 164 | ViewBox finalLocation = new ViewBox(top, left, bottom, right); 165 | onManagerLayoutNode(currentNode, currentNodeView, finalLocation, treeViewContainer); 166 | } 167 | 168 | @Override 169 | public void onManagerLayoutNode(NodeModel currentNode, 170 | View currentNodeView, 171 | ViewBox finalLocation, 172 | TreeViewContainer treeViewContainer){ 173 | if (!layoutAnimatePrepare(currentNode, currentNodeView, finalLocation, treeViewContainer)) { 174 | currentNodeView.layout(finalLocation.left, finalLocation.top, finalLocation.right, finalLocation.bottom); 175 | } 176 | } 177 | @Override 178 | public void onManagerFinishLayoutAllNodes(TreeViewContainer treeViewContainer){ 179 | layoutAnimate(treeViewContainer); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /library/src/main/java/com/gyso/treeview/layout/CompactUpTreeLayoutManager.java: -------------------------------------------------------------------------------- 1 | package com.gyso.treeview.layout; 2 | 3 | import android.content.Context; 4 | import android.view.View; 5 | 6 | import com.gyso.treeview.TreeViewContainer; 7 | import com.gyso.treeview.adapter.TreeViewHolder; 8 | import com.gyso.treeview.line.BaseLine; 9 | import com.gyso.treeview.model.ITraversal; 10 | import com.gyso.treeview.model.NodeModel; 11 | import com.gyso.treeview.model.TreeModel; 12 | import com.gyso.treeview.util.ViewBox; 13 | 14 | public class CompactUpTreeLayoutManager extends CompactDownTreeLayoutManager { 15 | private static final String TAG = CompactUpTreeLayoutManager.class.getSimpleName(); 16 | private boolean isJustCalculate; 17 | public CompactUpTreeLayoutManager(Context context, int spaceParentToChild, int spacePeerToPeer, BaseLine baseline) { 18 | super(context, spaceParentToChild, spacePeerToPeer, baseline); 19 | } 20 | @Override 21 | public int getTreeLayoutType() { 22 | return LAYOUT_TYPE_VERTICAL_UP; 23 | } 24 | 25 | @Override 26 | public void performLayout(final TreeViewContainer treeViewContainer) { 27 | isJustCalculate = true; 28 | super.performLayout(treeViewContainer); 29 | isJustCalculate = false; 30 | final TreeModel mTreeModel = treeViewContainer.getTreeModel(); 31 | if (mTreeModel != null) { 32 | final int cy= fixedViewBox.getHeight()/2; 33 | mTreeModel.doTraversalNodes(new ITraversal>() { 34 | @Override 35 | public void next(NodeModel next) { 36 | mirrorByCy(next,treeViewContainer,cy); 37 | } 38 | @Override 39 | public void finish() { 40 | onManagerFinishLayoutAllNodes(treeViewContainer); 41 | } 42 | }); 43 | } 44 | } 45 | 46 | private void mirrorByCy(NodeModel currentNode, TreeViewContainer treeViewContainer,int centerY){ 47 | TreeViewHolder currentHolder = treeViewContainer.getTreeViewHolder(currentNode); 48 | View currentNodeView = currentHolder == null ? null : currentHolder.getView(); 49 | if (currentNodeView == null) { 50 | throw new NullPointerException(" currentNodeView can not be null"); 51 | } 52 | int left =currentNodeView.getLeft(); 53 | int right = currentNodeView.getRight(); 54 | int bottom= centerY*2- currentNodeView.getTop(); 55 | int top = centerY*2- currentNodeView.getBottom(); 56 | ViewBox finalLocation = new ViewBox(top, left, bottom, right); 57 | onManagerLayoutNode(currentNode, currentNodeView, finalLocation, treeViewContainer); 58 | } 59 | 60 | @Override 61 | public void onManagerLayoutNode(NodeModel currentNode, View currentNodeView, ViewBox finalLocation, TreeViewContainer treeViewContainer) { 62 | if(isJustCalculate){ 63 | currentNodeView.layout(finalLocation.left, finalLocation.top, finalLocation.right, finalLocation.bottom); 64 | return; 65 | } 66 | if (!layoutAnimatePrepare(currentNode, currentNodeView, finalLocation, treeViewContainer)) { 67 | currentNodeView.layout(finalLocation.left, finalLocation.top, finalLocation.right, finalLocation.bottom); 68 | } 69 | } 70 | 71 | @Override 72 | public void onManagerFinishLayoutAllNodes(TreeViewContainer treeViewContainer) { 73 | if(!isJustCalculate){ 74 | super.onManagerFinishLayoutAllNodes(treeViewContainer); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /library/src/main/java/com/gyso/treeview/layout/TableLeftTreeLayoutManager.java: -------------------------------------------------------------------------------- 1 | package com.gyso.treeview.layout; 2 | 3 | import android.content.Context; 4 | import android.view.View; 5 | import com.gyso.treeview.TreeViewContainer; 6 | import com.gyso.treeview.adapter.TreeViewHolder; 7 | import com.gyso.treeview.line.BaseLine; 8 | import com.gyso.treeview.model.ITraversal; 9 | import com.gyso.treeview.model.NodeModel; 10 | import com.gyso.treeview.model.TreeModel; 11 | import com.gyso.treeview.util.ViewBox; 12 | 13 | public class TableLeftTreeLayoutManager extends TableRightTreeLayoutManager { 14 | private static final String TAG = TableLeftTreeLayoutManager.class.getSimpleName(); 15 | private boolean isJustCalculate; 16 | public TableLeftTreeLayoutManager(Context context, int spaceParentToChild, int spacePeerToPeer, BaseLine baseline) { 17 | super(context, spaceParentToChild, spacePeerToPeer, baseline); 18 | } 19 | 20 | @Override 21 | public int getTreeLayoutType() { 22 | return LAYOUT_TYPE_HORIZON_LEFT; 23 | } 24 | 25 | @Override 26 | public void performLayout(final TreeViewContainer treeViewContainer) { 27 | isJustCalculate = true; 28 | super.performLayout(treeViewContainer); 29 | isJustCalculate = false; 30 | final TreeModel mTreeModel = treeViewContainer.getTreeModel(); 31 | if (mTreeModel != null) { 32 | final int cx = fixedViewBox.getWidth()/2; 33 | mTreeModel.doTraversalNodes(new ITraversal>() { 34 | @Override 35 | public void next(NodeModel next) { 36 | mirrorByCx(next,treeViewContainer,cx); 37 | } 38 | @Override 39 | public void finish() { 40 | onManagerFinishLayoutAllNodes(treeViewContainer); 41 | } 42 | }); 43 | } 44 | } 45 | 46 | private void mirrorByCx(NodeModel currentNode, TreeViewContainer treeViewContainer,int centerX){ 47 | TreeViewHolder currentHolder = treeViewContainer.getTreeViewHolder(currentNode); 48 | View currentNodeView = currentHolder == null ? null : currentHolder.getView(); 49 | if (currentNodeView == null) { 50 | throw new NullPointerException(" currentNodeView can not be null"); 51 | } 52 | int left = centerX*2 - currentNodeView.getRight(); 53 | int right = centerX*2- currentNodeView.getLeft(); 54 | int top = currentNodeView.getTop(); 55 | int bottom =currentNodeView.getBottom(); 56 | ViewBox finalLocation = new ViewBox(top, left, bottom, right); 57 | onManagerLayoutNode(currentNode, currentNodeView, finalLocation, treeViewContainer); 58 | } 59 | 60 | @Override 61 | public void onManagerLayoutNode(NodeModel currentNode, View currentNodeView, ViewBox finalLocation, TreeViewContainer treeViewContainer) { 62 | if(isJustCalculate){ 63 | currentNodeView.layout(finalLocation.left, finalLocation.top, finalLocation.right, finalLocation.bottom); 64 | return; 65 | } 66 | if (!layoutAnimatePrepare(currentNode, currentNodeView, finalLocation, treeViewContainer)) { 67 | currentNodeView.layout(finalLocation.left, finalLocation.top, finalLocation.right, finalLocation.bottom); 68 | } 69 | } 70 | 71 | @Override 72 | public void onManagerFinishLayoutAllNodes(TreeViewContainer treeViewContainer) { 73 | if(!isJustCalculate){ 74 | super.onManagerFinishLayoutAllNodes(treeViewContainer); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /library/src/main/java/com/gyso/treeview/layout/TableUpTreeLayoutManagerTable.java: -------------------------------------------------------------------------------- 1 | package com.gyso.treeview.layout; 2 | 3 | import android.content.Context; 4 | import android.view.View; 5 | 6 | import com.gyso.treeview.TreeViewContainer; 7 | import com.gyso.treeview.adapter.TreeViewHolder; 8 | import com.gyso.treeview.line.BaseLine; 9 | import com.gyso.treeview.model.ITraversal; 10 | import com.gyso.treeview.model.NodeModel; 11 | import com.gyso.treeview.model.TreeModel; 12 | import com.gyso.treeview.util.ViewBox; 13 | 14 | public class TableUpTreeLayoutManagerTable extends TableDownTreeLayoutManager { 15 | private static final String TAG = TableUpTreeLayoutManagerTable.class.getSimpleName(); 16 | private boolean isJustCalculate; 17 | public TableUpTreeLayoutManagerTable(Context context, int spaceParentToChild, int spacePeerToPeer, BaseLine baseline) { 18 | super(context, spaceParentToChild, spacePeerToPeer, baseline); 19 | } 20 | @Override 21 | public int getTreeLayoutType() { 22 | return LAYOUT_TYPE_VERTICAL_UP; 23 | } 24 | 25 | @Override 26 | public void performLayout(final TreeViewContainer treeViewContainer) { 27 | isJustCalculate = true; 28 | super.performLayout(treeViewContainer); 29 | isJustCalculate = false; 30 | final TreeModel mTreeModel = treeViewContainer.getTreeModel(); 31 | if (mTreeModel != null) { 32 | final int cy= fixedViewBox.getHeight()/2; 33 | mTreeModel.doTraversalNodes(new ITraversal>() { 34 | @Override 35 | public void next(NodeModel next) { 36 | mirrorByCy(next,treeViewContainer,cy); 37 | } 38 | @Override 39 | public void finish() { 40 | onManagerFinishLayoutAllNodes(treeViewContainer); 41 | } 42 | }); 43 | } 44 | } 45 | 46 | private void mirrorByCy(NodeModel currentNode, TreeViewContainer treeViewContainer,int centerY){ 47 | TreeViewHolder currentHolder = treeViewContainer.getTreeViewHolder(currentNode); 48 | View currentNodeView = currentHolder == null ? null : currentHolder.getView(); 49 | if (currentNodeView == null) { 50 | throw new NullPointerException(" currentNodeView can not be null"); 51 | } 52 | int left =currentNodeView.getLeft(); 53 | int right = currentNodeView.getRight(); 54 | int bottom= centerY*2- currentNodeView.getTop(); 55 | int top = centerY*2- currentNodeView.getBottom(); 56 | ViewBox finalLocation = new ViewBox(top, left, bottom, right); 57 | onManagerLayoutNode(currentNode, currentNodeView, finalLocation, treeViewContainer); 58 | } 59 | 60 | @Override 61 | public void onManagerLayoutNode(NodeModel currentNode, View currentNodeView, ViewBox finalLocation, TreeViewContainer treeViewContainer) { 62 | if(isJustCalculate){ 63 | currentNodeView.layout(finalLocation.left, finalLocation.top, finalLocation.right, finalLocation.bottom); 64 | return; 65 | } 66 | if (!layoutAnimatePrepare(currentNode, currentNodeView, finalLocation, treeViewContainer)) { 67 | currentNodeView.layout(finalLocation.left, finalLocation.top, finalLocation.right, finalLocation.bottom); 68 | } 69 | } 70 | 71 | @Override 72 | public void onManagerFinishLayoutAllNodes(TreeViewContainer treeViewContainer) { 73 | if(!isJustCalculate){ 74 | super.onManagerFinishLayoutAllNodes(treeViewContainer); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /library/src/main/java/com/gyso/treeview/line/AngledLine.java: -------------------------------------------------------------------------------- 1 | package com.gyso.treeview.line; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.graphics.Path; 8 | import android.graphics.PointF; 9 | import android.view.View; 10 | 11 | import com.gyso.treeview.adapter.DrawInfo; 12 | import com.gyso.treeview.adapter.TreeViewHolder; 13 | import com.gyso.treeview.cache_pool.PointPool; 14 | import com.gyso.treeview.layout.TreeLayoutManager; 15 | import com.gyso.treeview.model.NodeModel; 16 | import com.gyso.treeview.util.DensityUtils; 17 | 18 | /** 19 | * @Author: 怪兽N 20 | * @Time: 2021/5/18 16:47 21 | * @Email: 674149099@qq.com 22 | * @WeChat: guaishouN 23 | * @Describe: 24 | * AngledLine between two nodes 25 | */ 26 | public class AngledLine extends BaseLine { 27 | public static final int DEFAULT_LINE_WIDTH_DP = 3; 28 | private int lineColor = Color.parseColor("#055287"); 29 | private int lineWidth = DEFAULT_LINE_WIDTH_DP; 30 | public AngledLine() { 31 | super(); 32 | } 33 | 34 | public AngledLine(int lineColor, int lineWidth_dp) { 35 | this(); 36 | this.lineColor = lineColor; 37 | this.lineWidth = lineWidth_dp; 38 | } 39 | 40 | public void setLineColor(int lineColor) { 41 | this.lineColor = lineColor; 42 | } 43 | 44 | public void setLineWidth(int lineWidth) { 45 | this.lineWidth = lineWidth; 46 | } 47 | @Override 48 | public void draw(DrawInfo drawInfo) { 49 | Canvas canvas = drawInfo.getCanvas(); 50 | TreeViewHolder fromHolder = drawInfo.getFromHolder(); 51 | TreeViewHolder toHolder = drawInfo.getToHolder(); 52 | Paint mPaint = drawInfo.getPaint(); 53 | Path mPath = drawInfo.getPath(); 54 | int holderLayoutType = toHolder.getHolderLayoutType(); 55 | int spacePeerToPeer = drawInfo.getSpacePeerToPeer(); 56 | int spaceParentToChild = drawInfo.getSpaceParentToChild(); 57 | 58 | //get view and node 59 | View fromView = fromHolder.getView(); 60 | NodeModel fromNode = fromHolder.getNode(); 61 | View toView = toHolder.getView(); 62 | NodeModel toNode = toHolder.getNode(); 63 | Context context = fromView.getContext(); 64 | 65 | PointF startPoint,point1,endPoint,point2; 66 | if(holderLayoutType== TreeLayoutManager.LAYOUT_TYPE_HORIZON_RIGHT){ 67 | startPoint = PointPool.obtain(fromView.getRight(),(fromView.getTop()+fromView.getBottom())/2f); 68 | point1 = PointPool.obtain(startPoint.x+spaceParentToChild/3f,startPoint.y); 69 | endPoint = PointPool.obtain(toView.getLeft(),(toView.getTop()+toView.getBottom())/2f); 70 | point2 = PointPool.obtain(startPoint.x+spaceParentToChild/3f,endPoint.y); 71 | 72 | }else if(holderLayoutType== TreeLayoutManager.LAYOUT_TYPE_HORIZON_LEFT){ 73 | startPoint = PointPool.obtain(fromView.getLeft(),(fromView.getTop()+fromView.getBottom())/2f); 74 | point1 = PointPool.obtain(startPoint.x-spaceParentToChild/3f,startPoint.y); 75 | endPoint = PointPool.obtain(toView.getRight(),(toView.getTop()+toView.getBottom())/2f); 76 | point2 = PointPool.obtain(startPoint.x-spaceParentToChild/3f,endPoint.y); 77 | 78 | }else if(holderLayoutType== TreeLayoutManager.LAYOUT_TYPE_VERTICAL_DOWN){ 79 | startPoint = PointPool.obtain((fromView.getLeft()+fromView.getRight())/2f,fromView.getBottom()); 80 | point1 = PointPool.obtain(startPoint.x,startPoint.y+spaceParentToChild/3f); 81 | endPoint = PointPool.obtain((toView.getLeft()+toView.getRight())/2f,toView.getTop()); 82 | point2 = PointPool.obtain(endPoint.x,startPoint.y+spaceParentToChild/3f); 83 | 84 | }else if(holderLayoutType== TreeLayoutManager.LAYOUT_TYPE_VERTICAL_UP){ 85 | startPoint = PointPool.obtain((fromView.getLeft()+fromView.getRight())/2f,fromView.getTop()); 86 | point1 = PointPool.obtain(startPoint.x,startPoint.y-spaceParentToChild/3f); 87 | endPoint = PointPool.obtain((toView.getLeft()+toView.getRight())/2f,toView.getBottom()); 88 | point2 = PointPool.obtain(endPoint.x,startPoint.y-spaceParentToChild/3f); 89 | 90 | }else{ 91 | super.draw(drawInfo); 92 | return; 93 | } 94 | 95 | //set paint 96 | mPath.reset(); 97 | mPaint.reset(); 98 | mPaint.setColor(lineColor); 99 | mPaint.setStyle(Paint.Style.STROKE); 100 | mPaint.setStrokeWidth(DensityUtils.dp2px(context,lineWidth)); 101 | mPaint.setAntiAlias(true); 102 | 103 | mPath.moveTo(startPoint.x,startPoint.y); 104 | mPath.lineTo(point1.x,point1.y); 105 | mPath.lineTo(point2.x,point2.y); 106 | mPath.lineTo(endPoint.x,endPoint.y); 107 | 108 | //do not forget release 109 | PointPool.free(startPoint); 110 | PointPool.free(point1); 111 | PointPool.free(point2); 112 | PointPool.free(endPoint); 113 | //draw 114 | canvas.drawPath(mPath,mPaint); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /library/src/main/java/com/gyso/treeview/line/BaseLine.java: -------------------------------------------------------------------------------- 1 | package com.gyso.treeview.line; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.graphics.Path; 8 | import android.view.View; 9 | import com.gyso.treeview.adapter.DrawInfo; 10 | import com.gyso.treeview.adapter.TreeViewHolder; 11 | import com.gyso.treeview.util.DensityUtils; 12 | 13 | /** 14 | * @Author: 怪兽N 15 | * @Time: 2021/5/7 21:02 16 | * @Email: 674149099@qq.com 17 | * @WeChat: guaishouN 18 | * @Describe: 19 | * line to connect the fromNodeView and toNodeView 20 | */ 21 | public class BaseLine { 22 | /** 23 | * this method will be invoke when the tree view is onDispatchDraw 24 | */ 25 | public void draw(DrawInfo drawInfo){ 26 | Canvas canvas = drawInfo.getCanvas(); 27 | TreeViewHolder fromHolder = drawInfo.getFromHolder(); 28 | TreeViewHolder toHolder = drawInfo.getToHolder(); 29 | Paint mPaint = drawInfo.getPaint(); 30 | Path mPath = drawInfo.getPath(); 31 | 32 | //get view and node 33 | View fromView = fromHolder.getView(); 34 | View toView = toHolder.getView(); 35 | Context context = fromView.getContext(); 36 | 37 | //set paint 38 | mPaint.reset(); 39 | mPath.reset(); 40 | mPaint.setColor(Color.MAGENTA); 41 | mPaint.setStyle(Paint.Style.STROKE); 42 | mPaint.setStrokeWidth(DensityUtils.dp2px(context,3)); 43 | mPaint.setAntiAlias(true); 44 | 45 | int fromCenterX = (fromView.getLeft()+fromView.getRight())/2; 46 | int fromCenterY = (fromView.getTop()+fromView.getBottom())/2; 47 | int toCenterX = (toView.getLeft()+toView.getRight())/2; 48 | int toCenterY = (toView.getTop()+toView.getBottom())/2; 49 | 50 | mPath.moveTo(fromCenterX, fromCenterY); 51 | mPath.lineTo(toCenterX, toCenterY); 52 | 53 | //draw 54 | canvas.drawPath(mPath,mPaint); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /library/src/main/java/com/gyso/treeview/line/DashLine.java: -------------------------------------------------------------------------------- 1 | package com.gyso.treeview.line; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.DashPathEffect; 7 | import android.graphics.Paint; 8 | import android.graphics.Path; 9 | import android.graphics.PointF; 10 | import android.view.View; 11 | 12 | import com.gyso.treeview.adapter.DrawInfo; 13 | import com.gyso.treeview.adapter.TreeViewHolder; 14 | import com.gyso.treeview.cache_pool.PointPool; 15 | import com.gyso.treeview.layout.TreeLayoutManager; 16 | import com.gyso.treeview.model.NodeModel; 17 | import com.gyso.treeview.util.DensityUtils; 18 | 19 | /** 20 | * @Author: 怪兽N 21 | * @Time: 2021/5/8 17:41 22 | * @Email: 674149099@qq.com 23 | * @WeChat: guaishouN 24 | * @Describe: 25 | * DashPathEffect line 26 | */ 27 | public class DashLine extends BaseLine { 28 | public static final int DEFAULT_LINE_WIDTH_DP = 3; 29 | private int lineColor = Color.parseColor("#E57373"); 30 | private int lineWidth = DEFAULT_LINE_WIDTH_DP; 31 | private final static DashPathEffect effect = new DashPathEffect(new float[]{20,10},10); 32 | public DashLine() { 33 | super(); 34 | } 35 | 36 | public DashLine(int lineColor, int lineWidth_dp) { 37 | this(); 38 | this.lineColor = lineColor; 39 | this.lineWidth = lineWidth_dp; 40 | } 41 | 42 | public void setLineColor(int lineColor) { 43 | this.lineColor = lineColor; 44 | } 45 | 46 | public void setLineWidth(int lineWidth) { 47 | this.lineWidth = lineWidth; 48 | } 49 | 50 | @Override 51 | public void draw(DrawInfo drawInfo) { 52 | Canvas canvas = drawInfo.getCanvas(); 53 | TreeViewHolder fromHolder = drawInfo.getFromHolder(); 54 | TreeViewHolder toHolder = drawInfo.getToHolder(); 55 | Paint mPaint = drawInfo.getPaint(); 56 | Path mPath = drawInfo.getPath(); 57 | int holderLayoutType = toHolder.getHolderLayoutType(); 58 | int spacePeerToPeer = drawInfo.getSpacePeerToPeer(); 59 | int spaceParentToChild = drawInfo.getSpaceParentToChild(); 60 | 61 | //get view and node 62 | View fromView = fromHolder.getView(); 63 | NodeModel fromNode = fromHolder.getNode(); 64 | View toView = toHolder.getView(); 65 | NodeModel toNode = toHolder.getNode(); 66 | Context context = fromView.getContext(); 67 | 68 | PointF startPoint,point1,endPoint,point2; 69 | if(holderLayoutType== TreeLayoutManager.LAYOUT_TYPE_HORIZON_RIGHT){ 70 | startPoint = PointPool.obtain(fromView.getRight(),(fromView.getTop()+fromView.getBottom())/2f); 71 | point1 = PointPool.obtain(startPoint.x+DensityUtils.dp2px(context,15),startPoint.y); 72 | endPoint = PointPool.obtain(toView.getLeft(),(toView.getTop()+toView.getBottom())/2f); 73 | point2 = PointPool.obtain(startPoint.x,endPoint.y); 74 | }else if(holderLayoutType== TreeLayoutManager.LAYOUT_TYPE_HORIZON_LEFT){ 75 | startPoint = PointPool.obtain(fromView.getLeft(),(fromView.getTop()+fromView.getBottom())/2f); 76 | point1 = PointPool.obtain(startPoint.x-DensityUtils.dp2px(context,15),startPoint.y); 77 | endPoint = PointPool.obtain(toView.getRight(),(toView.getTop()+toView.getBottom())/2f); 78 | point2 = PointPool.obtain(startPoint.x,endPoint.y); 79 | }else if(holderLayoutType== TreeLayoutManager.LAYOUT_TYPE_VERTICAL_DOWN){ 80 | startPoint = PointPool.obtain((fromView.getLeft()+fromView.getRight())/2f,fromView.getBottom()); 81 | point1 = PointPool.obtain(startPoint.x,startPoint.y+DensityUtils.dp2px(context,15)); 82 | endPoint = PointPool.obtain((toView.getLeft()+toView.getRight())/2f,toView.getTop()); 83 | point2 = PointPool.obtain(endPoint.x,startPoint.y); 84 | }else if(holderLayoutType== TreeLayoutManager.LAYOUT_TYPE_VERTICAL_UP){ 85 | startPoint = PointPool.obtain((fromView.getLeft()+fromView.getRight())/2f,fromView.getTop()); 86 | point1 = PointPool.obtain(startPoint.x,startPoint.y-DensityUtils.dp2px(context,15)); 87 | endPoint = PointPool.obtain((toView.getLeft()+toView.getRight())/2f,toView.getBottom()); 88 | point2 = PointPool.obtain(endPoint.x,startPoint.y); 89 | }else{ 90 | super.draw(drawInfo); 91 | return; 92 | } 93 | 94 | mPaint.reset(); 95 | mPath.reset(); 96 | //set paint 97 | mPaint.setColor(lineColor); 98 | mPaint.setStyle(Paint.Style.STROKE); 99 | mPaint.setStrokeWidth(DensityUtils.dp2px(context,lineWidth)); 100 | mPaint.setAntiAlias(true); 101 | mPaint.setPathEffect(effect); 102 | 103 | mPath.moveTo(startPoint.x,startPoint.y); 104 | mPath.cubicTo( 105 | point1.x,point1.y, 106 | point2.x,point2.y, 107 | endPoint.x,endPoint.y); 108 | 109 | //do not forget release 110 | PointPool.free(startPoint); 111 | PointPool.free(point1); 112 | PointPool.free(point2); 113 | PointPool.free(endPoint); 114 | //draw 115 | canvas.drawPath(mPath,mPaint); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /library/src/main/java/com/gyso/treeview/line/SmoothLine.java: -------------------------------------------------------------------------------- 1 | package com.gyso.treeview.line; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.graphics.Path; 8 | import android.graphics.PointF; 9 | import android.view.View; 10 | 11 | import com.gyso.treeview.adapter.DrawInfo; 12 | import com.gyso.treeview.adapter.TreeViewHolder; 13 | import com.gyso.treeview.cache_pool.PointPool; 14 | import com.gyso.treeview.layout.TreeLayoutManager; 15 | import com.gyso.treeview.model.NodeModel; 16 | import com.gyso.treeview.util.DensityUtils; 17 | 18 | /** 19 | * @Author: 怪兽N 20 | * @Time: 2021/5/8 9:47 21 | * @Email: 674149099@qq.com 22 | * @WeChat: guaishouN 23 | * @Describe: 24 | * Simple smooth line 25 | */ 26 | public class SmoothLine extends BaseLine { 27 | public static final int DEFAULT_LINE_WIDTH_DP = 3; 28 | private int lineColor = Color.parseColor("#055287"); 29 | private int lineWidth = DEFAULT_LINE_WIDTH_DP; 30 | public SmoothLine() { 31 | super(); 32 | } 33 | 34 | public SmoothLine(int lineColor, int lineWidth_dp) { 35 | this(); 36 | this.lineColor = lineColor; 37 | this.lineWidth = lineWidth_dp; 38 | } 39 | 40 | public void setLineColor(int lineColor) { 41 | this.lineColor = lineColor; 42 | } 43 | 44 | public void setLineWidth(int lineWidth) { 45 | this.lineWidth = lineWidth; 46 | } 47 | 48 | @Override 49 | public void draw(DrawInfo drawInfo) { 50 | Canvas canvas = drawInfo.getCanvas(); 51 | TreeViewHolder fromHolder = drawInfo.getFromHolder(); 52 | TreeViewHolder toHolder = drawInfo.getToHolder(); 53 | Paint mPaint = drawInfo.getPaint(); 54 | Path mPath = drawInfo.getPath(); 55 | int holderLayoutType = toHolder.getHolderLayoutType(); 56 | int spacePeerToPeer = drawInfo.getSpacePeerToPeer(); 57 | int spaceParentToChild = drawInfo.getSpaceParentToChild(); 58 | 59 | //get view and node 60 | View fromView = fromHolder.getView(); 61 | NodeModel fromNode = fromHolder.getNode(); 62 | View toView = toHolder.getView(); 63 | NodeModel toNode = toHolder.getNode(); 64 | Context context = fromView.getContext(); 65 | 66 | PointF startPoint,point1,endPoint,point2; 67 | if(holderLayoutType== TreeLayoutManager.LAYOUT_TYPE_HORIZON_RIGHT){ 68 | startPoint = PointPool.obtain(fromView.getRight(),(fromView.getTop()+fromView.getBottom())/2f); 69 | point1 = PointPool.obtain(startPoint.x+DensityUtils.dp2px(context,15),startPoint.y); 70 | endPoint = PointPool.obtain(toView.getLeft(),(toView.getTop()+toView.getBottom())/2f); 71 | point2 = PointPool.obtain(startPoint.x,endPoint.y); 72 | 73 | }else if(holderLayoutType== TreeLayoutManager.LAYOUT_TYPE_HORIZON_LEFT){ 74 | startPoint = PointPool.obtain(fromView.getLeft(),(fromView.getTop()+fromView.getBottom())/2f); 75 | point1 = PointPool.obtain(startPoint.x-DensityUtils.dp2px(context,15),startPoint.y); 76 | endPoint = PointPool.obtain(toView.getRight(),(toView.getTop()+toView.getBottom())/2f); 77 | point2 = PointPool.obtain(startPoint.x,endPoint.y); 78 | 79 | }else if(holderLayoutType== TreeLayoutManager.LAYOUT_TYPE_VERTICAL_DOWN){ 80 | startPoint = PointPool.obtain((fromView.getLeft()+fromView.getRight())/2f,fromView.getBottom()); 81 | point1 = PointPool.obtain(startPoint.x,startPoint.y+DensityUtils.dp2px(context,15)); 82 | endPoint = PointPool.obtain((toView.getLeft()+toView.getRight())/2f,toView.getTop()); 83 | point2 = PointPool.obtain(endPoint.x,startPoint.y); 84 | 85 | }else if(holderLayoutType== TreeLayoutManager.LAYOUT_TYPE_VERTICAL_UP){ 86 | startPoint = PointPool.obtain((fromView.getLeft()+fromView.getRight())/2f,fromView.getTop()); 87 | point1 = PointPool.obtain(startPoint.x,startPoint.y-DensityUtils.dp2px(context,15)); 88 | endPoint = PointPool.obtain((toView.getLeft()+toView.getRight())/2f,toView.getBottom()); 89 | point2 = PointPool.obtain(endPoint.x,startPoint.y); 90 | 91 | }else{ 92 | super.draw(drawInfo); 93 | return; 94 | } 95 | 96 | mPaint.reset(); 97 | mPath.reset(); 98 | //set paint 99 | mPaint.setColor(lineColor); 100 | mPaint.setStyle(Paint.Style.STROKE); 101 | mPaint.setStrokeWidth(DensityUtils.dp2px(context,lineWidth)); 102 | mPaint.setAntiAlias(true); 103 | mPath.moveTo(startPoint.x,startPoint.y); 104 | mPath.cubicTo( 105 | point1.x,point1.y, 106 | point2.x,point2.y, 107 | endPoint.x,endPoint.y); 108 | //do not forget release 109 | PointPool.free(startPoint); 110 | PointPool.free(point1); 111 | PointPool.free(point2); 112 | PointPool.free(endPoint); 113 | //draw 114 | canvas.drawPath(mPath,mPaint); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /library/src/main/java/com/gyso/treeview/line/StraightLine.java: -------------------------------------------------------------------------------- 1 | package com.gyso.treeview.line; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.graphics.Path; 8 | import android.graphics.PointF; 9 | import android.view.View; 10 | 11 | import com.gyso.treeview.adapter.DrawInfo; 12 | import com.gyso.treeview.adapter.TreeViewHolder; 13 | import com.gyso.treeview.cache_pool.PointPool; 14 | import com.gyso.treeview.layout.TreeLayoutManager; 15 | import com.gyso.treeview.model.NodeModel; 16 | import com.gyso.treeview.util.DensityUtils; 17 | 18 | /** 19 | * @Author: 怪兽N 20 | * @Time: 2021/5/8 9:40 21 | * @Email: 674149099@qq.com 22 | * @WeChat: guaishouN 23 | * @Describe: 24 | * Straight Line 25 | */ 26 | public class StraightLine extends BaseLine { 27 | public static final int DEFAULT_LINE_WIDTH_DP = 3; 28 | private int lineColor = Color.parseColor("#055287"); 29 | private int lineWidth = DEFAULT_LINE_WIDTH_DP; 30 | public StraightLine() { 31 | super(); 32 | } 33 | 34 | public StraightLine(int lineColor, int lineWidth_dp) { 35 | this(); 36 | this.lineColor = lineColor; 37 | this.lineWidth = lineWidth_dp; 38 | } 39 | 40 | public void setLineColor(int lineColor) { 41 | this.lineColor = lineColor; 42 | } 43 | 44 | public void setLineWidth(int lineWidth) { 45 | this.lineWidth = lineWidth; 46 | } 47 | 48 | @Override 49 | public void draw(DrawInfo drawInfo) { 50 | Canvas canvas = drawInfo.getCanvas(); 51 | TreeViewHolder fromHolder = drawInfo.getFromHolder(); 52 | TreeViewHolder toHolder = drawInfo.getToHolder(); 53 | Paint mPaint = drawInfo.getPaint(); 54 | Path mPath = drawInfo.getPath(); 55 | int holderLayoutType = toHolder.getHolderLayoutType(); 56 | int spacePeerToPeer = drawInfo.getSpacePeerToPeer(); 57 | int spaceParentToChild = drawInfo.getSpaceParentToChild(); 58 | //get view and node 59 | View fromView = fromHolder.getView(); 60 | NodeModel fromNode = fromHolder.getNode(); 61 | View toView = toHolder.getView(); 62 | NodeModel toNode = toHolder.getNode(); 63 | Context context = fromView.getContext(); 64 | 65 | 66 | PointF startPoint,endPoint; 67 | if(holderLayoutType == TreeLayoutManager.LAYOUT_TYPE_HORIZON_RIGHT){ 68 | startPoint = PointPool.obtain(fromView.getRight(),(fromView.getTop()+fromView.getBottom())/2f); 69 | endPoint = PointPool.obtain(toView.getLeft(),(toView.getTop()+toView.getBottom())/2f); 70 | 71 | }else if(holderLayoutType == TreeLayoutManager.LAYOUT_TYPE_HORIZON_LEFT){ 72 | startPoint = PointPool.obtain(fromView.getLeft(),(fromView.getTop()+fromView.getBottom())/2f); 73 | endPoint = PointPool.obtain(toView.getRight(),(toView.getTop()+toView.getBottom())/2f); 74 | 75 | }else if(holderLayoutType == TreeLayoutManager.LAYOUT_TYPE_VERTICAL_UP){ 76 | startPoint = PointPool.obtain((fromView.getLeft()+fromView.getRight())/2f,fromView.getTop()); 77 | endPoint = PointPool.obtain((toView.getLeft()+toView.getRight())/2f,toView.getBottom()); 78 | 79 | }else if (holderLayoutType == TreeLayoutManager.LAYOUT_TYPE_VERTICAL_DOWN){ 80 | startPoint = PointPool.obtain((fromView.getLeft()+fromView.getRight())/2f,fromView.getBottom()); 81 | endPoint = PointPool.obtain((toView.getLeft()+toView.getRight())/2f,toView.getTop()); 82 | 83 | }else{ 84 | super.draw(drawInfo); 85 | return; 86 | } 87 | 88 | //set paint 89 | mPaint.reset(); 90 | mPath.reset(); 91 | mPaint.setColor(lineColor); 92 | mPaint.setStyle(Paint.Style.STROKE); 93 | mPaint.setStrokeWidth(DensityUtils.dp2px(context,lineWidth)); 94 | mPaint.setAntiAlias(true); 95 | 96 | mPath.moveTo(startPoint.x,startPoint.y); 97 | mPath.lineTo(endPoint.x,endPoint.y); 98 | //release 99 | PointPool.free(startPoint); 100 | PointPool.free(endPoint); 101 | //draw 102 | canvas.drawPath(mPath,mPaint); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /library/src/main/java/com/gyso/treeview/listener/TreeViewControlListener.java: -------------------------------------------------------------------------------- 1 | package com.gyso.treeview.listener; 2 | 3 | import android.view.View; 4 | 5 | import androidx.annotation.Nullable; 6 | 7 | import com.gyso.treeview.model.NodeModel; 8 | 9 | /** 10 | * @Author: 怪兽N 11 | * @Time: 2021/6/15 12 | * @Email: 674149099@qq.com 13 | * @WeChat: guaishouN 14 | * @Describe: 15 | * Listener for drag-move node, scale, drag the hold treeView 16 | */ 17 | public interface TreeViewControlListener { 18 | int MIN_SCALE = -1; 19 | int FREE_SCALE = 0; 20 | int MAX_SCALE = 1; 21 | void onScaling(int state, int percent); 22 | void onDragMoveNodesHit(@Nullable NodeModel draggingNode, @Nullable NodeModel hittingNode, @Nullable View draggingView, @Nullable View hittingView); 23 | } 24 | -------------------------------------------------------------------------------- /library/src/main/java/com/gyso/treeview/listener/TreeViewItemClick.java: -------------------------------------------------------------------------------- 1 | package com.gyso.treeview.listener; 2 | 3 | import android.view.View; 4 | 5 | /** 6 | * guaishouN 674149099@qq.com 7 | */ 8 | 9 | public interface TreeViewItemClick { 10 | void onItemClick(View item); 11 | } 12 | -------------------------------------------------------------------------------- /library/src/main/java/com/gyso/treeview/listener/TreeViewItemLongClick.java: -------------------------------------------------------------------------------- 1 | package com.gyso.treeview.listener; 2 | 3 | import android.view.View; 4 | 5 | /** 6 | * guaishouN 674149099@qq.com 7 | */ 8 | 9 | public interface TreeViewItemLongClick { 10 | void onLongClick(View view); 11 | } 12 | -------------------------------------------------------------------------------- /library/src/main/java/com/gyso/treeview/listener/TreeViewNotifier.java: -------------------------------------------------------------------------------- 1 | package com.gyso.treeview.listener; 2 | 3 | 4 | import com.gyso.treeview.model.NodeModel; 5 | 6 | /** 7 | * @Author: 怪兽N 8 | * @Time: 2021/4/23 9 | * @Email: 674149099@qq.com 10 | * @WeChat: guaishouN 11 | * @Describe: 12 | */ 13 | public interface TreeViewNotifier{ 14 | void onDataSetChange(); 15 | void onRemoveNode(NodeModelnodeModel); 16 | void onRemoveChildNodes(NodeModel parentNode); 17 | void onItemViewChange(NodeModel nodeModel); 18 | void onAddNodes(NodeModel parent, NodeModel... childNodes); 19 | } -------------------------------------------------------------------------------- /library/src/main/java/com/gyso/treeview/model/ITraversal.java: -------------------------------------------------------------------------------- 1 | package com.gyso.treeview.model; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * traversal callback 7 | * guaishouN 674149099@qq.com 8 | */ 9 | 10 | public interface ITraversal extends Serializable { 11 | void next(T next); 12 | default void finish(){} 13 | } 14 | -------------------------------------------------------------------------------- /library/src/main/java/com/gyso/treeview/model/TreeModel.java: -------------------------------------------------------------------------------- 1 | package com.gyso.treeview.model; 2 | 3 | import android.util.SparseArray; 4 | import java.io.Serializable; 5 | import java.util.ArrayDeque; 6 | import java.util.Deque; 7 | import java.util.LinkedList; 8 | import java.util.List; 9 | import java.util.Stack; 10 | 11 | /** 12 | * manager the tree data nodes 13 | * guaishouN 674149099@qq.com 14 | */ 15 | public class TreeModel implements Serializable { 16 | private static final String TAG = TreeModel.class.getSimpleName(); 17 | /** 18 | * the root for the tree 19 | */ 20 | private NodeModel rootNode; 21 | private SparseArray> arrayByFloor = new SparseArray<>(10); 22 | private transient ITraversal> iTraversal; 23 | private int maxDeep =0; 24 | private int minDeep =0; 25 | public TreeModel(NodeModel rootNode) { 26 | this.rootNode = rootNode; 27 | } 28 | 29 | private boolean finishTraversal = false; 30 | 31 | /** 32 | * add the node in some father node 33 | * @param parent 34 | * @param childNodes 35 | */ 36 | @SafeVarargs 37 | public final void addNode(NodeModel parent, NodeModel... childNodes) { 38 | if(parent!=null&&childNodes!=null && childNodes.length>0){ 39 | parent.treeModel = this; 40 | List> nodeModels = new LinkedList<>(); 41 | for (int i = 0; i < childNodes.length; i++) { 42 | nodeModels.add((NodeModel)childNodes[i]); 43 | childNodes[i].treeModel = this; 44 | } 45 | ((NodeModel)parent).addChildNodes(nodeModels); 46 | for(NodeModel child:childNodes){ 47 | child.traverseIncludeSelf(next->{ 48 | next.floor = next.parentNode.floor+1; 49 | List floorList = getFloorList(next.floor); 50 | floorList.add(next); 51 | }); 52 | } 53 | } 54 | } 55 | 56 | /** 57 | * remove 58 | * @param parent p node 59 | * @param childNode c node 60 | */ 61 | public void removeNode(NodeModel parent, NodeModel childNode) { 62 | if(parent!=null&&childNode!=null){ 63 | parent.removeChildNode(childNode); 64 | childNode.traverseIncludeSelf(next->{ 65 | List nf = getFloorList(next.floor); 66 | nf.remove(next); 67 | next.floor = 0; 68 | }); 69 | } 70 | } 71 | 72 | public NodeModel getRootNode() { 73 | return rootNode; 74 | } 75 | 76 | 77 | /** 78 | *child nodes will ergodic in the last 79 | * 广度遍历 80 | * breadth search 81 | * For every floor , child nodes has been sort 0--->n; 82 | * And node will been display one by one floor. 83 | */ 84 | private void ergodicTreeByFloor() { 85 | Deque> deque = new ArrayDeque<>(); 86 | NodeModel rootNode = getRootNode(); 87 | deque.add(rootNode); 88 | while (!deque.isEmpty()) { 89 | rootNode = deque.poll(); 90 | if (iTraversal != null) { 91 | iTraversal.next(rootNode); 92 | } 93 | if(this.finishTraversal){ 94 | break; 95 | } 96 | if(rootNode==null){ 97 | continue; 98 | } 99 | LinkedList> childNodes = rootNode.getChildNodes(); 100 | if (childNodes.size() > 0) { 101 | deque.addAll(childNodes); 102 | } 103 | } 104 | if (iTraversal != null) { 105 | iTraversal.finish(); 106 | this.finishTraversal = false; 107 | } 108 | } 109 | 110 | public SparseArray> getArrayByFloor() { 111 | return arrayByFloor; 112 | } 113 | 114 | /** 115 | * @param floor level 116 | * @return all nodes in the same floor 117 | */ 118 | public List getFloorList(int floor){ 119 | LinkedList nodeModels = arrayByFloor.get(floor); 120 | if(nodeModels==null){ 121 | nodeModels = new LinkedList<>(); 122 | arrayByFloor.put(floor,nodeModels); 123 | } 124 | return nodeModels; 125 | } 126 | 127 | public void setFinishTraversal(boolean finishTraversal) { 128 | this.finishTraversal = finishTraversal; 129 | } 130 | 131 | public void doTraversalNodes(ITraversal> ITraversal){ 132 | doTraversalNodes(ITraversal,true); 133 | } 134 | 135 | /** 136 | * when ergodic this tree, it will call back on {@link ITraversal)} 137 | * @param ITraversal node 138 | */ 139 | public void doTraversalNodes(ITraversal> ITraversal, boolean isOrderByFloor) { 140 | this.iTraversal = ITraversal; 141 | this.finishTraversal = false; 142 | if(isOrderByFloor){ 143 | ergodicTreeByFloor(); 144 | }else{ 145 | ergodicTreeByDeep(); 146 | } 147 | } 148 | 149 | /** 150 | *child nodes will ergodic by deep 151 | * 深度遍历 152 | * depth search 153 | * For every child node list has been sort 0--->n; 154 | * And node will been display one then the children by deep until end. 155 | */ 156 | private void ergodicTreeByDeep(){ 157 | Stack> stack = new Stack<>(); 158 | NodeModel rootNode = getRootNode(); 159 | stack.add(rootNode); 160 | while (!stack.isEmpty()) { 161 | rootNode = stack.pop(); 162 | if (iTraversal != null) { 163 | iTraversal.next(rootNode); 164 | } 165 | if(this.finishTraversal){ 166 | break; 167 | } 168 | if(rootNode==null){ 169 | continue; 170 | } 171 | LinkedList> childNodes = rootNode.getChildNodes(); 172 | if (childNodes.size() > 0) { 173 | stack.addAll(childNodes); 174 | } 175 | } 176 | if (iTraversal != null) { 177 | iTraversal.finish(); 178 | this.finishTraversal = false; 179 | } 180 | } 181 | 182 | public int getMaxDeep() { 183 | return maxDeep; 184 | } 185 | 186 | public void setMaxDeep(int maxDeep) { 187 | this.maxDeep = maxDeep; 188 | } 189 | 190 | public int getMinDeep() { 191 | return minDeep; 192 | } 193 | 194 | public void setMinDeep(int minDeep) { 195 | this.minDeep = minDeep; 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /library/src/main/java/com/gyso/treeview/touch/DragBlock.java: -------------------------------------------------------------------------------- 1 | package com.gyso.treeview.touch; 2 | 3 | import android.graphics.PointF; 4 | import android.os.Handler; 5 | import android.os.Looper; 6 | import android.os.SystemClock; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.view.ViewParent; 10 | import android.view.animation.Interpolator; 11 | import android.widget.OverScroller; 12 | 13 | import com.gyso.treeview.R; 14 | import com.gyso.treeview.TreeViewContainer; 15 | import com.gyso.treeview.adapter.TreeViewHolder; 16 | import com.gyso.treeview.cache_pool.PointPool; 17 | import com.gyso.treeview.model.NodeModel; 18 | import com.gyso.treeview.util.ViewBox; 19 | 20 | import java.util.ArrayList; 21 | import java.util.HashMap; 22 | import java.util.LinkedList; 23 | import java.util.List; 24 | import java.util.Map; 25 | 26 | /** 27 | * @Author: 怪兽N 28 | * @Time: 2021/6/7 17:56 29 | * @Email: 674149099@qq.com 30 | * @WeChat: guaishouN 31 | * @Describe: 32 | * drag block 33 | */ 34 | public class DragBlock { 35 | private final List tmp; 36 | private volatile boolean isDragging; 37 | private final TreeViewContainer container; 38 | private final Map originPositionMap; 39 | private final OverScroller mScroller; 40 | private PointF prePointF = null; 41 | /** 42 | * Interpolator defining the animation curve for mScroller 43 | */ 44 | private static final Interpolator sInterpolator = t -> { 45 | t -= 1.0f; 46 | return t * t * t * t * t + 1.0f; 47 | }; 48 | 49 | public DragBlock(TreeViewContainer parent){ 50 | tmp = new ArrayList<>(); 51 | container = parent; 52 | this.mScroller = new OverScroller(container.getContext(), sInterpolator); 53 | originPositionMap = new HashMap<>(); 54 | } 55 | 56 | public boolean load(View view){ 57 | if(originPositionMap.isEmpty() && tmp.isEmpty()){ 58 | tmp.add(view); 59 | originPositionMap.put(view,new ViewBox(view)); 60 | addItem(view); 61 | return true; 62 | } 63 | return false; 64 | } 65 | 66 | private void addItem(View view){ 67 | Object tag = view.getTag(R.id.item_holder); 68 | if(tag instanceof TreeViewHolder){ 69 | TreeViewHolder holder = (TreeViewHolder)tag; 70 | NodeModel node = holder.getNode(); 71 | for (NodeModel n:node.getChildNodes()){ 72 | TreeViewHolder h = container.getTreeViewHolder(n); 73 | tmp.add(h.getView()); 74 | originPositionMap.put(h.getView(),new ViewBox(h.getView())); 75 | addItem(h.getView()); 76 | } 77 | } 78 | } 79 | 80 | public void drag(int dx, int dy){ 81 | if(!mScroller.isFinished()){ 82 | return; 83 | } 84 | this.isDragging = true; 85 | for (int i = 0; i < tmp.size(); i++) { 86 | View view = tmp.get(i); 87 | view.offsetLeftAndRight(dx); 88 | view.offsetTopAndBottom(dy); 89 | } 90 | } 91 | 92 | public void setDragging(boolean dragging) { 93 | isDragging = dragging; 94 | } 95 | 96 | private void autoRelease(){ 97 | if(mScroller.isFinished() && !isDragging){ 98 | originPositionMap.clear(); 99 | tmp.clear(); 100 | System.gc(); 101 | } 102 | } 103 | 104 | public void smoothRecover(View referenceView) { 105 | if(!mScroller.isFinished()){ 106 | return; 107 | } 108 | ViewBox rBox = originPositionMap.get(referenceView); 109 | if(rBox ==null){ 110 | return; 111 | } 112 | prePointF=PointPool.obtain(0f,0f); 113 | mScroller.startScroll(0,0,referenceView.getLeft()-rBox.left,referenceView.getTop()-rBox.top); 114 | } 115 | 116 | public boolean computeScroll() { 117 | boolean isSuc = mScroller.computeScrollOffset(); 118 | if(isSuc){ 119 | PointF curPointF = PointPool.obtain(mScroller.getCurrX(), mScroller.getCurrY()); 120 | mScroller.getCurrY(); 121 | for (int i = 0; i < tmp.size(); i++) { 122 | View view = tmp.get(i); 123 | int dx = (int)(curPointF.x-prePointF.x); 124 | int dy = (int)(curPointF.y-prePointF.y); 125 | view.offsetLeftAndRight(-dx); 126 | view.offsetTopAndBottom(-dy); 127 | } 128 | if(prePointF!=null){ 129 | prePointF.set(curPointF); 130 | } 131 | PointPool.free(curPointF); 132 | return true; 133 | } 134 | autoRelease(); 135 | return false; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /library/src/main/java/com/gyso/treeview/util/DensityUtils.java: -------------------------------------------------------------------------------- 1 | package com.gyso.treeview.util; 2 | 3 | import android.content.Context; 4 | import android.util.TypedValue; 5 | 6 | /** 7 | * 单位转换 工具类
8 | */ 9 | public class DensityUtils { 10 | 11 | /** 12 | * dp转px 13 | */ 14 | public static int dp2px(Context context, float dpVal) { 15 | int result = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpVal, context.getResources() 16 | .getDisplayMetrics()); 17 | return result; 18 | } 19 | 20 | /** 21 | * sp转px 22 | */ 23 | public static int sp2px(Context context, float spVal) { 24 | int result = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spVal, context.getResources() 25 | .getDisplayMetrics()); 26 | return result; 27 | } 28 | 29 | /** 30 | * px转dp 31 | */ 32 | public static int px2dp(Context context, float pxVal) { 33 | final float scale = context.getResources().getDisplayMetrics().density; 34 | int result = (int) (pxVal / scale); 35 | return result; 36 | } 37 | 38 | /** 39 | * px转sp 40 | */ 41 | public static float px2sp(Context context, float pxVal) { 42 | int result = (int) (pxVal / context.getResources().getDisplayMetrics().scaledDensity); 43 | return result; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /library/src/main/java/com/gyso/treeview/util/Interpolators.java: -------------------------------------------------------------------------------- 1 | package com.gyso.treeview.util; 2 | 3 | 4 | import android.view.animation.AccelerateDecelerateInterpolator; 5 | import android.view.animation.AccelerateInterpolator; 6 | import android.view.animation.BounceInterpolator; 7 | import android.view.animation.DecelerateInterpolator; 8 | import android.view.animation.Interpolator; 9 | import android.view.animation.LinearInterpolator; 10 | import android.view.animation.PathInterpolator; 11 | 12 | 13 | /** 14 | * @Author: 怪兽N 15 | * @Time: 2021/6/10 16:24 16 | * @Email: 674149099@qq.com 17 | * @WeChat: guaishouN 18 | * @Describe: 19 | * Utility class to receive interpolators from 20 | */ 21 | public class Interpolators { 22 | public static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f); 23 | 24 | /** 25 | * Like {@link #FAST_OUT_SLOW_IN}, but used in case the animation is played in reverse (i.e. t 26 | * goes from 1 to 0 instead of 0 to 1). 27 | */ 28 | public static final Interpolator FAST_OUT_SLOW_IN_REVERSE = 29 | new PathInterpolator(0.8f, 0f, 0.6f, 1f); 30 | public static final Interpolator FAST_OUT_LINEAR_IN = new PathInterpolator(0.4f, 0f, 1f, 1f); 31 | public static final Interpolator LINEAR_OUT_SLOW_IN = new PathInterpolator(0f, 0f, 0.2f, 1f); 32 | public static final Interpolator ALPHA_IN = new PathInterpolator(0.4f, 0f, 1f, 1f); 33 | public static final Interpolator ALPHA_OUT = new PathInterpolator(0f, 0f, 0.8f, 1f); 34 | public static final Interpolator LINEAR = new LinearInterpolator(); 35 | public static final Interpolator ACCELERATE = new AccelerateInterpolator(); 36 | public static final Interpolator ACCELERATE_DECELERATE = new AccelerateDecelerateInterpolator(); 37 | public static final Interpolator DECELERATE_QUINT = new DecelerateInterpolator(2.5f); 38 | public static final Interpolator CUSTOM_40_40 = new PathInterpolator(0.4f, 0f, 0.6f, 1f); 39 | public static final Interpolator ICON_OVERSHOT = new PathInterpolator(0.4f, 0f, 0.2f, 1.4f); 40 | public static final Interpolator PANEL_CLOSE_ACCELERATED 41 | = new PathInterpolator(0.3f, 0, 0.5f, 1); 42 | public static final Interpolator BOUNCE = new BounceInterpolator(); 43 | 44 | /** 45 | * Interpolator to be used when animating a move based on a click. Pair with enough duration. 46 | */ 47 | public static final Interpolator TOUCH_RESPONSE = 48 | new PathInterpolator(0.3f, 0f, 0.1f, 1f); 49 | 50 | /** 51 | * Like {@link #TOUCH_RESPONSE}, but used in case the animation is played in reverse (i.e. t 52 | * goes from 1 to 0 instead of 0 to 1). 53 | */ 54 | public static final Interpolator TOUCH_RESPONSE_REVERSE = 55 | new PathInterpolator(0.9f, 0f, 0.7f, 1f); 56 | } 57 | -------------------------------------------------------------------------------- /library/src/main/java/com/gyso/treeview/util/TreeViewLog.java: -------------------------------------------------------------------------------- 1 | package com.gyso.treeview.util; 2 | 3 | import android.util.Log; 4 | 5 | import com.gyso.treeview.BuildConfig; 6 | 7 | /** 8 | * @Author: 怪兽N 9 | * @Time: 2021/5/8 15:10 10 | * @Email: 674149099@qq.com 11 | * @WeChat: guaishouN 12 | * @Describe: 13 | * logger 14 | */ 15 | public class TreeViewLog{ 16 | private static boolean isDebug = BuildConfig.isDebug; 17 | public static void d(String tag, String msg){ 18 | if(isDebug){ 19 | Log.d(tag, msg); 20 | } 21 | } 22 | public static void e(String tag, String msg){ 23 | if(isDebug){ 24 | Log.e(tag, msg); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /library/src/main/java/com/gyso/treeview/util/ViewBox.java: -------------------------------------------------------------------------------- 1 | package com.gyso.treeview.util; 2 | 3 | import android.graphics.Matrix; 4 | import android.view.View; 5 | 6 | public class ViewBox { 7 | 8 | public int top; 9 | public int left; 10 | public int right; 11 | public int bottom; 12 | 13 | public ViewBox(){ 14 | 15 | } 16 | public ViewBox(View view){ 17 | this(view.getTop(),view.getLeft(),view.getBottom(),view.getRight()); 18 | } 19 | 20 | public ViewBox(int top, int left, int bottom, int right) { 21 | this.top = top; 22 | this.left = left; 23 | this.right = right; 24 | this.bottom = bottom; 25 | } 26 | 27 | public void setValues(ViewBox viewBox) { 28 | this.top = viewBox.top; 29 | this.left = viewBox.left; 30 | this.right = viewBox.right; 31 | this.bottom = viewBox.bottom; 32 | } 33 | 34 | public void setValues(int top, int left,int bottom,int right) { 35 | this.top = top; 36 | this.left = left; 37 | this.bottom = bottom; 38 | this.right = right; 39 | } 40 | 41 | public static ViewBox getViewBox(View view) { 42 | return new ViewBox(view); 43 | } 44 | 45 | /** 46 | *.scaleX(centerM[0]) 47 | *.translationX(centerM[2]) 48 | *.scaleY(centerM[4]) 49 | *.translationY(centerM[5]) 50 | * 51 | *float left = v.getLeft()*v.getScaleX()+v.getTranslationX(); 52 | *float top = v.getTop()*v.getScaleY()+v.getTranslationY(); 53 | *float right = v.getRight()*v.getScaleX()+v.getTranslationX(); 54 | *float bottom = v.getBottom()*v.getScaleY()+v.getTranslationY(); 55 | */ 56 | public ViewBox convert(Matrix matrix) { 57 | float[] fs = new float[9]; 58 | matrix.getValues(fs); 59 | float scaleX = fs[Matrix.MSCALE_X]; 60 | float scaleY = fs[Matrix.MSCALE_Y]; 61 | float translX = fs[Matrix.MTRANS_X]; 62 | float translY = fs[Matrix.MTRANS_Y]; 63 | float leftI = left*scaleX+translX; 64 | float topI = top*scaleY+translY; 65 | float rightI = right*scaleX+translX; 66 | float bottomI = bottom*scaleY+translY; 67 | return new ViewBox((int)topI,(int)leftI,(int)bottomI,(int)rightI); 68 | } 69 | 70 | public ViewBox invert(Matrix matrix){ 71 | float[] fs = new float[9]; 72 | matrix.getValues(fs); 73 | float scaleX = fs[Matrix.MSCALE_X]; 74 | float scaleY = fs[Matrix.MSCALE_Y]; 75 | float translX = fs[Matrix.MTRANS_X]; 76 | float translY = fs[Matrix.MTRANS_Y]; 77 | float leftI = (left-translX)/scaleX; 78 | float topI = (top-translY)/scaleY; 79 | float rightI = (right-translX)/scaleX; 80 | float bottomI = (bottom-translY)/scaleY; 81 | setValues((int)topI,(int)leftI,(int)rightI,(int)bottomI); 82 | return new ViewBox((int)topI,(int)leftI,(int)bottomI,(int)rightI); 83 | } 84 | 85 | public boolean contain(ViewBox other){ 86 | return other!=null && 87 | top<=other.top && 88 | left<=other.left && 89 | right>=other.right && 90 | bottom>=other.bottom; 91 | } 92 | 93 | public ViewBox multiply(float radio) { 94 | return new ViewBox( 95 | (int)(top * radio), 96 | (int)(left * radio), 97 | (int)(bottom * radio), 98 | (int)(right * radio) 99 | ); 100 | } 101 | 102 | public ViewBox add(ViewBox other) { 103 | if (other == null) { 104 | return this; 105 | } 106 | return new ViewBox( 107 | top + other.top, 108 | left + other.left, 109 | bottom + other.bottom, 110 | right + other.right 111 | ); 112 | } 113 | 114 | public ViewBox subtract(ViewBox other){ 115 | if (other == null) { 116 | return this; 117 | } 118 | return new ViewBox( 119 | top - other.top, 120 | left - other.left, 121 | bottom - other.bottom, 122 | right - other.right 123 | ); 124 | } 125 | 126 | /** 127 | * get the box height which added 2*dy 128 | * @return height 129 | */ 130 | public int getHeight(){ 131 | return bottom-top; 132 | } 133 | 134 | /** 135 | * get the box width which added 2*dy 136 | * @return width 137 | */ 138 | public int getWidth(){ 139 | return right-left; 140 | } 141 | 142 | public void clear() { 143 | this.top = 0; 144 | this.left = 0; 145 | this.right = 0; 146 | this.bottom = 0; 147 | } 148 | 149 | @Override 150 | public String toString() { 151 | return "{" + 152 | "t:" + top + 153 | " l:" + left + 154 | " b:" + bottom + 155 | " r:" + right + 156 | '}'; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /library/src/main/res/values/values.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | #E1BEE7 16 | -------------------------------------------------------------------------------- /library/src/test/java/com/gyso/treeview/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.gyso.treeview; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /local.properties: -------------------------------------------------------------------------------- 1 | ## This file must *NOT* be checked into Version Control Systems, 2 | # as it contains information specific to your local configuration. 3 | # 4 | # Location of the SDK. This is only used by Gradle. 5 | # For customization when using a Version Control System, please read the 6 | # header note. 7 | #Tue Mar 22 09:08:11 CST 2022 8 | #sdk.dir=/home/V01.NET/uidq3640/Downloads/android-sdk-linux 9 | sdk.dir=D\:\\programfiles\\sdk -------------------------------------------------------------------------------- /sample/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /sample/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 29 5 | buildToolsVersion "29.0.3" 6 | 7 | defaultConfig { 8 | applicationId "com.gyso.gysotreeviewapplication" 9 | minSdkVersion 21 10 | targetSdkVersion 30 11 | versionCode 1 12 | versionName "1.0" 13 | 14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 15 | } 16 | 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | 24 | buildFeatures { 25 | dataBinding = true 26 | viewBinding = true 27 | } 28 | compileOptions { 29 | sourceCompatibility JavaVersion.VERSION_1_8 30 | targetCompatibility JavaVersion.VERSION_1_8 31 | } 32 | } 33 | 34 | dependencies { 35 | implementation fileTree(dir: "libs", include: ["*.jar"]) 36 | implementation 'androidx.appcompat:appcompat:1.2.0' 37 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4' 38 | implementation "com.google.android.material:material:1.2.1" 39 | implementation project(path: ':library') 40 | testImplementation 'junit:junit:4.12' 41 | androidTestImplementation 'androidx.test.ext:junit:1.1.2' 42 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' 43 | } -------------------------------------------------------------------------------- /sample/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /sample/src/androidTest/java/com/gyso/gysotreeviewapplication/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.gyso.gysotreeviewapplication; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | assertEquals("com.gyso.gysotreeviewapplication", appContext.getPackageName()); 25 | } 26 | } -------------------------------------------------------------------------------- /sample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /sample/src/main/java/com/gyso/gysotreeviewapplication/base/Animal.java: -------------------------------------------------------------------------------- 1 | package com.gyso.gysotreeviewapplication.base; 2 | 3 | /** 4 | * @Author: 怪兽N 5 | * @Time: 2021/5/7 19:12 6 | * @Email: 674149099@qq.com 7 | * @WeChat: guaishouN 8 | * @Describe: 9 | * node bean 10 | */ 11 | public class Animal { 12 | public int headId; 13 | public String name; 14 | public Animal(int headId, String name) { 15 | this.headId = headId; 16 | this.name = name; 17 | } 18 | 19 | @Override 20 | public String toString() { 21 | return "Animal["+name+"]"; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /sample/src/main/java/com/gyso/gysotreeviewapplication/base/AnimalTreeViewAdapter.java: -------------------------------------------------------------------------------- 1 | package com.gyso.gysotreeviewapplication.base; 2 | 3 | import android.graphics.Color; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.ImageView; 8 | import android.widget.TextView; 9 | 10 | import androidx.annotation.NonNull; 11 | 12 | import com.gyso.gysotreeviewapplication.R; 13 | import com.gyso.gysotreeviewapplication.databinding.NodeBaseLayoutBinding; 14 | import com.gyso.treeview.adapter.DrawInfo; 15 | import com.gyso.treeview.adapter.TreeViewAdapter; 16 | import com.gyso.treeview.adapter.TreeViewHolder; 17 | import com.gyso.treeview.line.BaseLine; 18 | import com.gyso.treeview.line.DashLine; 19 | import com.gyso.treeview.model.NodeModel; 20 | 21 | /** 22 | * @Author: 怪兽N 23 | * @Time: 2021/4/23 16:48 24 | * @Email: 674149099@qq.com 25 | * @WeChat: guaishouN 26 | * @Describe: 27 | * Tree View Adapter for node data to view 28 | */ 29 | public class AnimalTreeViewAdapter extends TreeViewAdapter { 30 | private DashLine dashLine = new DashLine(Color.parseColor("#F06292"),6); 31 | private OnItemClickListener listener; 32 | 33 | public void setOnItemListener(OnItemClickListener listener) { 34 | this.listener = listener; 35 | } 36 | 37 | @Override 38 | public TreeViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, NodeModel node) { 39 | NodeBaseLayoutBinding nodeBinding = NodeBaseLayoutBinding.inflate(LayoutInflater.from(viewGroup.getContext()),viewGroup,false); 40 | return new TreeViewHolder<>(nodeBinding.getRoot(),node); 41 | } 42 | 43 | @Override 44 | public void onBindViewHolder(@NonNull TreeViewHolder holder) { 45 | //todo get view and node from holder, and then show by you 46 | View itemView = holder.getView(); 47 | NodeModel node = holder.getNode(); 48 | TextView nameView = itemView.findViewById(R.id.name); 49 | ImageView headView = itemView.findViewById(R.id.portrait); 50 | final Animal animal = node.value; 51 | nameView.setText(animal.name); 52 | headView.setImageResource(animal.headId); 53 | headView.setOnClickListener(v -> { 54 | if(listener!=null){ 55 | listener.onItemClick(v,node); 56 | } 57 | }); 58 | } 59 | 60 | @Override 61 | public BaseLine onDrawLine(DrawInfo drawInfo) { 62 | // TODO If you return an BaseLine, line will be draw by the return one instead of TreeViewLayoutManager's 63 | // TreeViewHolder toHolder = drawInfo.getToHolder(); 64 | // NodeModel node = toHolder.getNode(); 65 | // Object value = node.getValue(); 66 | // if(value instanceof Animal){ 67 | // Animal animal = (Animal) value; 68 | // if("sub4".compareToIgnoreCase(animal.name)<=0){ 69 | // return dashLine; 70 | // } 71 | // } 72 | return null; 73 | } 74 | 75 | public interface OnItemClickListener{ 76 | void onItemClick(View item, NodeModel node); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/card_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/drag_mode_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/ic_07.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/ic_16.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/ic_launcher_tree.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/percent_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 20 | 21 | 26 | 40 | 41 | 55 | 56 | 70 | 71 | 85 | 86 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/node_base_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 18 | 29 | -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guaishouN/android-thinkmap-treeview/285c2f3e2163abbe5644b080286b8030ac06409a/sample/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guaishouN/android-thinkmap-treeview/285c2f3e2163abbe5644b080286b8030ac06409a/sample/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guaishouN/android-thinkmap-treeview/285c2f3e2163abbe5644b080286b8030ac06409a/sample/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guaishouN/android-thinkmap-treeview/285c2f3e2163abbe5644b080286b8030ac06409a/sample/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guaishouN/android-thinkmap-treeview/285c2f3e2163abbe5644b080286b8030ac06409a/sample/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guaishouN/android-thinkmap-treeview/285c2f3e2163abbe5644b080286b8030ac06409a/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guaishouN/android-thinkmap-treeview/285c2f3e2163abbe5644b080286b8030ac06409a/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guaishouN/android-thinkmap-treeview/285c2f3e2163abbe5644b080286b8030ac06409a/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guaishouN/android-thinkmap-treeview/285c2f3e2163abbe5644b080286b8030ac06409a/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guaishouN/android-thinkmap-treeview/285c2f3e2163abbe5644b080286b8030ac06409a/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /sample/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #6200EE 4 | #3700B3 5 | #03DAC5 6 | #EEE9E9 7 | #055287 8 | #E57373 9 | #F06292 10 | #F1286C 11 | #80055287 12 | -------------------------------------------------------------------------------- /sample/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | GysoTreeViewApplication 3 | -------------------------------------------------------------------------------- /sample/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 13 | -------------------------------------------------------------------------------- /sample/src/test/java/com/gyso/gysotreeviewapplication/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.gyso.gysotreeviewapplication; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':library' 2 | include ':sample' 3 | rootProject.name = "GysoTreeViewApplication" --------------------------------------------------------------------------------