1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
package com.application.zhangshi_app_android.widget;
 
import android.content.Context;
import android.graphics.Color;
import android.graphics.Path;
import android.graphics.Typeface;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
 
import androidx.constraintlayout.utils.widget.ImageFilterView;
 
import com.android.app_base.manager.AppManager;
import com.android.app_base.utils.GlideUtil;
import com.android.app_base.utils.ScreenSizeUtils;
import com.application.zhangshi_app_android.R;
import com.application.zhangshi_app_android.bean.HomeRootBean;
 
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
 
/**
 * @author Ljj
 * @date 2023.08.13. 6:47
 * @desc
 */
public abstract class Node{
    protected Context context;
    protected HomeRootBean member;//成员
    protected Node parent;//父节点
    protected final List<Node> children = new ArrayList<>(); //子节点
    protected HomeMindMapLayout.OnItemClickListener onItemClickListener;
    protected static final int pathLength = ScreenSizeUtils.dip2px(20);// 连线的一般长度
    protected static final int  cornerRadius = ScreenSizeUtils.dip2px(5); // 连线拐角 圆角半径
 
    protected static final int spouseMargin = ScreenSizeUtils.dip2px(15);// 配偶两个view之间的间隔
    protected static final int verticalMargin = ScreenSizeUtils.dip2px(20);//    //节点竖直方向间距
    protected static final int horizontalMargin = 2 * pathLength;//  分叉节点之间的间距
    protected boolean isExpand = true;//是否展开子节点
    protected boolean isVisible = true;//是否可见
    protected int width, height;// 当前节点的宽高
    protected int viewLeft;// view的左边
    protected int viewRight;// view右边
    protected int viewTop;// view的上边
    protected int viewBottom;// view的下边
 
    protected int left;// 节点左边
    protected int right;// 节点右边
    protected int top;// 节点顶部
    protected int bottom;// 节点底部
    protected int centerY;// 节点中间Y
    protected final List<Path> nextPathList = new ArrayList<>();//后置连线
    protected final List<Path> prePathList = new ArrayList<>();//前置连线
    protected final List<Path> forkPathList = new ArrayList<>();//分叉连线
    protected final List<View> viewList = new ArrayList<>();
 
    public Node(Context context,HomeRootBean member){
        this.context = context;
        this.member = member;
        if (member != null) {
            // 递归创建子节点
            if (member.getChildList() != null && member.getChildList().size() > 0) {
                for (HomeRootBean child : member.getChildList()) {
                    // 根据是否有配偶创建不同的节点
                    if (child != null) {
                        Node node;
                        if (child.getSpouse() != null) {
                            node = new DoubleNode(context,child);
                        } else {
                            node = new SimpleNode(context,child);
                        }
                        node.parent = this;
                        children.add(node);
                    }
                }
            }
        }
 
    }
 
    /**
     * 初始化所有节点
     * @param x 所有节点最左边的x坐标
     * @param y 所有节点最高的y坐标
     */
    public void setPosition(int x,int y){
        if (this.parent!=null){
            throw new RuntimeException("只能初始化根节点");
        }
        //根据可见性找到最后一个第一子节点,即最上边的节点
        setViewPositionRecursion(getVisibleTopNode(), this,x,y);
    }
 
