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

android 创建多层树型结构

2015-12-29 15:33 429 查看
在项目开发过程中、遇到一个可能大家都经常会遇到的问题:在一个页面中要显示一个树型结构图,例如组织结构、文件系统等等。一开始是第一层、下面是第二层、第三层....

例如:



这样的结构图、想必肯定很常见,最近学习了一种比较好的方法,记录以及分享。

功能分析

一看这个结构,其实就是一个listview,只是在特定情况下显示部分层次的内容以及不显示其它层次内容,既然我们要实现树形结构、那么父层次跟子层次肯定是有联系的,也就是说每一条记录都应该有自己的id,而且必须有一个字段指向父类的Id。这样我们我们就可以理清它们之间的关系了。

功能实现

首先我们要准备一个基础数据bean:

package com.test.tree;

import com.test.tree.utils.TreeNodeId;
import com.test.tree.utils.TreeNodeLabel;
import com.test.tree.utils.TreeNodePid;

/**
 * Created by fuweiwei on 2015/12/24.
 */
public class BaseTreeBean {
    @TreeNodeId
    private String id;   //自己id
    @TreeNodePid
    private  String parentId; //上一层id
    @TreeNodeLabel
    private String name; //名字

    public BaseTreeBean(){

    }
    public BaseTreeBean(String id, String parentId, String name){
        this.id = id;
        this.parentId = parentId;
        this.name = name;

    }
    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getParentId() {
        return parentId;
    }

    public void setParentId(String parentId) {
        this.parentId = parentId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
这就是我们每一条记录的bean,这里我们使用了注解,后面方便我们通过发射机制来获取值。

然后我们就开始填充数据,创建树形结构:

package com.test.tree;

import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.widget.ListView;
import android.widget.Toast;

import com.test.tree.utils.Node;
import com.test.tree.utils.TreeListAdapter;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by fuweiwei on 2015/12/24.
 */
public class MainActivity extends FragmentActivity {
    private List<BaseTreeBean> mDatas = new ArrayList<BaseTreeBean>();
    private ListView mLvTree;
    private SimpleTreeAdapter mAdapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mLvTree = (ListView) findViewById(R.id.listview);
        initData();

    }
    public  void initData(){
        //增加第一层数据
        mDatas.add(new BaseTreeBean("1","0","文件夹"));
        //增加第二层数据
        mDatas.add(new BaseTreeBean("2","1","学习"));
        mDatas.add(new BaseTreeBean("3","1","游戏"));
        mDatas.add(new BaseTreeBean("4","1","娱乐"));
        //增加第三层数据
        mDatas.add(new BaseTreeBean("5","2","数学资料"));
        mDatas.add(new BaseTreeBean("6","2","语文资料"));
        mDatas.add(new BaseTreeBean("7","2","英语资料"));

        mDatas.add(new BaseTreeBean("8","3","竞技游戏"));
        mDatas.add(new BaseTreeBean("9","3","休闲游戏"));

        mDatas.add(new BaseTreeBean("10","4","电影"));
        mDatas.add(new BaseTreeBean("11","4","音乐"));
        //增加第四层数据
        mDatas.add(new BaseTreeBean("12","5","小学数学"));
        mDatas.add(new BaseTreeBean("13","5","初中数学"));
        mDatas.add(new BaseTreeBean("14","5","高中数学"));

        mDatas.add(new BaseTreeBean("15","8","英雄联盟"));
        mDatas.add(new BaseTreeBean("16","8","穿越火线"));

        mDatas.add(new BaseTreeBean("17","10","大话西游"));
        mDatas.add(new BaseTreeBean("18","10","功夫足球"));
        //可以一直添加数据层

        try {
            mAdapter = new SimpleTreeAdapter<BaseTreeBean>(mLvTree, MainActivity.this, mDatas, 0);
            mAdapter.setOnTreeNodeClickListener(new TreeListAdapter.OnTreeNodeClickListener() {
                @Override
                public void onClick(Node node, int position) {
                    if(node.isLeaf()&&node.getLevel()==3){
                        Toast.makeText(MainActivity.this,node.getName(),Toast.LENGTH_SHORT).show();
                    }

                }
            });
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        mLvTree.setAdapter(mAdapter);
    }
}


这里的代码不多、首先制造我们需要的树型结构数据,初始化adapter 任何给我们的listview。然后我们看下我们的adapter;

package com.test.tree;

import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;

import com.test.tree.utils.Node;
import com.test.tree.utils.TreeListAdapter;

import java.util.List;

public class SimpleTreeAdapter<T> extends TreeListAdapter<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)
	{

