通过对ListView简单的扩展、再封装,即可实现无限层级的树控件TreeView。
1 package cn.asiontang.nleveltreelistview; 2 3 import android.annotation.TargetApi; 4 import android.content.Context; 5 import android.os.Build; 6 import android.util.AttributeSet; 7 import android.view.View; 8 import android.widget.AdapterView; 9 import android.widget.ListAdapter; 10 import android.widget.ListView; 11 12 /** 13 * 支持N(无限)层级的树列表结构 14 * 15 * <p>参考资料:</p> 16 * <ul> 17 * <li> 18 * <a href="http://item.congci.com/item/android-wuxian-ji-shuzhuang-jiegou">Android无限级树状结构 - 19 * Android 20 * - 从此网</a> 21 * </li> 22 * </ul> 23 * 24 * @author AsionTang 25 * @since 2016年6月1日 18:38:43 26 */ 27 @SuppressWarnings("unused") 28 public class NLevelTreeView extends ListView 29 { 30 private OnTreeNodeClickListener mOnTreeNodeClickListener; 31 32 public NLevelTreeView(final Context context) 33 { 34 super(context); 35 this.init(); 36 } 37 38 public NLevelTreeView(final Context context, final AttributeSet attrs) 39 { 40 super(context, attrs); 41 this.init(); 42 } 43 44 public NLevelTreeView(final Context context, final AttributeSet attrs, final int defStyleAttr) 45 { 46 super(context, attrs, defStyleAttr); 47 this.init(); 48 } 49 50 @TargetApi(Build.VERSION_CODES.LOLLIPOP) 51 public NLevelTreeView(final Context context, final AttributeSet attrs, final int defStyleAttr, final int defStyleRes) 52 { 53 super(context, attrs, defStyleAttr, defStyleRes); 54 this.init(); 55 } 56 57 private void init() 58 { 59 } 60 61 public void setAdapter(final NLevelTreeNodeAdapter adapter) 62 { 63 super.setAdapter(adapter); 64 65 //让 NLevelTreeNodeAdapter 处理 节点 收缩展开 动作 66 super.setOnItemClickListener(adapter); 67 68 //处理当 叶子节点 被点击后的事件 回调。 69 adapter.setOuterOnItemClickListener(new OnItemClickListener() 70 { 71 @Override 72 public void onItemClick(final AdapterView<?> parent, final View view, final int position, final long id) 73 { 74 if (NLevelTreeView.this.mOnTreeNodeClickListener != null) 75 { 76 final NLevelTreeNode item = (NLevelTreeNode) parent.getItemAtPosition(position); 77 NLevelTreeView.this.mOnTreeNodeClickListener.onTreeNodeClick(item); 78 } 79 } 80 }); 81 } 82 83 /** 84 * 必须使用继承自 NLevelTreeNodeAdapter 的 适配器,否则会出现异常。 85 */ 86 @Override 87 public void setAdapter(final ListAdapter adapter) 88 { 89 if (adapter instanceof NLevelTreeNodeAdapter) 90 this.setAdapter((NLevelTreeNodeAdapter) adapter); 91 else 92 throw new RuntimeException("For NLevelTreeView, use setAdapter(NLevelTreeNodeAdapter) instead of setAdapter(ListAdapter)"); 93 } 94 95 /** 96 * 不支持使用此回调方式 97 */ 98 @Override 99 @Deprecated 100 public void setOnItemClickListener(final OnItemClickListener listener) 101 { 102 //实际的事件回调在setAdapter里设置,由 setOnTreeNodeClickListener 处理。 103 //super.setOuterOnItemClickListener(listener); 104 105 throw new RuntimeException("For NLevelTreeView, use setOnTreeNodeClickListener() instead of setOnItemClickListener()"); 106 } 107 108 /** 109 * 默认只支持叶子节点的Click事件 110 */ 111 public void setOnTreeNodeClickListener(final OnTreeNodeClickListener listener) 112 { 113 this.mOnTreeNodeClickListener = listener; 114 } 115 116 /** 117 * 默认只支持叶子节点的Click事件 118 */ 119 public interface OnTreeNodeClickListener 120 { 121 /** 122 * 默认只支持叶子节点的Click事件 123 */ 124 void onTreeNodeClick(NLevelTreeNode node); 125 } 126 }
1 package cn.asiontang.nleveltreelistview; 2 3 import android.content.Context; 4 import android.view.View; 5 import android.widget.AdapterView; 6 7 import java.util.HashSet; 8 import java.util.List; 9 import java.util.Set; 10 11 /** 12 * @author AsionTang 13 * @since 2016年6月1日 18:38:43 14 */ 15 @SuppressWarnings("unused") 16 public abstract class NLevelTreeNodeAdapter extends BaseAdapterEx3<NLevelTreeNode> implements AdapterView.OnItemClickListener 17 { 18 private final Set<NLevelTreeNode> mExpandedNodeList = new HashSet<>(); 19 private AdapterView.OnItemClickListener mOuterOnItemClickListener; 20 21 public NLevelTreeNodeAdapter(final Context context, final int itemLayoutResId) 22 { 23 super(context, itemLayoutResId); 24 } 25 26 public NLevelTreeNodeAdapter(final Context context, final int itemLayoutResId, final List<NLevelTreeNode> objects) 27 { 28 super(context, itemLayoutResId, objects); 29 } 30 31 @Override 32 public void convertView(final ViewHolder viewHolder, final NLevelTreeNode item) 33 { 34 this.convertView(viewHolder, item, this.mExpandedNodeList.contains(item)); 35 } 36 37 public abstract void convertView(final ViewHolder viewHolder, final NLevelTreeNode item, boolean isExpanded); 38 39 @Override 40 public void onItemClick(final AdapterView<?> parent, final View view, final int position, final long id) 41 { 42 final NLevelTreeNode item = getItem(position); 43 if (this.mExpandedNodeList.contains(item)) 44 { 45 //把展开的节点,收缩起来 46 this.mExpandedNodeList.remove(item); 47 48 final int nextPosition = position + 1; 49 while (true) 50 { 51 //说明已经删除到最后一个节点了。 52 if (nextPosition >= this.getOriginaItems().size()) 53 break; 54 55 final NLevelTreeNode tmpNode = this.getOriginaItems().get(nextPosition); 56 57 //只删除比它自己级别深的节点(如它的子、孙、重孙节点) 58 if (tmpNode.getLevel() <= item.getLevel()) 59 break; 60 61 this.getOriginaItems().remove(tmpNode); 62 63 //防止它的子孙节点也有可能是展开的,所有也要移除其状态。 64 this.mExpandedNodeList.remove(tmpNode); 65 } 66 this.refresh(); 67 } 68 else 69 { 70 //没有子节点,则不允许展开 71 if (item.getChilds().size() == 0) 72 { 73 //默认只支持叶子节点的Click事件 74 if (this.mOuterOnItemClickListener != null) 75 this.mOuterOnItemClickListener.onItemClick(parent, view, position, id); 76 return; 77 } 78 79 //把收缩的节点,展开起来 80 this.mExpandedNodeList.add(item); 81 82 this.getOriginaItems().addAll(position + 1, item.getChilds()); 83 84 this.refresh(); 85 } 86 } 87 88 /** 89 * 设置外围调用者真正需要的 项点击OnItemClickListener 事件 回调。 90 */ 91 protected void setOuterOnItemClickListener(final AdapterView.OnItemClickListener listener) 92 { 93 this.mOuterOnItemClickListener = listener; 94 } 95 }
1 package cn.asiontang.nleveltreelistview; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 /** 7 * @author AsionTang 8 * @since 2016年6月1日 18:38:43 9 */ 10 @SuppressWarnings("unused") 11 public class NLevelTreeNode 12 { 13 private final List<NLevelTreeNode> mChilds = new ArrayList<>(); 14 private CharSequence mId; 15 private int mLevel = 0; 16 private CharSequence mName; 17 private NLevelTreeNode mParentNode; 18 19 public NLevelTreeNode() 20 { 21 } 22 23 public NLevelTreeNode(final NLevelTreeNode parentNode, final int level, final CharSequence id, final CharSequence name) 24 { 25 this.setParentNode(parentNode); 26 this.setLevel(level); 27 this.setID(id); 28 this.setName(name); 29 } 30 31 public NLevelTreeNode(final int level, final CharSequence id, final CharSequence name) 32 { 33 this(null, level, id, name); 34 } 35 36 public NLevelTreeNode(final CharSequence id, final CharSequence name) 37 { 38 this(null, 0, id, name); 39 } 40 41 public NLevelTreeNode(final CharSequence name) 42 { 43 this(null, 0, name, name); 44 } 45 46 /** 47 * 为此Node添加一个子节点 48 */ 49 public NLevelTreeNode addChild(final NLevelTreeNode child) 50 { 51 if (!this.mChilds.contains(child)) 52 { 53 this.mChilds.add(child); 54 child.setParentNode(this); 55 } 56 return this; 57 } 58 59 /** 60 * 设置此Node所属的所有子节点 61 */ 62 public NLevelTreeNode addChilds(final List<NLevelTreeNode> childs) 63 { 64 for (final NLevelTreeNode child : childs) 65 this.addChild(child); 66 return this; 67 } 68 69 /** 70 * 获取此Node指定位置的子节点 71 */ 72 public NLevelTreeNode getChild(final int index) 73 { 74 return this.mChilds.get(index); 75 } 76 77 /** 78 * 获取此Node所属的所有子节点 79 */ 80 public List<NLevelTreeNode> getChilds() 81 { 82 return this.mChilds; 83 } 84 85 /** 86 * 获取当前Node 唯一标识符(当此Node被点击时,可供区分被点击的是谁) 87 */ 88 public CharSequence getID() 89 { 90 return this.mId; 91 } 92 93 /** 94 * 设置当前Node 唯一标识符(当此Node被点击时,可供区分被点击的是谁) 95 */ 96 public NLevelTreeNode setID(final CharSequence id) 97 { 98 this.mId = id; 99 return this; 100 } 101 102 /** 103 * 获取当前Node所属哪个层级;一般从0级(根节点)开始递增。 104 */ 105 public int getLevel() 106 { 107 return this.mLevel; 108 } 109 110 /** 111 * 设置当前Node所在的层级;一般从0级(根节点)开始递增。 112 */ 113 public NLevelTreeNode setLevel(final int level) 114 { 115 this.mLevel = level; 116 117 //必须立即更新子节点的级别,否则就乱套了。 118 for (final NLevelTreeNode child : this.mChilds) 119 child.setLevel(level + 1); 120 return this; 121 } 122 123 /** 124 * 获取当前Node 名字 125 */ 126 public CharSequence getName() 127 { 128 return this.mName; 129 } 130 131 /** 132 * 设置当前Node 名字 133 */ 134 public NLevelTreeNode setName(final CharSequence name) 135 { 136 this.mName = name; 137 return this; 138 } 139 140 /** 141 * 获取 此Note 的父节点 142 */ 143 public NLevelTreeNode getParentNode() 144 { 145 return this.mParentNode; 146 } 147 148 /** 149 * 设置 此Note 的父节点 150 */ 151 public NLevelTreeNode setParentNode(final NLevelTreeNode parentNode) 152 { 153 this.mParentNode = parentNode; 154 if (parentNode != null) 155 { 156 parentNode.addChild(this); 157 this.setLevel(parentNode.getLevel() + 1); 158 } 159 return this; 160 } 161 }
源码:
https://bitbucket.org/AsionTang/75.nleveltreeview/overview
https://github.com/asiontang/75.NLevelTreeView