当前位置: 移动技术网 > 移动技术>移动开发>Android > Android树形控件绘制方法

Android树形控件绘制方法

2019年07月24日  | 移动技术网移动技术  | 我要评论

前言

作为一个开发者,日常会接触到很多优秀的软件,其实,或多或少会有这样的想法,我能不能开发一个自己软件,甚至办公软件都希望是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

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

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网