Android 使用listview实现树形结构

时间:2023-03-25 08:09:57


一、概述:

1、效果图:

Android 使用listview实现树形结构

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 {

}