Android树形控件绘制方法
前言
作为一个开发者,日常会接触到很多优秀的软件,其实,或多或少会有这样的想法,我能不能开发一个自己软件,甚至办公软件都希望是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; /** * created by owant on 16/12/2016. */ public class treenode<t> { /** * the parent node,if root node parent node=null; */ public treenode<t> parentnode; /** * the data value */ public t value; /** * have the child nodes */ public linkedlist<treenode<t>> childnodes; /** * focus tag for the tree add nodes */ public boolean focus; /** * index of the tree floor */ public int floor; public treenode(t value) { this.value = value; this.childnodes = new linkedlist<treenode<t>>(); // this.focus = false; // this.parentnode = null; } public treenode<t> getparentnode() { return parentnode; } public void setparentnode(treenode<t> parentnode) { this.parentnode = parentnode; } public t getvalue() { return value; } public void setvalue(t value) { this.value = value; } public linkedlist<treenode<t>> getchildnodes() { return childnodes; } public void setchildnodes(linkedlist<treenode<t>> 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; /** * created by owant on 16/12/2016. */ public class tree<t> { /** * the root for the tree */ public treenode<t> rootnode; public tree(treenode<t> rootnode) { this.rootnode = rootnode; } /** * add the node in some father node * * @param start * @param nodes */ public void addnode(treenode<t> start, treenode<t>... nodes) { int index = 1; treenode<t> temp = start; if (temp.getparentnode() != null) { index = temp.getparentnode().floor; } for (treenode<t> t : nodes) { t.setparentnode(start); t.setfloor(index); start.getchildnodes().add(t); } } public boolean remvoenode(treenode<t> starnode, treenode<t> deletenote) { boolean rm = false; int size = starnode.getchildnodes().size(); if (size > 0) { rm = starnode.getchildnodes().remove(deletenote); } return rm; } public treenode<t> getrootnode() { return rootnode; } public void setrootnode(treenode<t> rootnode) { this.rootnode = rootnode; } /** * 同一个父节点的上下 * * @param midprenode * @return * @throws notfindnodeexception */ public treenode<t> getlownode(treenode<t> midprenode) { treenode<t> find = null; treenode<t> parentnode = midprenode.getparentnode(); if (parentnode != null && parentnode.getchildnodes().size() >= 2) { deque<treenode<t>> queue = new arraydeque<>(); treenode<t> rootnode = parentnode; queue.add(rootnode); boolean up = false; while (!queue.isempty()) { rootnode = (treenode<t>) queue.poll(); if (up) { if (rootnode.getfloor() == midprenode.getfloor()) { find = rootnode; } break; } //到了该元素 if (rootnode == midprenode) up = true; linkedlist<treenode<t>> childnodes = rootnode.getchildnodes(); if (childnodes.size() > 0) { for (treenode<t> item : childnodes) { queue.add(item); } } } } return find; } public treenode<t> getprenode(treenode<t> midprenode) { treenode<t> parentnode = midprenode.getparentnode(); treenode<t> find = null; if (parentnode != null && parentnode.getchildnodes().size() > 0) { deque<treenode<t>> queue = new arraydeque<>(); treenode<t> rootnode = parentnode; queue.add(rootnode); while (!queue.isempty()) { rootnode = (treenode<t>) queue.poll(); //到了该元素 if (rootnode == midprenode) { //返回之前的值 break; } find = rootnode; linkedlist<treenode<t>> childnodes = rootnode.getchildnodes(); if (childnodes.size() > 0) { for (treenode<t> item : childnodes) { queue.add(item); } } } if (find != null && find.getfloor() != midprenode.getfloor()) { find = null; } } return find; } public arraylist<treenode<t>> getalllownodes(treenode<t> addnode) { arraylist<treenode<t>> array = new arraylist<>(); treenode<t> parentnode = addnode.getparentnode(); while (parentnode != null) { treenode<t> lownode = getlownode(parentnode); while (lownode != null) { array.add(lownode); lownode = getlownode(lownode); } parentnode = parentnode.getparentnode(); } return array; } public arraylist<treenode<t>> getallprenodes(treenode<t> addnode) { arraylist<treenode<t>> array = new arraylist<>(); treenode<t> parentnode = addnode.getparentnode(); while (parentnode != null) { treenode<t> lownode = getprenode(parentnode); while (lownode != null) { array.add(lownode); lownode = getprenode(lownode); } parentnode = parentnode.getparentnode(); } return array; } public linkedlist<treenode<t>> getnodechildnodes(treenode<t> node) { return node.getchildnodes(); } public void printtree() { stack<treenode<t>> stack = new stack<>(); treenode<t> rootnode = getrootnode(); stack.add(rootnode); while (!stack.isempty()) { treenode<t> pop = stack.pop(); system.out.println(pop.getvalue().tostring()); linkedlist<treenode<t>> childnodes = pop.getchildnodes(); for (treenode<t> item : childnodes) { stack.add(item); } } } public void printtree2() { deque<treenode<t>> queue = new arraydeque<>(); treenode<t> rootnode = getrootnode(); queue.add(rootnode); while (!queue.isempty()) { rootnode = (treenode<t>) queue.poll(); system.out.println(rootnode.getvalue().tostring()); linkedlist<treenode<t>> childnodes = rootnode.getchildnodes(); if (childnodes.size() > 0) { for (treenode<t> 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; /** * created by owant on 09/01/2017. */ public class supertreeview extends viewgroup { /** * the default x,y mdx */ private int mdx; private int mdy; private int mwith; private int mheight; private context mcontext; private tree<string> mtree; private arraylist<nodeview> 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); } /** * 添加view到group */ private void onaddnodeviews() { if (mtree != null) { treenode<string> rootnode = mtree.getrootnode(); deque<treenode<string>> deque = new arraydeque<>(); deque.add(rootnode); while (!deque.isempty()) { treenode<string> 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<treenode<string>> childnodes = poll.getchildnodes(); for (treenode<string> 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); } /** * 标准的位置分布 * * @param rootview */ private void standardtreechildlayout(nodeview rootview) { treenode<string> treenode = rootview.gettreenode(); if (treenode != null) { //所有的子节点 linkedlist<treenode<string>> 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(); } } } } } /** * 移动 * * @param rootview * @param dy */ private void movenodelayout(nodeview rootview, int dy) { deque<treenode<string>> queue = new arraydeque<>(); treenode<string> rootnode = rootview.gettreenode(); queue.add(rootnode); while (!queue.isempty()) { rootnode = (treenode<string>) 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<treenode<string>> childnodes = rootnode.getchildnodes(); for (treenode<string> item : childnodes) { queue.add(item); } } } private void fatherchildcorrect(nodeview nv) { int count = nv.gettreenode().getchildnodes().size(); if (nv.getparent() != null && count >= 2) { treenode<string> tn = nv.gettreenode().getchildnodes().get(0); treenode<string> 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<treenode<string>> alllownodes = mtree.getalllownodes(bn); arraylist<treenode<string>> allprenodes = mtree.getallprenodes(tn); for (treenode<string> low : alllownodes) { nodeview view = findtreenodeview(low); movenodelayout(view, bndr); } for (treenode<string> pre : allprenodes) { nodeview view = findtreenodeview(pre); movenodelayout(view, -topdr); } } } /** * 绘制树形的连线 * * @param canvas * @param root */ private void drawtreeline(canvas canvas, treenode<string> root) { nodeview fatherview = findtreenodeview(root); if (fatherview != null) { linkedlist<treenode<string>> childnodes = root.getchildnodes(); for (treenode<string> node : childnodes) { drawlinetoview(canvas, fatherview, findtreenodeview(node)); drawtreeline(canvas, node); } } } /** * 绘制两个view直接的连线 * * @param canvas * @param from * @param to */ 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<string> 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<string> tree) { this.mtree = tree; onaddnodeviews(); } public tree<string> gettree() { return mtree; } }
粘贴代码后发现,没有什么好说了,对于读者来说,应该是一脸懵逼的。毕竟,那个位置是如何确定的呀,view和view的连线呀…… 对于整个view来说这是最难的,我也是探索了好久才得出结论的。首先,对于一个只有两层的树形来说,如图:
pqrs是基于f来计算的,之后分布。之后我就得到的方法如下:
/** * 标准的位置分布 * * @param rootview */ private void standardtreechildlayout(nodeview rootview) { treenode<string> treenode = rootview.gettreenode(); if (treenode != null) { //所有的子节点 linkedlist<treenode<string>> 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
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。