最近项目用到树状ListView,要可展开收起。Android自带的ExpandableListView不太适合扩展,看了网上一些实现,发现通用性不是很好,于是参考可取之处,自己写了一个比较通过的实现。效果如下:
1、首先是数据模型。
public class Node<T extends Node> implements Comparable<Node> { /** 自己的id */ public int id; /** 上一层id */ public int pId; /** 层级 */ public int level; /** 是否展开 */ public boolean isExpand; /** 子节点 */ public List<T> childNodes; public Node() { } public Node(int id, int pId, int level, boolean isExpand) { this.id = id; this.pId = pId; this.level = level; this.isExpand = isExpand; } @Override public int compareTo(@NonNull Node o) { return id - o.id; } public boolean hasChild() { return childNodes != null && !childNodes.isEmpty(); } public void addChild(T node) { if (childNodes == null) { childNodes = new ArrayList<>(); } childNodes.add(node); } }2、然后就是ListView的数据适配器了。
public abstract class TreeAdapter<T extends Node<T>> extends BaseAdapter { private List<T> totalNodes = new ArrayList<>(); private List<T> showNodes = new ArrayList<>(); private List<T> firstLevelNodes = new ArrayList<>(); private SparseIntArray addedChildNodeIds = new SparseIntArray(); private OnInnerItemClickListener<T> listener; private OnInnerItemLongClickListener<T> longListener; public interface OnInnerItemClickListener<T> { void onClick(T node); } public interface OnInnerItemLongClickListener<T> { void onLongClick(T node); } public TreeAdapter(List<T> nodes) { setNodes(nodes); } public void setOnInnerItemClickListener(OnInnerItemClickListener<T> listener) { this.listener = listener; } public void setOnInnerItemLongClickListener(OnInnerItemLongClickListener<T> listener) { longListener = listener; } public void setNodes(List<T> nodes) { if (nodes != null) { totalNodes = nodes; //过滤出显示的节点 init(); super.notifyDataSetChanged(); } } private void init() { showNodes.clear(); initNodes(); addedChildNodeIds.clear(); showNodes.addAll(firstLevelNodes); filterShowAndSortNodes(); } @Override public void notifyDataSetChanged() { init(); super.notifyDataSetChanged(); } private void initNodes() { firstLevelNodes.clear(); //先循环一次,获取最小的level Integer level = null; for (T node : totalNodes) { if (level == null || level > node.level) { level = node.level; } } for (T node : totalNodes) { //过滤出最外层 if (node.level == level) { firstLevelNodes.add(node); } //清空之前添加的 if (node.hasChild()) { node.childNodes.clear(); } //给节点添加子节点并排序 for (T t : totalNodes) { if (node.id == t.id && node != t) { throw new IllegalArgumentException("id cannot be duplicated"); } if (node.id == t.pId && node.level != t.level) { node.addChild(t); } } if (node.hasChild()) { Collections.sort(node.childNodes); } } Collections.sort(firstLevelNodes); } private void filterShowAndSortNodes() { for (int i = 0; i < showNodes.size(); i++) { T node = showNodes.get(i); int value = addedChildNodeIds.get(node.id); if (value == 0 && node.isExpand && node.hasChild()) { List<T> list = new ArrayList<>(showNodes); list.addAll(i + 1, node.childNodes); showNodes = list; addedChildNodeIds.put(node.id, 1); filterShowAndSortNodes(); break; } } } @Override public int getCount() { return showNodes.size(); } @Override public T getItem(int position) { return showNodes.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { Holder<T> holder; if (convertView == null) { holder = getHolder(position); } else { holder = (Holder<T>) convertView.getTag(); } T node = showNodes.get(position); holder.setData(node); holder.position = position; View view = holder.getConvertView(); view.setOnClickListener(clickListener); if (!node.hasChild()) { view.setOnLongClickListener(longClickListener); } return view; } public abstract static class Holder<T> { private View convertView; int position; public Holder() { convertView = createConvertView(); convertView.setTag(this); } public View getConvertView() { return convertView; } /** * 设置数据 */ protected abstract void setData(T node); /** * 创建界面 */ protected abstract View createConvertView(); } protected abstract Holder<T> getHolder(int position); private View.OnClickListener clickListener = new View.OnClickListener() { @Override public void onClick(View v) { Holder<T> holder = (Holder<T>) v.getTag(); T node = showNodes.get(holder.position); if (node.hasChild()) { node.isExpand = !node.isExpand; if (!node.isExpand) { fold(node.childNodes); } showNodes.clear(); addedChildNodeIds.clear(); showNodes.addAll(firstLevelNodes); filterShowAndSortNodes(); TreeAdapter.super.notifyDataSetChanged(); } else if (listener != null) { listener.onClick(node); } } }; private View.OnLongClickListener longClickListener = new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { if (longListener != null) { Holder<T> holder = (Holder<T>) v.getTag(); longListener.onLongClick(showNodes.get(holder.position)); } return true; } }; //递归收起节点及子节点 private void fold(List<T> list) { for (T t : list) { t.isExpand = false; if (t.hasChild()) { fold(t.childNodes); } } } }3、以上两个文件直接写成成库,然后就可以使用了。
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ListView lv = new ListView(this); setContentView(lv, new ViewGroup.LayoutParams(-1, -1)); final List<Item> list = new ArrayList<>(); list.add(new Item(0, 0, 0, false, "Android")); list.add(new Item(1, 0, 1, false, "Service")); list.add(new Item(2, 0, 1, false, "Activity")); list.add(new Item(3, 0, 1, false, "Receiver")); list.add(new Item(4, 0, 0, true, "Java Web")); list.add(new Item(5, 4, 1, false, "CSS")); list.add(new Item(6, 4, 1, false, "Jsp")); list.add(new Item(7, 4, 1, true, "Html")); list.add(new Item(8, 7, 2, false, "p")); final MyAdapter adapter = new MyAdapter(list); adapter.setOnInnerItemClickListener(new TreeAdapter.OnInnerItemClickListener<Item>() { @Override public void onClick(Item node) { Toast.makeText(MainActivity.this, "click: " + node.name, Toast.LENGTH_SHORT).show(); } }); adapter.setOnInnerItemLongClickListener(new TreeAdapter.OnInnerItemLongClickListener<Item>() { @Override public void onLongClick(Item node) { Toast.makeText(MainActivity.this, "long click: " + node.name, Toast.LENGTH_SHORT).show(); } }); lv.setAdapter(adapter); new Handler().postDelayed(new Runnable() { @Override public void run() { list.add(new Item(9, 7, 2, false, "a")); adapter.notifyDataSetChanged(); } }, 2000); } private class MyAdapter extends TreeAdapter<Item> { MyAdapter(List<Item> nodes) { super(nodes); } @Override public int getViewTypeCount() { return super.getViewTypeCount() + 1; } /** * 获取当前位置的条目类型 */ @Override public int getItemViewType(int position) { if (getItem(position).hasChild()) { return 1; } return 0; } @Override protected Holder<Item> getHolder(int position) { switch(getItemViewType(position)) { case 1: return new Holder<Item>() { private ImageView iv; private TextView tv; @Override protected void setData(Item node) { iv.setVisibility(node.hasChild() ? View.VISIBLE : View.INVISIBLE); iv.setBackgroundResource(node.isExpand ? R.mipmap.expand : R.mipmap.fold); RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) iv.getLayoutParams(); params.leftMargin = (node.level + 1) * dip2px(20); iv.setLayoutParams(params); tv.setText(node.name); } @Override protected View createConvertView() { View view = View.inflate(MainActivity.this, R.layout.item_tree_list_has_child, null); iv = (ImageView) view.findViewById(R.id.ivIcon); tv = (TextView) view.findViewById(R.id.tvName); return view; } }; default: return new Holder<Item>() { private TextView tv; @Override protected void setData(Item node) { tv.setText(node.name); RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) tv.getLayoutParams(); params.leftMargin = (node.level + 3) * dip2px(20); tv.setLayoutParams(params); } @Override protected View createConvertView() { View view = View.inflate(MainActivity.this, R.layout.item_tree_list_no_child, null); tv = (TextView) view.findViewById(R.id.tvName); return view; } }; } } } /** * 根据手机的分辨率从 dip 的单位 转成为 px(像素) */ public int dip2px(float dpValue) { final float scale = getResources().getDisplayMetrics().density; return (int) (dpValue * scale + 0.5f); } private class Item extends Node<Item> { String name; Item(int id, int pId, int level, boolean isExpand, String name) { super(id, pId, level, isExpand); this.name = name; } } }
源码: https://github.com/fszeng2011/treeadapter