    /**
     * 递归设置所有节点的位置
     * @param topNode 顶部节点,这颗节点树的最上边的节点
     * @param rootNode 根节点,这颗节点树的根节点
     *                startNode 必须是 topNode 祖先节点
     * @param x 整颗节点树的x坐标
     * @param y 整颗节点树的y坐标
     */
    public void setViewPositionRecursion(Node topNode,Node rootNode,int x,int y){
        if (topNode == rootNode) {
            //如果顶部节点就是根节点,那么根节点的位置就是x,y,不需要继续递归
            rootNode.setViewPosition(x, y + rootNode.getAllVisibleHeight()/2);
            return;
        }
 
        //因为 y 是整颗节点树的y坐标,正常情况下,y都会是 顶部节点topNode 的y坐标
        //但是 有一种情况,就是顶部节点topNode刚好是根节点rootNode的唯一子节点,且topNode没有配偶,此时topNode的高度比rootNode的高度小,所以此时的y是rootNode的y坐标
        int centerY;
        if (rootNode.height >= topNode.height && rootNode == topNode.parent && rootNode.children.size() == 1){
            centerY = y + rootNode.getAllVisibleHeight()/2;
        }else {
            centerY = y + topNode.getAllVisibleHeight()/2;
        }
        topNode.setViewPosition(x + topNode.getToSpecifyNodeWidth(rootNode) - topNode.width, centerY);
        // 根据topNode的位置,递归设置所有节点的位置
        Node node = topNode;
        while (node != rootNode) {
            Node parentNode = node.parent;
            List<Integer> centerYList = new ArrayList<>();
            int currentBottom = 0;
            for (Node child : parentNode.children) {
                if (child == node) {
                    centerYList.add(child.centerY);
                    currentBottom = currentBottom + node.getVisibleTopNode().top + node.getAllVisibleHeight();
                    continue;
                }
                if (!child.isVisible){
                    continue;
                }
                // 递归设置以child为根节点的所有节点的位置
                setViewPositionRecursion(child.getVisibleTopNode(), child,  node.left,currentBottom + verticalMargin);
 
                currentBottom = currentBottom + verticalMargin + child.getAllVisibleHeight();
                centerYList.add(child.centerY);
            }
 
            //取最大值、最小值 和 的中间
            int totalCenterY = 0;
            if (centerYList.size() > 0) {
                int min = centerYList.get(0);
                int max = centerYList.get(0);
                for (int i = 1; i < centerYList.size(); i++) {
                    int centerY1 = centerYList.get(i);
                    if (centerY1 < min) {
                        min = centerY1;
                    }
                    if (centerY1 > max) {
                        max = centerY1;
                    }
                }
                totalCenterY = (min + max) / 2;
            }
 
            if (parentNode.children.size() > 1) {
                // 设置父节点的位置
                parentNode.setViewPosition(node.left - horizontalMargin - parentNode.width, totalCenterY);
                // 设置分叉节点的位置
                parentNode.setForkYList(centerYList);
            }else {
                parentNode.setViewPosition(node.left - parentNode.width, totalCenterY);
            }
            node = parentNode;
        }
    }
 
    public Node getVisibleTopNode(){
        Node topNode;
        if (!isVisible){
            return null;
        }
        if (!isExpand){
            return this;
        }
        if (children.size() > 0) {
            topNode = children.get(0).getVisibleTopNode();
        } else {
            topNode = this;
        }
        return topNode;
    }
 
    // 设置对应类型节点的位置 由子类实现
    protected abstract void setViewPosition(int x, int centerY);
    protected abstract void setForkYList(List<Integer> yList);
 
