Android 使用listview实现树形结构

来源:互联网 发布:三种网络分层结构 编辑:程序博客网 时间:2024/06/18 06:51

一、概述:

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

0 0
原创粉丝点击