前言
作为一个开发者,日常会接触到很多优秀的软件,其实,或多或少会有这样的想法,我能不能开发一个自己软件,甚至办公软件都希望是Markdown的文本,为何用office?我常常想自己做一个IDE什么的。但是,很多只是想了一下就过了,一直没有实现.
我接触思维导图软件已经很久的了,开始是使用微软的思维导图软件,接着XMind,后来使用了MindMaple Lite。感觉很好用的。也想过如何去实现一个思维导图的软件,加之我特别注意软件的快捷键,我选取软件常常是,看快捷如何,快捷键差的就不要了。基于自己的实践使用思维导图。前一个月我就在github上实现了一个树形图的Android控件,这个其实是我想实现思维导图的开始。实现后,才发现并没有多大的障碍。下面我就说说我如何打造一个树形控件的。先上效果:
效果1
效果2
实现
一步一步可夺城。将自己要实现的东西肢解,那些实现得了的?那些未知的?
思路步骤概要
整个结构分为:树形,节点; 对于Android的结构有:模型(树形,节点),View;
- 实现树形的节点node 的Model;
- 实现树形Model;
- 实现View的绘制:1.添加View;2.确定Nodes的位置;3.连线Node;
详细步骤
看到思路步骤概要后,相信我们的思路已经很清晰了。感觉是不是很simple,是的,实现也如此。到这里了,我就开始编码。但是为了教会大家,我提几个疑问给大家:
树的遍历如何实现?(可以google,如果你学过数据结构当然simple了)
节点和节点这间使用什么关联?(next)
如何确定Node的位置?位置有什么规律?(??)
如何实现两个View之间的连线?(??)
……
其实问题还真的有一点。但是这些都不能妨碍我们的步伐,还是写好已知的代码吧 。
代码
1.树的节点。主要是一些需要的数据。父节点,值,子节点,是否对焦(对于将来用的),在树形的层……
package com.owant.drawtreeview.model; import java.util.linkedList; public class TreeNode{ public TreeNode parentNode; public T value; public linkedList > childNodes; public boolean focus; public int floor; public TreeNode(T value) { this.value = value; this.childNodes = new linkedList >(); // this.focus = false; // this.parentNode = null; } public TreeNode getParentNode() { return parentNode; } public void setParentNode(TreeNode parentNode) { this.parentNode = parentNode; } public T getValue() { return value; } public void setValue(T value) { this.value = value; } public linkedList > getChildNodes() { return childNodes; } public void setChildNodes(linkedList > childNodes) { this.childNodes = childNodes; } public boolean isFocus() { return focus; } public void setFocus(boolean focus) { this.focus = focus; } public int getFloor() { return floor; } public void setFloor(int floor) { this.floor = floor; } }
2.树形。根节点,添加节点,遍历,上一个节点,下一个节点,基于点拆分的上下节点集合。
package com.owant.drawtreeview.model; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; import java.util.linkedList; import java.util.Stack; public class Tree{ public TreeNode rootNode; public Tree(TreeNode rootNode) { this.rootNode = rootNode; } public void addNode(TreeNode start, TreeNode ... nodes) { int index = 1; TreeNode temp = start; if (temp.getParentNode() != null) { index = temp.getParentNode().floor; } for (TreeNode t : nodes) { t.setParentNode(start); t.setFloor(index); start.getChildNodes().add(t); } } public boolean remvoeNode(TreeNode starNode, TreeNode deleteNote) { boolean rm = false; int size = starNode.getChildNodes().size(); if (size > 0) { rm = starNode.getChildNodes().remove(deleteNote); } return rm; } public TreeNode getRootNode() { return rootNode; } public void setRootNode(TreeNode rootNode) { this.rootNode = rootNode; } public TreeNode getLowNode(TreeNode midPreNode) { TreeNode find = null; TreeNode parentNode = midPreNode.getParentNode(); if (parentNode != null && parentNode.getChildNodes().size() >= 2) { Deque > queue = new ArrayDeque<>(); TreeNode rootNode = parentNode; queue.add(rootNode); boolean up = false; while (!queue.isEmpty()) { rootNode = (TreeNode ) queue.poll(); if (up) { if (rootNode.getFloor() == midPreNode.getFloor()) { find = rootNode; } break; } //到了该元素 if (rootNode == midPreNode) up = true; linkedList > childNodes = rootNode.getChildNodes(); if (childNodes.size() > 0) { for (TreeNode item : childNodes) { queue.add(item); } } } } return find; } public TreeNode getPreNode(TreeNode midPreNode) { TreeNode parentNode = midPreNode.getParentNode(); TreeNode find = null; if (parentNode != null && parentNode.getChildNodes().size() > 0) { Deque > queue = new ArrayDeque<>(); TreeNode rootNode = parentNode; queue.add(rootNode); while (!queue.isEmpty()) { rootNode = (TreeNode ) queue.poll(); //到了该元素 if (rootNode == midPreNode) { //返回之前的值 break; } find = rootNode; linkedList > childNodes = rootNode.getChildNodes(); if (childNodes.size() > 0) { for (TreeNode item : childNodes) { queue.add(item); } } } if (find != null && find.getFloor() != midPreNode.getFloor()) { find = null; } } return find; } public ArrayList > getAllLowNodes(TreeNode addNode) { ArrayList > array = new ArrayList<>(); TreeNode parentNode = addNode.getParentNode(); while (parentNode != null) { TreeNode lowNode = getLowNode(parentNode); while (lowNode != null) { array.add(lowNode); lowNode = getLowNode(lowNode); } parentNode = parentNode.getParentNode(); } return array; } public ArrayList > getAllPreNodes(TreeNode addNode) { ArrayList > array = new ArrayList<>(); TreeNode parentNode = addNode.getParentNode(); while (parentNode != null) { TreeNode lowNode = getPreNode(parentNode); while (lowNode != null) { array.add(lowNode); lowNode = getPreNode(lowNode); } parentNode = parentNode.getParentNode(); } return array; } public linkedList > getNodeChildNodes(TreeNode node) { return node.getChildNodes(); } public void printTree() { Stack > stack = new Stack<>(); TreeNode rootNode = getRootNode(); stack.add(rootNode); while (!stack.isEmpty()) { TreeNode pop = stack.pop(); System.out.println(pop.getValue().toString()); linkedList > childNodes = pop.getChildNodes(); for (TreeNode item : childNodes) { stack.add(item); } } } public void printTree2() { Deque > queue = new ArrayDeque<>(); TreeNode rootNode = getRootNode(); queue.add(rootNode); while (!queue.isEmpty()) { rootNode = (TreeNode ) queue.poll(); System.out.println(rootNode.getValue().toString()); linkedList > childNodes = rootNode.getChildNodes(); if (childNodes.size() > 0) { for (TreeNode item : childNodes) { queue.add(item); } } } } }
3.测试模型 当我们实现了模型后,要写一些列子来测试模型是否正确,进行打印,遍历等测试,这是很重要的。对于树形的node的上一个node和下一个node的理解等。
4.树形的View
package com.owant.drawtreeview.view;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import com.owant.drawtreeview.R;
import com.owant.drawtreeview.model.Tree;
import com.owant.drawtreeview.model.TreeNode;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.linkedList;
public class SuperTreeView extends ViewGroup {
private int mDx;
private int mDy;
private int mWith;
private int mHeight;
private Context mContext;
private Tree mTree;
private ArrayList mNodesViews;
public SuperTreeView(Context context) {
this(context, null, 0);
}
public SuperTreeView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SuperTreeView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
mNodesViews = new ArrayList<>();
mContext = context;
mDx = dp2px(mContext, 26);
mDy = dp2px(mContext, 22);
}
private void onAddNodeViews() {
if (mTree != null) {
TreeNode rootNode = mTree.getRootNode();
Deque> deque = new ArrayDeque<>();
deque.add(rootNode);
while (!deque.isEmpty()) {
TreeNode poll = deque.poll();
NodeView nodeView = new NodeView(mContext);
nodeView.setTreeNode(poll);
ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
nodeView.setLayoutParams(lp);
this.addView(nodeView);
mNodesViews.add(nodeView);
linkedList> childNodes = poll.getChildNodes();
for (TreeNode ch : childNodes) {
deque.push(ch);
}
}
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final int size = getChildCount();
for (int i = 0; i < size; i++) {
measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
mHeight = getMeasuredHeight();
mWith = getMeasuredWidth();
if (mTree != null) {
NodeView rootView = findTreeNodeView(mTree.getRootNode());
if (rootView != null) {
//root的位置
rootTreeViewLayout(rootView);
//标准位置
for (NodeView nv : mNodesViews) {
standardTreeChildLayout(nv);
}
//基于父子的移动
for (NodeView nv : mNodesViews) {
fatherChildCorrect(nv);
}
}
}
}
private void rootTreeViewLayout(NodeView rootView) {
int lr = mDy;
int tr = mHeight / 2 - rootView.getMeasuredHeight() / 2;
int rr = lr + rootView.getMeasuredWidth();
int br = tr + rootView.getMeasuredHeight();
rootView.layout(lr, tr, rr, br);
}
@Override
protected void dispatchDraw(Canvas canvas) {
if (mTree != null) {
drawTreeLine(canvas, mTree.getRootNode());
}
super.dispatchDraw(canvas);
}
private void standardTreeChildLayout(NodeView rootView) {
TreeNode treeNode = rootView.getTreeNode();
if (treeNode != null) {
//所有的子节点
linkedList> childNodes = treeNode.getChildNodes();
int size = childNodes.size();
int mid = size / 2;
int r = size % 2;
//基线
// b
// a-------
// c
//
int left = rootView.getRight() + mDx;
int top = rootView.getTop() + rootView.getMeasuredHeight() / 2;
int right = 0;
int bottom = 0;
if (size == 0) {
return;
} else if (size == 1) {
NodeView midChildNodeView = findTreeNodeView(childNodes.get(0));
top = top - midChildNodeView.getMeasuredHeight() / 2;
right = left + midChildNodeView.getMeasuredWidth();
bottom = top + midChildNodeView.getMeasuredHeight();
midChildNodeView.layout(left, top, right, bottom);
} else {
int topLeft = left;
int topTop = top;
int topRight = 0;
int topBottom = 0;
int bottomLeft = left;
int bottomTop = top;
int bottomRight = 0;
int bottomBottom = 0;
if (r == 0) {//偶数
for (int i = mid - 1; i >= 0; i--) {
NodeView topView = findTreeNodeView(childNodes.get(i));
NodeView bottomView = findTreeNodeView(childNodes.get(size - i - 1));
if (i == mid - 1) {
topTop = topTop - mDy / 2 - topView.getMeasuredHeight();
topRight = topLeft + topView.getMeasuredWidth();
topBottom = topTop + topView.getMeasuredHeight();
bottomTop = bottomTop + mDy / 2;
bottomRight = bottomLeft + bottomView.getMeasuredWidth();
bottomBottom = bottomTop + bottomView.getMeasuredHeight();
} else {
topTop = topTop - mDy - topView.getMeasuredHeight();
topRight = topLeft + topView.getMeasuredWidth();
topBottom = topTop + topView.getMeasuredHeight();
bottomTop = bottomTop + mDy;
bottomRight = bottomLeft + bottomView.getMeasuredWidth();
bottomBottom = bottomTop + bottomView.getMeasuredHeight();
}
topView.layout(topLeft, topTop, topRight, topBottom);
bottomView.layout(bottomLeft, bottomTop, bottomRight, bottomBottom);
bottomTop = bottomView.getBottom();
}
} else {
NodeView midView = findTreeNodeView(childNodes.get(mid));
midView.layout(left, top - midView.getMeasuredHeight() / 2, left + midView.getMeasuredWidth(),
top - midView.getMeasuredHeight() / 2 + midView.getMeasuredHeight());
topTop = midView.getTop();
bottomTop = midView.getBottom();
for (int i = mid - 1; i >= 0; i--) {
NodeView topView = findTreeNodeView(childNodes.get(i));
NodeView bottomView = findTreeNodeView(childNodes.get(size - i - 1));
topTop = topTop - mDy - topView.getMeasuredHeight();
topRight = topLeft + topView.getMeasuredWidth();
topBottom = topTop + topView.getMeasuredHeight();
bottomTop = bottomTop + mDy;
bottomRight = bottomLeft + bottomView.getMeasuredWidth();
bottomBottom = bottomTop + bottomView.getMeasuredHeight();
topView.layout(topLeft, topTop, topRight, topBottom);
bottomView.layout(bottomLeft, bottomTop, bottomRight, bottomBottom);
bottomTop = bottomView.getBottom();
}
}
}
}
}
private void moveNodeLayout(NodeView rootView, int dy) {
Deque> queue = new ArrayDeque<>();
TreeNode rootNode = rootView.getTreeNode();
queue.add(rootNode);
while (!queue.isEmpty()) {
rootNode = (TreeNode) queue.poll();
rootView = findTreeNodeView(rootNode);
int l = rootView.getLeft();
int t = rootView.getTop() + dy;
rootView.layout(l, t, l + rootView.getMeasuredWidth(), t + rootView.getMeasuredHeight());
linkedList> childNodes = rootNode.getChildNodes();
for (TreeNode item : childNodes) {
queue.add(item);
}
}
}
private void fatherChildCorrect(NodeView nv) {
int count = nv.getTreeNode().getChildNodes().size();
if (nv.getParent() != null && count >= 2) {
TreeNode tn = nv.getTreeNode().getChildNodes().get(0);
TreeNode bn = nv.getTreeNode().getChildNodes().get(count - 1);
Log.i("see fc", nv.getTreeNode().getValue() + ":" + tn.getValue() + "," + bn.getValue());
int topDr = nv.getTop() - findTreeNodeView(tn).getBottom() + mDy;
int bnDr = findTreeNodeView(bn).getTop() - nv.getBottom() + mDy;
//上移动
ArrayList> allLowNodes = mTree.getAllLowNodes(bn);
ArrayList> allPreNodes = mTree.getAllPreNodes(tn);
for (TreeNode low : allLowNodes) {
NodeView view = findTreeNodeView(low);
moveNodeLayout(view, bnDr);
}
for (TreeNode pre : allPreNodes) {
NodeView view = findTreeNodeView(pre);
moveNodeLayout(view, -topDr);
}
}
}
private void drawTreeLine(Canvas canvas, TreeNode root) {
NodeView fatherView = findTreeNodeView(root);
if (fatherView != null) {
linkedList> childNodes = root.getChildNodes();
for (TreeNode node : childNodes) {
drawLineToView(canvas, fatherView, findTreeNodeView(node));
drawTreeLine(canvas, node);
}
}
}
private void drawLineToView(Canvas canvas, View from, View to) {
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
float width = 2f;
paint.setStrokeWidth(dp2px(mContext, width));
paint.setColor(mContext.getResources().getColor(R.color.chelsea_cucumber));
int top = from.getTop();
int formY = top + from.getMeasuredHeight() / 2;
int formX = from.getRight();
int top1 = to.getTop();
int toY = top1 + to.getMeasuredHeight() / 2;
int toX = to.getLeft();
Path path = new Path();
path.moveTo(formX, formY);
path.quadTo(toX - dp2px(mContext, 15), toY, toX, toY);
canvas.drawPath(path, paint);
}
private NodeView findTreeNodeView(TreeNode node) {
NodeView v = null;
for (NodeView view : mNodesViews) {
if (view.getTreeNode() == node) {
v = view;
continue;
}
}
return v;
}
public int dp2px(Context context, float dpVal) {
int result = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpVal, context.getResources()
.getDisplayMetrics());
return result;
}
public void setTree(Tree tree) {
this.mTree = tree;
onAddNodeViews();
}
public Tree getTree() {
return mTree;
}
}
粘贴代码后发现,没有什么好说了,对于读者来说,应该是一脸懵逼的。毕竟,那个位置是如何确定的呀,view和view的连线呀…… 对于整个View来说这是最难的,我也是探索了好久才得出结论的。首先,对于一个只有两层的树形来说,如图:
PQRS是基于F来计算的,之后分布。之后我就得到的方法如下:
private void standardTreeChildLayout(NodeView rootView) {
TreeNode treeNode = rootView.getTreeNode();
if (treeNode != null) {
//所有的子节点
linkedList> childNodes = treeNode.getChildNodes();
int size = childNodes.size();
int mid = size / 2;
int r = size % 2;
//基线
// b
// a-------
// c
//
int left = rootView.getRight() + mDx;
int top = rootView.getTop() + rootView.getMeasuredHeight() / 2;
int right = 0;
int bottom = 0;
if (size == 0) {
return;
} else if (size == 1) {
NodeView midChildNodeView = findTreeNodeView(childNodes.get(0));
top = top - midChildNodeView.getMeasuredHeight() / 2;
right = left + midChildNodeView.getMeasuredWidth();
bottom = top + midChildNodeView.getMeasuredHeight();
midChildNodeView.layout(left, top, right, bottom);
} else {
int topLeft = left;
int topTop = top;
int topRight = 0;
int topBottom = 0;
int bottomLeft = left;
int bottomTop = top;
int bottomRight = 0;
int bottomBottom = 0;
if (r == 0) {//偶数
for (int i = mid - 1; i >= 0; i--) {
NodeView topView = findTreeNodeView(childNodes.get(i));
NodeView bottomView = findTreeNodeView(childNodes.get(size - i - 1));
if (i == mid - 1) {
topTop = topTop - mDy / 2 - topView.getMeasuredHeight();
topRight = topLeft + topView.getMeasuredWidth();
topBottom = topTop + topView.getMeasuredHeight();
bottomTop = bottomTop + mDy / 2;
bottomRight = bottomLeft + bottomView.getMeasuredWidth();
bottomBottom = bottomTop + bottomView.getMeasuredHeight();
} else {
topTop = topTop - mDy - topView.getMeasuredHeight();
topRight = topLeft + topView.getMeasuredWidth();
topBottom = topTop + topView.getMeasuredHeight();
bottomTop = bottomTop + mDy;
bottomRight = bottomLeft + bottomView.getMeasuredWidth();
bottomBottom = bottomTop + bottomView.getMeasuredHeight();
}
topView.layout(topLeft, topTop, topRight, topBottom);
bottomView.layout(bottomLeft, bottomTop, bottomRight, bottomBottom);
bottomTop = bottomView.getBottom();
}
} else {
NodeView midView = findTreeNodeView(childNodes.get(mid));
midView.layout(left, top - midView.getMeasuredHeight() / 2, left + midView.getMeasuredWidth(),
top - midView.getMeasuredHeight() / 2 + midView.getMeasuredHeight());
topTop = midView.getTop();
bottomTop = midView.getBottom();
for (int i = mid - 1; i >= 0; i--) {
NodeView topView = findTreeNodeView(childNodes.get(i));
NodeView bottomView = findTreeNodeView(childNodes.get(size - i - 1));
topTop = topTop - mDy - topView.getMeasuredHeight();
topRight = topLeft + topView.getMeasuredWidth();
topBottom = topTop + topView.getMeasuredHeight();
bottomTop = bottomTop + mDy;
bottomRight = bottomLeft + bottomView.getMeasuredWidth();
bottomBottom = bottomTop + bottomView.getMeasuredHeight();
topView.layout(topLeft, topTop, topRight, topBottom);
bottomView.layout(bottomLeft, bottomTop, bottomRight, bottomBottom);
bottomTop = bottomView.getBottom();
}
}
}
}
}
之后等到的View情况如下:
说明我们还需要纠正。下面是纠正的探索,我精简一下结构如下情况:
发现:
B需要在E的上面;
D需要在I的下面;
就是EI要撑开ID的位置。之后我们可以先写这个算法,发现基本可以了。但是还是有问题,同层的还是会重合,只有我们又进行同层的纠正。发现好像解决了,其实还是不行,测试还是发现问题,对于单伸长还有问题,之后又是修改…………
最后发现……这里就不说了,就是自己要探索啦。
总结
最后我才发现了那个完善的纠正算法,就是代码了的。大家可以在我的github中找到我之前的TreeView中的那个位置算法探索。欢迎大家支持:https://github.com/owant/TreeView
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持考高分网。



