最近项目用到树状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