您的位置:首页 > 移动开发 > Android开发

Android 使用listview实现树形结构

2016-06-08 09:38 751 查看

一、概述:

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 {

}


五、源码下载:

链接:http://pan.baidu.com/s/1hsfiABM 密码:goev

———————————————————————

有需求者请加qq:136137465,非诚勿扰

(java 架构师全套教程,共760G, 让你从零到架构师,每月轻松拿3万)

01.高级架构师四十二个阶段高

02.Java高级系统培训架构课程148课时

03.Java高级互联网架构师课程

04.Java互联网架构Netty、Nio、Mina等-视频教程

05.Java高级架构设计2016整理-视频教程

06.架构师基础、高级片

07.Java架构师必修linux运维系列课程

08.Java高级系统培训架构课程116课时

(送:hadoop系列教程,java设计模式与数据结构, Spring Cloud微服务, SpringBoot入门)

——————————————————————–
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息