一、概述:
1、效果图:
2、实现的功能:
1)缩进的树形结构
2)点击箭头可以展开与关闭
3)可以是任意层级的树
3、使用的技术:
1)子父节点关联
2)在listview树结构里的onItemClick实现函数回调OnTreeNodeClickListener
if (onTreeNodeClickListener != null) {
onTreeNodeClickListener.onClick(mNodes.get(position), position);
}
3)对节点排序:
this.mAllNodes = TreeHelper.getSortedNodes(datas, defaultExpandLevel);
4)对节点的过滤
public static List<Node> filterVisibleNode(List<Node> mAllNodes) {
List<Node> result = new ArrayList<Node>();
for (Node node : mAllNodes) {
// 根节点是必须可见的,如果父亲节点是展开的话,这个节点当然是展开的
if (node.isRoot() || node.isParentExptend()) {
setIcon(node);
result.add(node);
}
}
return result;
}
5)使用注解把数据转化为节点
protected static <T> List<Node> convetData2Node(List<T> datas) throws IllegalArgumentException, IllegalAccessException {
List<Node> nodes = new ArrayList<Node>();
for (T t : datas) {
int id = -1;
int pId = -1;
String lable = null;
// 使用反射的方法获得类的名称
Class<? extends Object> clazz = t.getClass();
// 根据类得到声明的字段
Field[] fields = clazz.getDeclaredFields();
// 遍历所有的字段
for (Field field : fields) {
// 如果字段里有注解,说明得到的字段就存在
if (field.getAnnotation(TreeNodeId.class) != null) {
field.setAccessible(true);
id = field.getInt(t);
}
if (field.getAnnotation(TreeNodePid.class) != null) {
field.setAccessible(true);
pId = field.getInt(t);
}
if (field.getAnnotation(TreeNodeLabel.class) != null) {
field.setAccessible(true);
lable = (String) field.get(t);
}
//如果都遍历了,就不需要再次遍历了
if (id != -1 && pId != -1 && lable != null) {
break;
}
}
Node node = new Node(id, pId, lable);
nodes.add(node);
}
/**
* 使用选着排序,比较两个节点的关系
*/
for (int i = 0; i < nodes.size(); i++) {
Node node1 = nodes.get(i);
// 从i+1处开始比较,使用了选择排序法
for (int j = i + 1; j < nodes.size(); j++) {
Node node2 = nodes.get(j);
// 说明,node2是node1的父类
if (node1.getpId() == node2.getId()) {
node2.getChildren().add(node1);
node1.setParent(node2);
// node1是node2的父类
} else if (node2.getpId() == node1.getId()) {
node1.getChildren().add(node2);
node2.setParent(node1);
}
}
}
/**
* 设置图片
*/
for (Node node : nodes) {
setIcon(node);
}
return nodes;
}
二、框架搭建:
1、创建实体类:
/**
* 创建文件实体类
*
* @Project App_View
* @Package com.android.view.tree
* @author chenlin
* @version 1.0
* @Date 2014年6月4日
*/
public class FileBean {
@TreeNodeId
private int _id;
@TreeNodePid
private int parentId;//父节点id
@TreeNodeLabel
private String name;//文件名称
private long length;//文件长度
private String desc;//文件描述
public FileBean() {
}
public FileBean(int _id, int parentId, String name) {
super();
this._id = _id;
this.parentId = parentId;
this.name = name;
}
public int get_id() {
return _id;
}
public void set_id(int _id) {
this._id = _id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public long getLength() {
return length;
}
public void setLength(long length) {
this.length = length;
}
public int getParentId() {
return parentId;
}
public void setParentId(int parentId) {
this.parentId = parentId;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
}
2、创建节点类:
/**
* 节点
* @Project App_View
* @Package com.android.view.tree
* @author chenlin
* @version 1.0
* @Date 2013年6月4日
* @Note TODO
*/
public class Node {
private int id;
private int pId = 0;//父节点,根节点是0
private int level;//级别
private String name;//节点名称
private int icon;//小图标
private Node parent;//父节点
//子节点
private List<Node> children = new ArrayList<Node>();
private boolean isExpend = false;//是否展开
public Node() {
}
public Node(int id, int pId, String name) {
this.id = id;
this.pId = pId;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getpId() {
return pId;
}
public void setpId(int pId) {
this.pId = pId;
}
public void setLevel(int level) {
this.level = level;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getIcon() {
return icon;
}
public void setIcon(int icon) {
this.icon = icon;
}
public Node getParent() {
return parent;
}
public void setParent(Node parent) {
this.parent = parent;
}
public List<Node> getChildren() {
return children;
}
public void setChildren(List<Node> children) {
this.children = children;
}
public boolean isExpend() {
return isExpend;
}
/**
* 设置展开时不但要展开自己,也要展开所有的子节点
* @param isExpend
*/
public void setExpend(boolean isExpend) {
this.isExpend = isExpend;
if (!isExpend) {
for(Node node : children){
node.setExpend(isExpend);
}
}
}
/**
* 获得级别
* @return
*/
public int getLevel() {
return parent == null ? 0 : parent.getLevel() + 1;
}
/**
* 判断是否是叶子节点
* @return
*/
public boolean isLeaf(){
return children.size() == 0;
}
/**
* 判断是否是根节点
* @return
*/
public boolean isRoot(){
return parent == null;
}
/**
* 判断父节点是否打开
* @return
*/
public boolean isParentExptend(){
if (parent == null) {
return false;
}
return parent.isExpend();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((children == null) ? 0 : children.hashCode());
result = prime * result + icon;
result = prime * result + id;
result = prime * result + (isExpend ? 1231 : 1237);
result = prime * result + level;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + pId;
result = prime * result + ((parent == null) ? 0 : parent.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Node other = (Node) obj;
if (children == null) {
if (other.children != null)
return false;
} else if (!children.equals(other.children))
return false;
if (icon != other.icon)
return false;
if (id != other.id)
return false;
if (isExpend != other.isExpend)
return false;
if (level != other.level)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (pId != other.pId)
return false;
if (parent == null) {
if (other.parent != null)
return false;
} else if (!parent.equals(other.parent))
return false;
return true;
}
}
3、主页面
/**
* 主页
*
* @Project App_View
* @Package com.android.view.tree
* @author chenlin
* @version 1.0
* @Date 2015年6月4日
*/
public class TreeActivity extends Activity {
private List<FileBean> mDatas = new ArrayList<FileBean>();
private ListView mListView;
@SuppressWarnings("rawtypes")
private TreeListViewAdapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tree);
initDatas();
init();
}
private void init() {
try {
mListView = (ListView) findViewById(R.id.lv_tree);
mAdapter = new SimpleTreeAdapter<FileBean>(mListView, this, mDatas, 10);
mListView.setAdapter(mAdapter);
mAdapter.setOnTreeNodeClickListener(new OnTreeNodeClickListener() {
@Override
public void onClick(Node node, int position) {
Toast.makeText(TreeActivity.this, node.getName(), Toast.LENGTH_LONG).show();
}
});
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
private void initDatas() {
// id , pid , label , 其他属性
mDatas.add(new FileBean(1, 0, "文件管理系统"));
mDatas.add(new FileBean(2, 1, "游戏"));
mDatas.add(new FileBean(3, 1, "文档"));
mDatas.add(new FileBean(4, 1, "程序"));
mDatas.add(new FileBean(5, 2, "war3"));
mDatas.add(new FileBean(6, 2, "刀塔传奇"));
mDatas.add(new FileBean(7, 4, "面向对象"));
mDatas.add(new FileBean(8, 4, "非面向对象"));
mDatas.add(new FileBean(9, 7, "C++"));
mDatas.add(new FileBean(10, 7, "JAVA"));
mDatas.add(new FileBean(11, 7, "Javascript"));
mDatas.add(new FileBean(12, 8, "C"));
}
}
4、布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<ListView
android:id="@+id/lv_tree"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="#aaa"
android:dividerHeight="1px" >
</ListView>
</LinearLayout>
三、实现适配器
1、添加基本适配器
/**
* 树形结构适配器
*
* @Project App_View
* @Package com.android.view.tree
* @author chenlin
* @version 1.0
* @Date 2014年6月4日
* @Note TODO
*/
public abstract class TreeListViewAdapter<T> extends BaseAdapter {
protected ListView mListView;
protected Context mContext;
/** 存储所有可见的Node */
protected List<Node> mNodes;
/** 存储所有的Node */
protected List<Node> mAllNodes;
protected LayoutInflater mInflater;
/*********************************************************************************
* 点击的回调接口
*/
private OnTreeNodeClickListener onTreeNodeClickListener;
public interface OnTreeNodeClickListener {
void onClick(Node node, int position);
}
public void setOnTreeNodeClickListener(OnTreeNodeClickListener onTreeNodeClickListener) {
this.onTreeNodeClickListener = onTreeNodeClickListener;
}
/************************************************************************************/
public TreeListViewAdapter(ListView mTree, Context context, List<T> datas, int defaultExpandLevel) throws IllegalArgumentException,
IllegalAccessException {
this.mContext = context;
this.mInflater = LayoutInflater.from(context);
// 对所有的Node进行排序
this.mAllNodes = TreeHelper.getSortedNodes(datas, defaultExpandLevel);
// 过滤出可见的Node
this.mNodes = TreeHelper.filterVisibleNode(mAllNodes);
// 点击item事件
mTree.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// 点击时展开或关闭item
expandOrCollapse(position);
//使用回调函数
if (onTreeNodeClickListener != null) {
onTreeNodeClickListener.onClick(mNodes.get(position), position);
}
}
});
}
/**
* 点击时展开或关闭item
* @param position
*/
protected void expandOrCollapse(int position) {
Node node = mNodes.get(position);
if (node!= null) {
if (!node.isLeaf()) {
//表示如果关闭的就打开,如果打开的就关闭
node.setExpend(!node.isExpend());
//重新赋值
mNodes = TreeHelper.filterVisibleNode(mAllNodes);
//通知视图改变了
notifyDataSetChanged();
}
}
}
@Override
public int getCount() {
return mNodes.size();
}
@Override
public Object getItem(int position) {
return mNodes.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Node node = mNodes.get(position);
convertView = getConvertView(node, position, convertView, parent);
// 设置内边距, 层级越大,则离左边的距离越大
convertView.setPadding(node.getLevel() * 30, 3, 3, 3);
return convertView;
}
public abstract View getConvertView(Node node, int position, View convertView, ViewGroup parent);
}
2、适配器实现类
/**
* 简单适配器
*
* @Project App_View
* @Package com.android.view.tree
* @author chenlin
* @version 1.0
* @Date 2014年6月4日
* @param <T>
*/
public class SimpleTreeAdapter<T> extends TreeListViewAdapter<T> {
public SimpleTreeAdapter(ListView mTree, Context context, List<T> datas, int defaultExpandLevel) throws IllegalArgumentException,
IllegalAccessException {
super(mTree, context, datas, defaultExpandLevel);
}
@Override
public View getConvertView(Node node, int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mInflater.inflate(R.layout.list_tree_item, parent, false);
}
ViewHolder viewHolder = ViewHolder.getHolder(convertView);
// 如果图标不存在,就隐藏
if (node.getIcon() == -1) {
viewHolder.icon.setVisibility(View.INVISIBLE);
} else {
viewHolder.icon.setVisibility(View.VISIBLE);
viewHolder.icon.setImageResource(node.getIcon());
}
viewHolder.label.setText(node.getName());
return convertView;
}
static class ViewHolder {
ImageView icon;
TextView label;
public static ViewHolder getHolder(View view) {
Object tag = view.getTag();
if (tag != null) {
return (ViewHolder) tag;
} else {
ViewHolder viewHolder = new ViewHolder();
viewHolder.icon = (ImageView) view.findViewById(R.id.id_treenode_icon);
viewHolder.label = (TextView) view.findViewById(R.id.id_treenode_label);
view.setTag(viewHolder);
return viewHolder;
}
}
}
}
四、树形结构实现
1、帮助类
/**
* 树结构帮助类
*
* @Project App_View
* @Package com.android.view.tree
* @author chenlin
* @version 1.0
* @Date 2014年6月4日
* @Note TODO
*/
public class TreeHelper {
private static final String TAG = "ture";
/**
* 得到排好序的节点
*
* @param datas
* @param defaultExpandLevel
* @return
* @throws IllegalArgumentException
* @throws IllegalAccessException
*/
public static <T> List<Node> getSortedNodes(List<T> datas, int defaultExpandLevel) throws IllegalArgumentException,
IllegalAccessException {
List<Node> result = new ArrayList<Node>();
// 1.将用户数据转化为List<Node>以及设置Node间关系
List<Node> nodes = convetData2Node(datas);
// 2.拿到跟节点
List<Node> rootNodes = getRootNodes(nodes);
// 3.依次展开排序把字节点添加到根节点
for (Node node : rootNodes) {
addNode(result, node, defaultExpandLevel, 1);
}
return result;
}
/**
* 把数据转化为节点数据
*
* @param datas
* @return
* @throws IllegalArgumentException
* @throws IllegalAccessException
*/
protected static <T> List<Node> convetData2Node(List<T> datas) throws IllegalArgumentException, IllegalAccessException {
List<Node> nodes = new ArrayList<Node>();
for (T t : datas) {
int id = -1;
int pId = -1;
String lable = null;
// 使用反射的方法获得类的名称
Class<? extends Object> clazz = t.getClass();
// 根据类得到声明的字段
Field[] fields = clazz.getDeclaredFields();
// 遍历所有的字段
for (Field field : fields) {
// 如果字段里有注解,说明得到的字段就存在
if (field.getAnnotation(TreeNodeId.class) != null) {
field.setAccessible(true);
id = field.getInt(t);
}
if (field.getAnnotation(TreeNodePid.class) != null) {
field.setAccessible(true);
pId = field.getInt(t);
}
if (field.getAnnotation(TreeNodeLabel.class) != null) {
field.setAccessible(true);
lable = (String) field.get(t);
}
//如果都遍历了,就不需要再次遍历了
if (id != -1 && pId != -1 && lable != null) {
break;
}
}
Node node = new Node(id, pId, lable);
nodes.add(node);
}
/**
* 使用选着排序,比较两个节点的关系
*/
for (int i = 0; i < nodes.size(); i++) {
Node node1 = nodes.get(i);
// 从i+1处开始比较,使用了选择排序法
for (int j = i + 1; j < nodes.size(); j++) {
Node node2 = nodes.get(j);
// 说明,node2是node1的父类
if (node1.getpId() == node2.getId()) {
node2.getChildren().add(node1);
node1.setParent(node2);
// node1是node2的父类
} else if (node2.getpId() == node1.getId()) {
node1.getChildren().add(node2);
node2.setParent(node1);
}
}
}
/**
* 设置图片
*/
for (Node node : nodes) {
setIcon(node);
}
return nodes;
}
/**
* 最主要根据节点是否有字节点和是否展开来判断显示什么样的图标 如果子节点是展开的,用- 否则有+
*
* @param node
*/
private static void setIcon(Node node) {
Logger.i(TAG, "node.isExpend() == " + node.isExpend());
Logger.i(TAG, "node.getChildren().size() == " + node.getChildren().size());
if (node.getChildren().size() > 0 && node.isExpend()) {
node.setIcon(R.drawable.tree_ex);
} else if (node.getChildren().size() > 0 && !node.isExpend()) {
node.setIcon(R.drawable.tree_ec);
} else {
// 设置为-1时会在适配器里判断,如果为-1就隐藏
node.setIcon(-1);
}
}
/**
* 把一个节点上的所有的内容都挂上去
*
* @param nodes
* @param node
* @param defaultExpandLevel
* @param i
*/
protected static void addNode(List<Node> nodes, Node node, int defaultExpandLevel, int currentLevel) {
//添加到集合里
nodes.add(node);
// 如果传进来的<currentLevel,说明在下一级,展开
if (defaultExpandLevel >= currentLevel) {
node.setExpend(true);
}
if (node.isLeaf()) {
return;
}
// 使用递归,展开所有的子node
for (int i = 0; i < node.getChildren().size(); i++) {
addNode(nodes, node.getChildren().get(i), defaultExpandLevel, currentLevel + 1);
}
}
/**
* 判断是否是根节点,只要判断是否是isRoot();
*
* @param nodes
* @return
*/
protected static List<Node> getRootNodes(List<Node> nodes) {
List<Node> result = new ArrayList<Node>();
for (Node node : nodes) {
if (node.isRoot()) {
result.add(node);
}
}
return result;
}
/**
* 过滤出所有可见的Node
*
* @param mAllNodes
* @return
*/
public static List<Node> filterVisibleNode(List<Node> mAllNodes) {
List<Node> result = new ArrayList<Node>();
for (Node node : mAllNodes) {
// 根节点是必须可见的,如果父亲节点是展开的话,这个节点当然是展开的
if (node.isRoot() || node.isParentExptend()) {
setIcon(node);
result.add(node);
}
}
return result;
}
}
二、几个注解文件
@Target({ ElementType.FIELD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface TreeNodeId {
}
@Target({ ElementType.FIELD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface TreeNodeLabel {
}
@Target({ ElementType.FIELD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface TreeNodePid {
}