Android树形控件绘制方法

作者:欧拉啊旺 时间:2022-09-05 01:50:55 

前言

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

Android树形控件绘制方法

效果1

Android树形控件绘制方法

效果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的理解等。 

Android树形控件绘制方法

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来说这是最难的,我也是探索了好久才得出结论的。首先,对于一个只有两层的树形来说,如图:

Android树形控件绘制方法

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情况如下:

Android树形控件绘制方法

说明我们还需要纠正。下面是纠正的探索,我精简一下结构如下情况:

Android树形控件绘制方法

发现:

B需要在E的上面;
D需要在I的下面;

就是EI要撑开ID的位置。之后我们可以先写这个算法,发现基本可以了。但是还是有问题,同层的还是会重合,只有我们又进行同层的纠正。发现好像解决了,其实还是不行,测试还是发现问题,对于单伸长还有问题,之后又是修改…………

最后发现……这里就不说了,就是自己要探索啦。

总结

最后我才发现了那个完善的纠正算法,就是代码了的。大家可以在我的github中找到我之前的TreeView中的那个位置算法探索。欢迎大家支持:https://github.com/owant/TreeView

标签:Android,树形控件
0
投稿

猜你喜欢

  • java客户端Jedis操作Redis Sentinel 连接池的实现方法

    2023-08-19 10:55:19
  • java实现简单猜数字

    2022-12-31 16:20:02
  • 消息中间件详解以及比较选择

    2022-08-17 03:01:01
  • C#的FileInfo类实现文件操作实例

    2021-06-30 06:39:44
  • Java实现双向循环链表

    2023-11-08 04:14:40
  • Android学习教程之日历控件使用(7)

    2023-01-06 23:10:46
  • java简单解析xls文件的方法示例【读取和写入】

    2022-04-15 19:30:43
  • 分享C#操作内存读写方法的主要实现代码

    2022-10-08 08:08:44
  • Android Intent的几种用法详细解析

    2023-08-23 20:01:01
  • 如何基于Java实现对象List排序

    2022-01-28 03:59:16
  • 小白2分钟学会Visual Studio如何将引用包打包到NuGet上

    2022-01-14 10:25:53
  • c#根据网址抓取网页截屏生成图片的示例

    2021-08-31 14:29:13
  • 浅谈Java继承中的转型及其内存分配

    2023-11-03 22:56:04
  • c#栈变化规则图解示例(栈的生长与消亡)

    2021-10-12 05:11:11
  • 解决SpringBoot加载application.properties配置文件的坑

    2023-02-05 19:55:27
  • Android GPS获取当前经纬度坐标

    2021-10-31 12:09:40
  • java模拟实现双向链表

    2022-06-05 09:16:16
  • c#实现断点续传功能示例分享

    2022-05-03 04:39:49
  • Android悬浮窗屏蔽悬浮窗外部所有的点击事件的实例代码

    2022-11-13 17:09:47
  • java连接sql server 2008数据库代码

    2023-05-27 10:40:01
  • asp之家 软件编程 m.aspxhome.com