上一篇讲解了如何通过装饰模式和简单工厂模式来优化拓展树形布局。本篇讲解如何给树形布局添加遍历监听器。
说到遍历树这种数据结构,最常见的就是深度优先遍历(dfs)和广度优先遍历(bfs)。我们可以给树形布局添加一个遍历监听器,在布局遍历子控件的时候做一些事情。
为了能在遍历每个结点的时候做我们想做的事情,需要定义一个监听器来回调访问结点时的方法,代码如下:
/** * 遍历监听器 */ public interface SearchListener{ /** * 访问结点时调用 * @param thisNode 此结点 * @param parentNode 父结点 * @return 返回true,则继续遍历,返回false,则中断此次遍历 */ boolean onNode(View thisNode,View parentNode); }onNode方法会返回一个布尔量,它表示是否继续此次遍历,如果需要中断此次遍历,返回false就可以了。
之前的文章说过树这种数据结构具有很强的递归性,因此这里采用递归的方法实现子控件的深度优先遍历,代码如下:
/** * 深度优先遍历子控件 * @param searchListener 遍历监听器 */ public void dfs(SearchListener searchListener){ if(searchListener == null){ return; } //树的根结点 View rootNode = getChildAt(0); dfsInside(this,null,searchListener); } protected boolean dfsInside(View thisNode,View parentNode,SearchListener searchListener){ if(thisNode instanceof TreeLayout){ TreeLayout treeLayout = (TreeLayout) thisNode; View rootNode = treeLayout.getChildAt(0); if(!searchListener.onNode(rootNode,parentNode)){ return false; } for(int i = 1;i < treeLayout.getChildCount();i++){ View childNode = treeLayout.getChildAt(i); //1 boolean continueSearch = treeLayout.dfsInside(childNode,rootNode,searchListener); if(!continueSearch){ return false; } } return true; } return searchListener.onNode(thisNode,parentNode); }代码1处得到访问此次结点的结果,如果为false,就中断本次遍历。
下面来做一个测试看看结果,每个结点都是一个Button。
int i = 0; treeLayout.dfs(new TreeLayout.SearchListener() { @Override public boolean onNode(View thisNode, View parentNode) { if(thisNode instanceof Button){ ((Button)thisNode).setText("" + i); i++; } return true; } });访问前如下: 访问后的结果。
广度优先遍历的特点是访问完本层的结点才访问下一层,可以通过队列来实现。
/** * 广度优先遍历子控件 * @param searchListener 遍历监听器 */ public void bfs(SearchListener searchListener){ if(searchListener == null){ return; } List<Node> nodeQueue = new LinkedList<>(); bfsInside(this,null,searchListener,nodeQueue); } protected boolean bfsInside(View thisNode,View parentNode,SearchListener searchListener,List<Node> nodeQueue){ if(thisNode instanceof TreeLayout){ TreeLayout treeLayout = (TreeLayout) thisNode; View rootNode = treeLayout.getChildAt(0); if(!searchListener.onNode(rootNode,parentNode)){ return false; } for(int i = 1;i < treeLayout.getChildCount();i++){ View childNode = treeLayout.getChildAt(i); boolean continueSearch = searchListener.onNode(childNode,rootNode); if(!continueSearch){ return false; } if(childNode instanceof TreeLayout){ nodeQueue.add(new Node(rootNode,childNode)); } } } if(nodeQueue.size() <= 0){ return false; } Node node = nodeQueue.remove(0); return bfsInside(node.thisView,node.parentNodeView,searchListener,nodeQueue); } private static class Node{ View parentNodeView; View thisView; public Node(View parentNodeView, View thisView) { this.parentNodeView = parentNodeView; this.thisView = thisView; } }看看广度遍历的结果。
搜索到目标控件就结束,这样功能只需要在遍历监听器的onNode方法返回false就可以中断了,测试代码如下。
int i = 0; treeLayout.dfs(new TreeLayout.SearchListener() { @Override public boolean onNode(View thisNode, View parentNode) { if(thisNode instanceof Button){ ((Button)thisNode).setText("" + i); i++; } if(i > 6){ //中断遍历 return false; } return true; } });结果如下:
对源码感兴趣的朋友可以到Github项目里阅读完整代码。