首页 > 系统 > Android > 正文

Android树形控件绘制方法

2019-12-12 04:01:56
字体:
来源:转载
供稿:网友

前言

作为一个开发者,日常会接触到很多优秀的软件,其实,或多或少会有这样的想法,我能不能开发一个自己软件,甚至办公软件都希望是Markdown的文本,为何用office?我常常想自己做一个IDE什么的。但是,很多只是想了一下就过了,一直没有实现.
我接触思维导图软件已经很久的了,开始是使用微软的思维导图软件,接着XMind,后来使用了MindMaple Lite。感觉很好用的。也想过如何去实现一个思维导图的软件,加之我特别注意软件的快捷键,我选取软件常常是,看快捷如何,快捷键差的就不要了。基于自己的实践使用思维导图。前一个月我就在github上实现了一个树形图的Android控件,这个其实是我想实现思维导图的开始。实现后,才发现并没有多大的障碍。下面我就说说我如何打造一个树形控件的。先上效果:


效果1


效果2

实现

一步一步可夺城。将自己要实现的东西肢解,那些实现得了的?那些未知的?

思路步骤概要

整个结构分为:树形,节点; 对于Android的结构有:模型(树形,节点),View;

  1. 实现树形的节点node 的Model;
  2. 实现树形Model;
  3. 实现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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持武林网。

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表