		ViewHolder viewHolder = null;
		if (convertView == null)
		{
			convertView = mInflater.inflate(R.layout.adapter_tree_item, parent, false);
			viewHolder = new ViewHolder();
			viewHolder.icon = (ImageView) convertView
					.findViewById(R.id.id_treenode_icon);
			viewHolder.label = (TextView) convertView
					.findViewById(R.id.id_treenode_label);
			convertView.setTag(viewHolder);
			viewHolder.next = (ImageView) convertView
					.findViewById(R.id.id_treenode_next);

		} else
		{
			viewHolder = (ViewHolder) convertView.getTag();
		}

		if (node.getIcon() == -1)
		{
			viewHolder.icon.setVisibility(View.INVISIBLE);
			if(node.getLevel()==3){
				viewHolder.next.setVisibility(View.VISIBLE);
			}else{
				viewHolder.next.setVisibility(View.INVISIBLE);
			}
		} else
		{
			viewHolder.next.setVisibility(View.INVISIBLE);
			viewHolder.icon.setVisibility(View.VISIBLE);
			viewHolder.icon.setImageResource(node.getIcon());
		}
		viewHolder.label.setText(node.getName());

		return convertView;
	}

	private final class ViewHolder
	{
		ImageView icon;
		ImageView next;
		TextView label;
	}

}


这里跟我们普通继承BaseApter没什么区别,就是给每个Item 赋值,关键的地方来了,TreeListAdapter是什么?,我们来看看:

package com.test.tree.utils;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.ListView;

import java.util.List;

/**
 * Created by fuweiwei on 2015/12/24.
 */
public abstract class TreeListAdapter<T> extends BaseAdapter
{

	protected Context mContext;
	/**
	 * 存储所有可见的Node
	 */
	protected List<Node> mNodes;
	protected LayoutInflater mInflater;
	/**
	 * 存储所有的Node
	 */
	protected List<Node> mAllNodes;

	/**
	 * 点击的回调接口
	 */
	private OnTreeNodeClickListener onTreeNodeClickListener;

	public interface OnTreeNodeClickListener
	{
		void onClick(Node node, int position);
	}

	public void setOnTreeNodeClickListener(
			OnTreeNodeClickListener onTreeNodeClickListener)
	{
		this.onTreeNodeClickListener = onTreeNodeClickListener;
	}

	/**
	 *
	 * @param mTree  listview
	 * @param context 上下文对象
	 * @param datas  泛型数据
	 * @param defaultExpandLevel 默认展开几级树
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 */
	public TreeListAdapter(ListView mTree, Context context, List<T> datas,
						   int defaultExpandLevel) throws IllegalArgumentException,
			IllegalAccessException
	{
		mContext = context;
		/**
		 * 对所有的Node进行排序
		 */
		mAllNodes = TreeHelper.getSortedNodes(datas, defaultExpandLevel);
		/**
		 * 过滤出可见的Node
		 */
		mNodes = TreeHelper.filterVisibleNode(mAllNodes);
		mInflater = LayoutInflater.from(context);

		/**
		 * 设置节点点击时,可以展开以及关闭;并且将ItemClick事件继续往外公布
		 */
		mTree.setOnItemClickListener(new OnItemClickListener()
		{
			@Override
			public void onItemClick(AdapterView<?> parent, View view,
					int position, long id)
			{
				expandOrCollapse(position);

				if (onTreeNodeClickListener != null)
				{
					onTreeNodeClickListener.onClick(mNodes.get(position),
							position);
				}
			}

		});

	}