    /**
     *  创建节点item视图
     * @param bean 节点数据
     * @param isFamily 是否是本家成员
     * @return 节点item视图
     */
    protected View createItemView(HomeRootBean bean,boolean isFamily){
        // 使用布局填充器加载节点布局
        LayoutInflater inflater = LayoutInflater.from(context);
        View familyMemberView = inflater.inflate(R.layout.item_family_member, null, false);
        familyMemberView.measure(View.MeasureSpec.makeMeasureSpec((1 << 30) - 1, View.MeasureSpec.AT_MOST),
                View.MeasureSpec.makeMeasureSpec((1 << 30) - 1, View.MeasureSpec.AT_MOST));
 
        // 防止addView时,所有这里创建的view的高宽被限制成一样的
        ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        familyMemberView.setLayoutParams(params);
 
        if (bean == null) {
            return familyMemberView;
        }
        // 获取布局中的组件
        TextView tv_generation = familyMemberView.findViewById(R.id.tv_generation);
        ImageFilterView iv_avatar = familyMemberView.findViewById(R.id.iv_avatar);
        TextView tv_name = familyMemberView.findViewById(R.id.tv_name);
 
        if (!isFamily){
            tv_name.setTextColor(Color.parseColor("#FF8296C5"));
            tv_name.setTypeface(Typeface.DEFAULT);
        }
        // 设置节点内容
        tv_generation.setText(String.valueOf(bean.getIdentity()));
        if (bean.getImg() != null) {
            GlideUtil.loadImage(bean.getImg(), iv_avatar);
        }
        tv_name.setText(bean.getNickName());
        tv_name.setSelected(true);
        familyMemberView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (onItemClickListener != null){
                    onItemClickListener.onItemClick(familyMemberView,bean);
                }
            }
        });
        return familyMemberView;
    }
    protected ImageView createExpandIconView() {
        ImageView expandView = new ImageView(AppManager.getAppManager().currentActivity());
        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(pathLength,pathLength);
        expandView.setLayoutParams(params);
        int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(pathLength, View.MeasureSpec.EXACTLY);
        int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(pathLength, View.MeasureSpec.EXACTLY);
        expandView.measure(widthMeasureSpec,heightMeasureSpec);
        return expandView;
    }
 
    public void setVisible(boolean visible) {
        isVisible = visible;
        setViewVisible(visible);
        if (!visible){//不可见时 子节点也不可见
            for (Node child : children) {
                child.setVisible(false);
            }
        }else { //可见时,子节点可见性由是否展开决定
            for (Node child : children) {
                child.setVisible(isExpand);
            }
        }
    }
 
    private void setViewVisible(boolean visible){
        for (View view : viewList) {
            view.setVisibility(visible?View.VISIBLE:View.GONE);
        }
    }
 
 
    //计算从根节点到当前节点的 所经过 的 分叉节点数(有多个子节点的节点)
    public int calculateForkCountRecursion(Node node){
        if (node == null) {
            return 0;
        }
        if (node.parent == null){
            return 0;
        }
        if (node.parent.children.size() > 1){
            return 1 + calculateForkCountRecursion(node.parent);
        }else {
            return calculateForkCountRecursion(node.parent);
        }
    }
 
    public int getAllWidth(){
        int maxWidth = 0;
        for (Node deepestVisibleNode : findDeepestNodes(false)) {
            int width = deepestVisibleNode.getToSpecifyNodeWidth(null);
            if (width > maxWidth){
                maxWidth = width;
            }
        }
        return maxWidth;
    }
 
    //获取 ”显示出 以当前节点为根节点 的 所有可见节点 “所需的宽度
    public int getAllVisibleWidth(){
        int maxWidth = 0;
        if (isVisible){
            for (Node deepestVisibleNode : findDeepestNodes(true)) {
                int width = deepestVisibleNode.getToSpecifyNodeWidth(null);
                if (width > maxWidth){
                    maxWidth = width;
                }
            }
        }
        return maxWidth;
    }
 
    //获取当前节点到只指定节点的全部宽度 (指定节点为null时,表示到根节点)
    public int getToSpecifyNodeWidth(Node specifyNode){
        int totalWidth = width;
        Node currentNode = parent;
        if (specifyNode == null){
            while (currentNode != null){
                totalWidth += currentNode.width;
                if (currentNode.children.size() > 1){
                    //
                    totalWidth += horizontalMargin;
                }
                currentNode = currentNode.parent;
            }
        }else {
            boolean flag = false;
            while (currentNode != null){
                totalWidth += currentNode.width;
                if (currentNode.children.size() > 1){
                    totalWidth += horizontalMargin;
                }
                if (currentNode == specifyNode){
                    flag = true;
                    break;
                }
                currentNode = currentNode.parent;
            }
            if (!flag){
                totalWidth = 0;
            }
        }
 
        return totalWidth;
    }
    // 寻找当前可见的最深节点列表,true表示只考虑可见节点,false表示考虑所有节点
    public List<Node> findDeepestNodes(boolean considerVisible) {
        List<Node> deepestNodes = new ArrayList<>(); // 初始化为空列表
        int maxDepth = -1;  // 默认深度为-1,表示没有找到最深节点
 
        Queue<Node> queue = new LinkedList<>();
        queue.offer(this);
 
        while (!queue.isEmpty()) {
            Node current = queue.poll();
            int currentDepth = current.getDepth();
 
            // 根据传入的参数判断是否考虑可见节点
            if (!considerVisible || (considerVisible && current.isVisible)) {
                if (currentDepth >= maxDepth) {
                    // 如果当前节点的深度大于等于maxDepth,更新maxDepth
                    if (currentDepth > maxDepth) {
                        maxDepth = currentDepth;
                        deepestNodes.clear(); // 清空之前的结果
                    }
                    deepestNodes.add(current);
                }
            }
 
            // 只有当前节点可见时才继续遍历其子节点
            if (!considerVisible || (considerVisible && current.isVisible)) {
                for (Node child : current.children) {
                    if (child.parent != null) {
                        queue.offer(child);
                    }
                }
            }
        }
 
        return deepestNodes;
    }
 
    // 获取节点的深度
    public int getDepth() {
        int depth = 0;
        Node currentNode = this;
        while (currentNode.parent != null) {
            depth++;
            currentNode = currentNode.parent;
        }
        return depth;
    }
 
    public int getAllHeight(){
        return getNodeTreeHeightRecursion(this,false);
    }
 
    //获取 ”显示出 以当前节点为根节点 的 所有可见节点 “所需的高度
    public int getAllVisibleHeight(){
        return isVisible? getNodeTreeHeightRecursion(this,true):0;
    }
 
    // 递归获取节点的可见高度总和,true表示考虑节点的可见性(即获取可见节点),false表示不考虑(即获取全部节点)
    public int getNodeTreeHeightRecursion(Node node, boolean considerVisible){
        if (node == null){
            return 0;
        }
        if (considerVisible) {
            if (!node.isVisible) {
                return 0;
            }
            if (!node.isExpand) {
                return node.height;
            }
        }
        if (node.children.size() == 0){
            return node.height;
        }else {
            int heightSum = 0;
            for (Node child : node.children) {
                if (getNodeTreeHeightRecursion(child,considerVisible)!=0){
                    heightSum += verticalMargin;
                }
                heightSum += getNodeTreeHeightRecursion(child,considerVisible);
            }
            //子节点每两个之间还有竖直间隔
            return Math.max(node.height,heightSum);
        }
    }
 
    public int getWidth(){
        return width;
    }
 
    public int getHeight(){
        return height;
    }
 
    public List<Node> getChildren() {
        return children;
    }
 
 
    public void addChild(Node node){
        children.add(node);
    }
 
    public boolean isExpand() {
        return isExpand;
    }
 
    public void setExpand(boolean expand) {
        isExpand = expand;
    }
 
    public boolean isVisible() {
        return isVisible;
    }
 
    public List<Path> getNextPathList() {
        return nextPathList;
    }
 
    public List<Path> getPrePathList() {
        return prePathList;
    }
 
    public List<View> getViewList() {
        return viewList;
    }
 
    // 获取 每个 itemView 的宽度
    public int getItemViewWidth(){
        View itemView =  createItemView(null,false);
        return itemView.getMeasuredWidth();
    }
    // 获取 每个 itemView 的高度
    public int getItemViewHeight(){
        View itemView =  createItemView(null,false);
        return itemView.getMeasuredHeight();
    }
    //获取展开按钮的宽度
    public int getExpandIconWidth(){
        ImageView expandIconView = createExpandIconView();
        return expandIconView.getMeasuredWidth();
    }
 
 
    public int getViewWidth(){
        return viewBottom - viewTop;
    }
 
    public void setOnItemClickListener(HomeMindMapLayout.OnItemClickListener onItemClickListener) {
        this.onItemClickListener = onItemClickListener;
        //所有的子节点也设置
        for (Node child : children) {
            child.setOnItemClickListener(onItemClickListener);
        }
    }
}