第一次接触树形ListView是一年前,当时公司做的项目是一个企业的员工管理软件,在展示员工时用到的,我花了大半天时间,才把树形ListView搞明白,完成任务后就没有然后了(当时主管还对我说要注意代码的积累,可我没在意)。今年五月份来北京找工作,找了两个月才找到工作(对这份工作还不能满意,没办法还得要吃饭),这两个月,浮躁的心也静下来了,做技术的就踏踏实实的把技术搞好,再去想其他的。废话不多说了说下这个树形ListView吧,做了些封装,只要明白原理,用的时候只需根据自己的需求修改些地方就可以了。
我封装的这个树形ListView针对数据是有从属关系的数据。
针对数据,
第一步:将数据通过反射和注解转化为想要的数据
第二步:理清数据关系(简单说,就是找出谁是谁的爹,谁是谁的儿)
第三步:将数据排序(要知道ListView是将数据源里的数据挨个展现出来的,所以顺序很重要)
这是针对数据的操作
怎么将树形层级展示出来呢?通过View 的位置的后移做到的,层级越高,放的距离越远离屏幕左方(一个固定距离*层级数)
如何实现展开和缩放(展开一些子节点就会展示出来,收缩一些数据就会隐藏)
通过两个数据源,一个数据源为显示的数据源,另一个为全部数据源,根据结点状态过滤出显示数据源。
上面这些就是树形ListView的大体思想
首先介绍下代码结构
MainActivity,SimpleTreeAdapter,FileNode只是些使用时的一些类,
Node,TreeHelper,TreeListViewAdapter等这些是我们封装的
首先说下Node,Node的类是我们封装的数据源,我们要将数据转化为Node类
int id;
int pid = 0;
/**
*名称
*/
String label;
/**
* 层级
*/
int level;
/**
* 当前是否展开
*/
private boolean isExpand =false;
int index;
private int icon;
Node parent;
List<Node> children = new ArrayList<Node>();
public Node(int id, int pid, String label) {
super();
this.id = id;
this.pid = pid;
this.label = label;
}
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 String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
/**
* 节点的层级
* @return
*/
public int getLevel() {
return parent ==null?0:parent.getLevel()+1;
}
public void setLevel(int level) {
this.level = level;
}
public boolean isExpand() {
return isExpand;
}
public void setExpand(boolean isExpand) {
this.isExpand = isExpand;
if(!isExpand){
for(Node n :children){
n.setExpand(false);
}
}
}
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 isRoot(){
return parent == null;
}
/**
* 父节点是否展开
* @return
*/
public boolean isParentExpand(){
if(parent == null)
return true;
return parent.isExpand();
}
/**
* 是否是叶节点
* @return
*/
public boolean isLeaf(){
return children.size() == 0;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
setExpand,getLevel这两个方法挺有意思的,主要是一种递归的思想
TreeHelper类是这个Tree ListView的核心思想
/**
* 将用户数据转化为Node
* @param datas
* @return
* @throws IllegalAccessException
* @throws IllegalArgumentException
*/
public static <T> List<Node> convertToNodes(List<T> datas) throws IllegalAccessException, IllegalArgumentException{
List<Node> nodes = new ArrayList<Node>();
Log.i("TAG", "convertToNodes data="+datas.size()+"");
int index = 0;
for(T t: datas){
Node node =null;
Class clazz = t.getClass();
Field[] fileds = clazz.getDeclaredFields();
int id =-1;
int pid=-1;
String label=null;
Log.i("TAG", "fileds.length="+fileds.length);
for(Field f :fileds){
if(f.getAnnotation(TreeNodeId.class)!=null){
f.setAccessible(true);
id = f.getInt(t);
Log.i("TAG", "通过反射注解获取到注解");
}else{
Log.i("TAG", "通过反射注解获取失败");
}
if(f.getAnnotation(TreeNodePid.class)!=null){
f.setAccessible(true);
pid = f.getInt(t);
Log.i("TAG", "通过反射注解获取到注解");
}else{
Log.i("TAG", "通过反射注解获取失败");
}
if(f.getAnnotation(TreeNodeLabel.class)!=null){
f.setAccessible(true);
label = (String) f.get(t);
Log.i("TAG", "convertToNodes label="+label+"");
Log.i("TAG", "通过反射注解获取到注解");
}else{
Log.i("TAG", "通过反射注解获取失败");
}
}
Log.i("TAG", "convertToNodes id,pid,label="+id+"-"+pid+"-"+label);
node = new Node(id, pid, label);
node.setIndex(index);
nodes.add(node);
index++;
}
Log.i("TAG", "convertToNodes nodes="+nodes.size()+"");
//关联关系
for(int i =0;i<nodes.size();i++){
Node n = nodes.get(i);
for(int j =i+1;j<nodes.size();j++){
Node m = nodes.get(j);
if(n.getPid() == m.getId()){
n.parent = m;
m.getChildren().add(n);
}
if(n.getId()==m.getPid()){
m.parent =n;
n.getChildren().add(m);
}
}
}
//设置图片
for(Node n :nodes){
setNodeIcon(n);
Log.i("TAG", "标签内容="+n.getLabel());
}
return nodes;
}
/**
* 设置图标
* @param n
*/
private static void setNodeIcon(Node n) {
if(n.getChildren().size()>0&&n.isExpand()){
n.setIcon(R.drawable.expand);
}else if(n.getChildren().size()>0&&!n.isExpand()){
n.setIcon(R.drawable.shrink);
}else{
n.setIcon(-1);
}
}
public static <T> List<Node> getSortedNodes(List<T> datas,int defaultExpandLevel) throws IllegalAccessException, IllegalArgumentException{
List<Node> result = new ArrayList<Node>();
List<Node> nodes = convertToNodes(datas);
List<Node> rootNodes = getRootNodes(nodes);
for(Node node : rootNodes){
addNode(result,node,defaultExpandLevel,1);
}
return result;
}
private static void addNode(List<Node> result, Node node,
int defaultExpandLevel, int curLevel) {
node.setLevel(curLevel);
result.add(node);
if(node.isLeaf())
return ;
if(defaultExpandLevel>=curLevel){
node.setExpand(true);
}else{
node.setExpand(false);
}
for(Node n:node.getChildren()){
addNode(result, n, defaultExpandLevel, curLevel+1);
}
}
/**
* 过滤出要显示的
* @param nodes
* @return
*/
public static List<Node> filterVisbleNode(List<Node> nodes){
List<Node> visbleNode = new ArrayList<Node>();
for(Node n :nodes){
if(n.isRoot()||n.isParentExpand()){
setNodeIcon(n);
visbleNode.add(n);
}
}
return visbleNode;
}
/**
* 获取根节点
* @param nodes
* @return
*/
private static List<Node> getRootNodes(List<Node> nodes) {
List<Node> rootNodes = new ArrayList<Node>();
for(Node n :nodes){
if(n.isRoot()){
setNodeIcon(n);
rootNodes.add(n);
}
}
return rootNodes;
}
包含了,数据的转化(反射和注释准备下篇文章在阐述),数据的排序,数据的排序包括认亲,递归获取数据,数据
的过滤
protected Context context;
protected List<Node> mAllNodes;
protected List<Node> visbleNodes;
protected LayoutInflater inflater;
protected ListView mTree;
public interface OnTreeNodeClickListener{
void onClick(Node node,int position);
}
private OnTreeNodeClickListener mListener;
public void setOnTreeNodeClickListener(OnTreeNodeClickListener mClickListener){
this.mListener =mClickListener;
}
public <T> TreeListViewAdapter(ListView mTree, Context context,List<T> datas,int defaultExpandLevel) throws IllegalAccessException, IllegalArgumentException {
// TODO Auto-generated constructor stub data
this.context = context;
mAllNodes =TreeHelper.getSortedNodes(datas, defaultExpandLevel);
visbleNodes = TreeHelper.filterVisbleNode(mAllNodes);
Log.i("TAG", "TreeListViewAdapter"+visbleNodes.size());
Log.i("TAG", "TreeListViewAdapter"+mAllNodes.size());
inflater = LayoutInflater.from(context);
this.mTree = mTree;
this.mTree.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
expandOrCollapse(position);
if(mListener!=null){
mListener.onClick(visbleNodes.get(position), position);
}
}
});
}
/**
* 点击收缩或展开
* @param position
*/
protected void expandOrCollapse(int position) {
Node n = visbleNodes.get(position);
if(n!=null){
if(n.isLeaf()){
return ;
}
n.setExpand(!n.isExpand());
visbleNodes = TreeHelper.filterVisbleNode(mAllNodes);
notifyDataSetChanged();
}
}
@Override
public int getCount() {
// TODO Auto-generated method stub
return visbleNodes.size();
}
@Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return visbleNodes.get(position);
}
@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Node n = visbleNodes.get(position);
convertView = getConvertView(visbleNodes.get(position), position, convertView, parent);
Log.i("---------", "getView="+visbleNodes.get(position).getLabel());
convertView.setPadding(30*n.getLevel(), 3, 3, 3);
return convertView;
}
public abstract View getConvertView(Node node ,int position,View convertView,ViewGroup parent);
这是将树形adapter封装一次,让外层调用者少操些ListView的心,
这三个类是树形ListView的核心类,在项目结构上看到的最后三个类是注释类,自己bean必须加上注释才能便于我解析,否则就无法解析,就不能正常转换数据。
望各位路过的神不吝赐教