Android实现树状多层可展开收起ListView

时间:2021-03-07 19:33:50

最近项目用到树状ListView,要可展开收起。Android自带的ExpandableListView不太适合扩展,看了网上一些实现,发现通用性不是很好,于是参考可取之处,自己写了一个比较通过的实现。效果如下:

Android实现树状多层可展开收起ListView Android实现树状多层可展开收起ListView

1、首先是数据模型。

public class Node<T extends Node> implements Comparable<Node> {
    /** 自己的id */
    public int id;
    /** 上一层id */
    public int pId;
    /** 层级 */
    public int level;
    /** 是否展开 */
    public boolean isExpand;
    /** 子节点 */
    public List<T> childNodes;

    public Node() {
    }

    public Node(int id, int pId, int level, boolean isExpand) {
        this.id = id;
        this.pId = pId;
        this.level = level;
        this.isExpand = isExpand;
    }

    @Override
    public int compareTo(@NonNull Node o) {
        return id - o.id;
    }
    
    public boolean hasChild() {
        return childNodes != null && !childNodes.isEmpty();
    }
    
    public void addChild(T node) {
        if (childNodes == null) {
            childNodes = new ArrayList<>();
        }
        childNodes.add(node);
    }
}
2、然后就是ListView的数据适配器了。

public abstract class TreeAdapter<T extends Node<T>> extends BaseAdapter {
    private List<T> totalNodes = new ArrayList<>();
    private List<T> showNodes = new ArrayList<>();
    private List<T> firstLevelNodes = new ArrayList<>();
    private SparseIntArray addedChildNodeIds = new SparseIntArray();
    private OnInnerItemClickListener<T> listener;
    private OnInnerItemLongClickListener<T> longListener;

    public interface OnInnerItemClickListener<T> {
        void onClick(T node);
    }
    
    public interface OnInnerItemLongClickListener<T> {
        void onLongClick(T node);
    }
    
    public TreeAdapter(List<T> nodes) {
        setNodes(nodes);        
    }
    
    public void setOnInnerItemClickListener(OnInnerItemClickListener<T> listener) {
        this.listener = listener;
    }
    
    public void setOnInnerItemLongClickListener(OnInnerItemLongClickListener<T> listener) {
        longListener = listener;
    }
    
    public void setNodes(List<T> nodes) {
        if (nodes != null) {
            totalNodes = nodes;
            //过滤出显示的节点
            init();
            super.notifyDataSetChanged();
        }
    }
    
    private void init() {
        showNodes.clear();
        initNodes();
        addedChildNodeIds.clear();
        showNodes.addAll(firstLevelNodes);
        filterShowAndSortNodes();
    }

    @Override
    public void notifyDataSetChanged() {
        init();
        super.notifyDataSetChanged();
    }

    private void initNodes() {
        firstLevelNodes.clear();
        //先循环一次,获取最小的level
        Integer level = null;
        for (T node : totalNodes) {
            if (level == null || level > node.level) {
                level = node.level;
            }
        }
        for (T node : totalNodes) {
            //过滤出最外层
            if (node.level == level) {
                firstLevelNodes.add(node);
            }
            //清空之前添加的
            if (node.hasChild()) {
                node.childNodes.clear();
            }
            //给节点添加子节点并排序
            for (T t : totalNodes) {
                if (node.id == t.id && node != t) {
                    throw new IllegalArgumentException("id cannot be duplicated");
                }
                if (node.id == t.pId && node.level != t.level) {
                    node.addChild(t);
                }
            }
            if (node.hasChild()) {
                Collections.sort(node.childNodes);
            }
        }    
        Collections.sort(firstLevelNodes);
    }
    
    private void filterShowAndSortNodes() {
        for (int i = 0; i < showNodes.size(); i++) {
            T node = showNodes.get(i);
            int value = addedChildNodeIds.get(node.id);
            if (value == 0 && node.isExpand && node.hasChild()) {
                List<T> list = new ArrayList<>(showNodes);
                list.addAll(i + 1, node.childNodes);
                showNodes = list;
                addedChildNodeIds.put(node.id, 1);
                filterShowAndSortNodes();
                break;
            }
        }
    }

    @Override
    public int getCount() {
        return showNodes.size();
    }

    @Override
    public T getItem(int position) {
        return showNodes.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        Holder<T> holder;
        if (convertView == null) {
            holder = getHolder(position);
        } else {
            holder = (Holder<T>) convertView.getTag();
        }
        T node = showNodes.get(position);
        holder.setData(node);
        holder.position = position;
        View view = holder.getConvertView();
        view.setOnClickListener(clickListener);
        if (!node.hasChild()) {
            view.setOnLongClickListener(longClickListener);
        }
        return view;
    }
    
    public abstract static class Holder<T> {
        private View convertView;
        int position;

        public Holder() {
            convertView = createConvertView();
            convertView.setTag(this);
        }

        public View getConvertView() {
            return convertView;
        }
        
        /**
         * 设置数据
         */
        protected abstract void setData(T node);

        /**
         * 创建界面
         */
        protected abstract View createConvertView();
    }

    protected abstract Holder<T> getHolder(int position);
    