	/**
	 * 相应ListView的点击事件 展开或关闭某节点
	 * 
	 * @param position
	 */
	public void expandOrCollapse(int position)
	{
		Node n = mNodes.get(position);

		if (n != null)// 排除传入参数错误异常
		{
			if (!n.isLeaf())
			{
				n.setExpand(!n.isExpand());
				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);

}
这里的adapter继承了BaseAdapter ,大家可能注意到了这里使用的数据集并不是我们的BaseTreeBean,那是为什么呢,因为用户的数据集是不固定的,只能提供id,pId这样的属性,也就是说,用户给的bean并不适合我们用来控制显示,所有使用了Node,那我们看看Node是什么:

package com.test.tree.utils;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by fuweiwei on 2015/12/24.
 */
public class Node
{

	private String id;
	/**
	 * 根节点pId为0
	 */
	private String pId = "0";

	private String name;
	/**
	 * 当前的级别
	 */
	private int level;

	/**
	 * 是否展开
	 */
	private boolean isExpand = false;

	private int icon;

	/**
	 * 下一级的子Node
	 */
	private List<Node> children = new ArrayList<Node>();

	/**
	 * 父Node
	 */
	private Node parent;

	public Node()
	{
	}

	public Node(String id, String pId, String name)
	{
		super();
		this.id = id;
		this.pId = pId;
		this.name = name;
	}
	public int getIcon()
	{
		return icon;
	}

	public void setIcon(int icon)
	{
		this.icon = icon;
	}

	public String getId()
	{
		return id;
	}

	public void setId(String id)
	{
		this.id = id;
	}

	public String getpId()
	{
		return pId;
	}

	public void setpId(String pId)
	{
		this.pId = pId;
	}

	public String getName()
	{
		return name;
	}

	public void setName(String name)
	{
		this.name = name;
	}

	public void setLevel(int level)
	{
		this.level = level;
	}

	public boolean isExpand()
	{
		return isExpand;
	}

	public List<Node> getChildren()
	{
		return children;
	}

	public void setChildren(List<Node> children)
	{
		this.children = children;
	}

	public Node getParent()
	{
		return parent;
	}

	public void setParent(Node parent)
	{
		this.parent = parent;
	}

	/**
	 * 是否为跟节点
	 * 
	 * @return
	 */
	public boolean isRoot()
	{
		return parent == null;
	}

	/**
	 * 判断父节点是否展开
	 * 
	 * @return
	 */
	public boolean isParentExpand()
	{
		if (parent == null)
			return false;
		return parent.isExpand();
	}

	/**
	 * 是否是叶子界点
	 * 
	 * @return
	 */
	public boolean isLeaf()
	{
		return children.size() == 0;
	}

	/**
	 * 获取level
	 */
	public int getLevel()
	{
		return parent == null ? 0 : parent.getLevel() + 1;
	}

	/**
	 * 设置展开
	 * 
	 * @param isExpand
	 */
	public void setExpand(boolean isExpand)
	{
		this.isExpand = isExpand;
		if (!isExpand)
		{

			for (Node node : children)
			{
				node.setExpand(isExpand);
			}
		}
	}

}


Node包括树节点的常用属性,包括节点的关闭是否、级别已经子节点、都是用来控制显示的。既然我们数据表bean有了、那怎么跟我们用户的数据集关联起来呢,这里就要使用到一开始我们所说的反射机制了。在TreeListAdapter中有一个TreeHelper、就是用来转化处理我们所需要数据集的。那我们来看下TreeHelper:

package com.test.tree.utils;

import com.test.tree.R;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by fuweiwei on 2015/12/24.
 */
public class TreeHelper
{
	/**
	 * 传入我们的普通bean,转化为我们排序后的Node
	 * 
	 * @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>();
		// 将用户数据转化为List<Node>
		List<Node> nodes = convetData2Node(datas);
		// 拿到根节点
		List<Node> rootNodes = getRootNodes(nodes);
		// 排序以及设置Node间关系
		for (Node node : rootNodes)
		{
			addNode(result, node, defaultExpandLevel, 1);
		}
		return result;
	}

	/**
	 * 过滤出所有可见的Node
	 * 
	 * @param nodes
	 * @return
	 */
	public static List<Node> filterVisibleNode(List<Node> nodes)
	{
		List<Node> result = new ArrayList<Node>();

		for (Node node : nodes)
		{
			// 如果为跟节点,或者上层目录为展开状态
			if (node.isRoot() || node.isParentExpand())
			{
				setNodeIcon(node);
				result.add(node);
			}
		}
		return result;
	}

	/**
	 * 将我们的数据转化为树的节点
	 * 
	 * @param datas
	 * @return
	 * @throws NoSuchFieldException
	 * @throws IllegalAccessException
	 * @throws IllegalArgumentException
	 */
	private static <T> List<Node> convetData2Node(List<T> datas)
			throws IllegalArgumentException, IllegalAccessException

	{
		List<Node> nodes = new ArrayList<Node>();
		Node node = null;

		for (T t : datas)
		{
			String id = null;
			String pId =null;
			String label = null;
			Class<? extends Object> clazz = t.getClass();
			Field[] declaredFields = clazz.getDeclaredFields();
			for (Field f : declaredFields)
			{
				if (f.getAnnotation(TreeNodeId.class) != null)
				{
					f.setAccessible(true);
					id = (String) f.get(t);
				}
				if (f.getAnnotation(TreeNodePid.class) != null)
				{
					f.setAccessible(true);
					pId = (String) f.get(t);;
				}
				if (f.getAnnotation(TreeNodeLabel.class) != null)
				{
					f.setAccessible(true);
					label = (String) f.get(t);
				}

				if  (id!=null && pId != null && label != null)
				{
					break;
				}
			}
			node = new Node(id, pId, label);
			nodes.add(node);
		}

		/**
		 * 设置Node间,父子关系;让每两个节点都比较一次,即可设置其中的关系
		 */
		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 (m.getpId().equals( n.getId()))
				{
					n.getChildren().add(m);
					m.setParent(n);
				} else if (m.getId().equals( n.getpId()))
				{
					m.getChildren().add(n);
					n.setParent(m);
				}
			}
		}

		// 设置图片
		for (Node n : nodes)
		{
			setNodeIcon(n);
		}
		return nodes;
	}