    private View.OnClickListener clickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Holder<T> holder = (Holder<T>) v.getTag();
            T node = showNodes.get(holder.position);
            if (node.hasChild()) {
                node.isExpand = !node.isExpand;
                if (!node.isExpand) {
                    fold(node.childNodes);
                }
                showNodes.clear();
                addedChildNodeIds.clear();
                showNodes.addAll(firstLevelNodes);
                filterShowAndSortNodes();
                TreeAdapter.super.notifyDataSetChanged();
            } else if (listener != null) {
                listener.onClick(node);
            }                
        }
    };
    
    private View.OnLongClickListener longClickListener = new View.OnLongClickListener() {
        @Override
        public boolean onLongClick(View v) {
            if (longListener != null) {
                Holder<T> holder = (Holder<T>) v.getTag();
                longListener.onLongClick(showNodes.get(holder.position));
            }
            return true;
        }
    };
    
    //递归收起节点及子节点
    private void fold(List<T> list) {
        for (T t : list) {
            t.isExpand = false;
            if (t.hasChild()) {
                fold(t.childNodes);
            }
        }
    }
}
3、以上两个文件直接写成成库,然后就可以使用了。

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ListView lv = new ListView(this);
        setContentView(lv, new ViewGroup.LayoutParams(-1, -1));
        final List<Item> list = new ArrayList<>();
        list.add(new Item(0, 0, 0, false, "Android"));
        list.add(new Item(1, 0, 1, false, "Service"));
        list.add(new Item(2, 0, 1, false, "Activity"));
        list.add(new Item(3, 0, 1, false, "Receiver"));
        list.add(new Item(4, 0, 0, true, "Java Web"));
        list.add(new Item(5, 4, 1, false, "CSS"));
        list.add(new Item(6, 4, 1, false, "Jsp"));
        list.add(new Item(7, 4, 1, true, "Html"));
        list.add(new Item(8, 7, 2, false, "p"));
        final MyAdapter adapter = new MyAdapter(list);
        adapter.setOnInnerItemClickListener(new TreeAdapter.OnInnerItemClickListener<Item>() {
            @Override
            public void onClick(Item node) {
                Toast.makeText(MainActivity.this, "click: " + node.name, Toast.LENGTH_SHORT).show();
            }
        });
        adapter.setOnInnerItemLongClickListener(new TreeAdapter.OnInnerItemLongClickListener<Item>() {
            @Override
            public void onLongClick(Item node) {
                Toast.makeText(MainActivity.this, "long click: " + node.name, Toast.LENGTH_SHORT).show();
            }
        });
        lv.setAdapter(adapter);
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                list.add(new Item(9, 7, 2, false, "a"));
                adapter.notifyDataSetChanged();
            }
        }, 2000);
    }

    private class MyAdapter extends TreeAdapter<Item> {
        MyAdapter(List<Item> nodes) {
            super(nodes);
        }

        @Override
        public int getViewTypeCount() {
            return super.getViewTypeCount() + 1;
        }

        /**
         * 获取当前位置的条目类型
         */
        @Override
        public int getItemViewType(int position) {
            if (getItem(position).hasChild()) {
                return 1;
            }
            return 0;
        }
        
        @Override
        protected Holder<Item> getHolder(int position) {
            switch(getItemViewType(position)) {
                case 1:
                    return new Holder<Item>() {
                        private ImageView iv;
                        private TextView tv;

                        @Override
                        protected void setData(Item node) {
                            iv.setVisibility(node.hasChild() ? View.VISIBLE : View.INVISIBLE);
                            iv.setBackgroundResource(node.isExpand ? R.mipmap.expand : R.mipmap.fold);
                            RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) iv.getLayoutParams();
                            params.leftMargin = (node.level + 1) * dip2px(20);
                            iv.setLayoutParams(params);
                            tv.setText(node.name);
                        }

                        @Override
                        protected View createConvertView() {
                            View view = View.inflate(MainActivity.this, R.layout.item_tree_list_has_child, null);
                            iv = (ImageView) view.findViewById(R.id.ivIcon);
                            tv = (TextView) view.findViewById(R.id.tvName);
                            return view;
                        }
                    };
                default:
                    return new Holder<Item>() {
                        private TextView tv;
                        
                        @Override
                        protected void setData(Item node) {
                            tv.setText(node.name);
                            RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) tv.getLayoutParams();
                            params.leftMargin = (node.level + 3) * dip2px(20);
                            tv.setLayoutParams(params);
                        }

                        @Override
                        protected View createConvertView() {
                            View view = View.inflate(MainActivity.this, R.layout.item_tree_list_no_child, null);
                            tv = (TextView) view.findViewById(R.id.tvName);
                            return view;
                        }
                    };
            }
        }
    }

    /**
     * 根据手机的分辨率从 dip 的单位 转成为 px(像素) 
     */
    public int dip2px(float dpValue) {
        final float scale = getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }
    
    private class Item extends Node<Item> {
        String name;

        Item(int id, int pId, int level, boolean isExpand, String name) {
            super(id, pId, level, isExpand);
            this.name = name;
        }
    }
}

源码: https://github.com/fszeng2011/treeadapter