	private static List<Node> getRootNodes(List<Node> nodes)
	{
		List<Node> root = new ArrayList<Node>();
		for (Node node : nodes)
		{
			if (node.isRoot())
				root.add(node);
		}
		return root;
	}

	/**
	 * 把一个节点上的所有的内容都挂上去
	 */
	private static void  addNode(List<Node> nodes, Node node,
			int defaultExpandLeval, int currentLevel)
	{

		nodes.add(node);
		if (defaultExpandLeval >= currentLevel)
		{
			node.setExpand(true);
		}

		if (node.isLeaf())
			return;
		for (int i = 0; i < node.getChildren().size(); i++)
		{
			addNode(nodes, node.getChildren().get(i), defaultExpandLeval,
					currentLevel + 1);
		}
	}

	/**
	 * 设置节点的图标
	 * 
	 * @param node
	 */
	private static void setNodeIcon(Node node)
	{
		if (node.getChildren().size() > 0 && node.isExpand())
		{
			node.setIcon(R.drawable.icon_tree_ex);
		} else if (node.getChildren().size() > 0 && !node.isExpand())
		{
			node.setIcon(R.drawable.icon_tree_ec);
		} else
			node.setIcon(-1);

	}

}


方法我们一个个来看:

1、convetData2Node:遍历传进来的bean,通过注解加反射获取id、pid、label转化为Node,然后设置各个Node之间的关系

2、getRootNodes:通过遍历获取根节点

3、addNode:通过递归的方式、把一个节点上的所有的子节点都按顺序放入

4、setNodeIcon:这个比较简单,就是设置每个Item前面的小图标

5、getSortedNodes:这个上面这些方法,把我们传入的数据集转换我们需要的Node数据集

6、filterVisibleNode:通过遍历获取所有可见的Node,只要是根节点获取父节点展开状态就添加返回

上面就是这个Helper的方法,有效的把用户数据转化为我们需要的数据集。

最后看下我们的注解类,作用就是起到标识的作用,用于反射。

TreeNodeId:

package com.test.tree.utils;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TreeNodeId
{
}
TreeNodePid:

package com.test.tree.utils;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TreeNodePid
{
}


TreeNodeLabel:

package com.test.tree.utils;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TreeNodeLabel
{
}


大功告成,以上是我对这个功能的总结,只是用于记录和分享,有什么宝贵的意见欢迎大家指正,下面有Demo源码。

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