Welcome

首页 / 移动开发 / Android / Android多级树形列表控件

我们开发app过程中,经常会碰到需要 多级列表展示的效果。而Android原生sdk中根本没有3级 4级甚至更多级别的列表控件。
所以我们就要自己去实现一个类似treeListView 的控件,下面这个是我项目中的一个效果图,可支持多级列表扩展。

  
android中有ExpandListView控件,但是这个控件只支持两级列表。对于多级列表如果重写这个不是很好用。
实现这种列表 思想就是递归,构造一个子父级的关系。
话不多说 代码中体会
Activity

package com.example.customtreeviewdemo;import java.util.ArrayList; import java.util.List;import android.app.Activity; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.ListView; import android.widget.Toast;import com.example.customtreeviewdemo.bean.MyNodeBean; import com.example.customtreeviewdemo.tree.Node; import com.example.customtreeviewdemo.tree.TreeListViewAdapter.OnTreeNodeClickListener;public class MainActivity extends Activity {private ListView treeLv; private Button checkSwitchBtn; private MyTreeListViewAdapter<MyNodeBean> adapter; private List<MyNodeBean> mDatas = new ArrayList<MyNodeBean>(); //标记是显示Checkbox还是隐藏 private boolean isHide = true;@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);initDatas(); treeLv = (ListView) this.findViewById(R.id.tree_lv); checkSwitchBtn = (Button)this.findViewById(R.id.check_switch_btn);checkSwitchBtn.setOnClickListener(new OnClickListener(){@Override public void onClick(View v) { if(isHide){ isHide = false; }else{ isHide = true; }adapter.updateView(isHide); }}); try { adapter = new MyTreeListViewAdapter<MyNodeBean>(treeLv, this, mDatas, 10, isHide);adapter.setOnTreeNodeClickListener(new OnTreeNodeClickListener() { @Override public void onClick(Node node, int position) { if (node.isLeaf()) { Toast.makeText(getApplicationContext(), node.getName(), Toast.LENGTH_SHORT).show(); } }@Override public void onCheckChange(Node node, int position, List<Node> checkedNodes) {StringBuffer sb = new StringBuffer(); for (Node n : checkedNodes) { int pos = n.getId() - 1; sb.append(mDatas.get(pos).getName()).append("---") .append(pos + 1).append(";");}Toast.makeText(getApplicationContext(), sb.toString(), Toast.LENGTH_SHORT).show(); }}); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } treeLv.setAdapter(adapter);}private void initDatas() { mDatas.add(new MyNodeBean(1, 0, "中国古代")); mDatas.add(new MyNodeBean(2, 1, "唐朝")); mDatas.add(new MyNodeBean(3, 1, "宋朝")); mDatas.add(new MyNodeBean(4, 1, "明朝")); mDatas.add(new MyNodeBean(5, 2, "李世民")); mDatas.add(new MyNodeBean(6, 2, "李白"));mDatas.add(new MyNodeBean(7, 3, "赵匡胤")); mDatas.add(new MyNodeBean(8, 3, "苏轼"));mDatas.add(new MyNodeBean(9, 4, "朱元璋")); mDatas.add(new MyNodeBean(10, 4, "唐伯虎")); mDatas.add(new MyNodeBean(11, 4, "文征明")); mDatas.add(new MyNodeBean(12, 7, "赵建立")); mDatas.add(new MyNodeBean(13, 8, "苏东东")); mDatas.add(new MyNodeBean(14, 10, "秋香")); } } 
Adapter
这个adapter是继承了自己的定义的一个TreeListViewAdapter,核心实现都是在TreeListViewAdapter这个里面

package com.example.customtreeviewdemo;import java.util.List;import android.content.Context; import android.view.View; import android.view.ViewGroup; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView;import com.example.customtreeviewdemo.tree.Node; import com.example.customtreeviewdemo.tree.TreeListViewAdapter;public class MyTreeListViewAdapter<T> extends TreeListViewAdapter<T> {public MyTreeListViewAdapter(ListView mTree, Context context, List<T> datas, int defaultExpandLevel,boolean isHide) throws IllegalArgumentException, IllegalAccessException { super(mTree, context, datas, defaultExpandLevel,isHide); }@SuppressWarnings("unchecked") @Override public View getConvertView(Node node, int position, View convertView, ViewGroup parent) { ViewHolder viewHolder = null; if (convertView == null) { convertView = mInflater.inflate(R.layout.list_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_name); viewHolder.checkBox = (CheckBox)convertView.findViewById(R.id.id_treeNode_check);convertView.setTag(viewHolder);} else { viewHolder = (ViewHolder) convertView.getTag(); }if (node.getIcon() == -1) { viewHolder.icon.setVisibility(View.INVISIBLE); } else { viewHolder.icon.setVisibility(View.VISIBLE); viewHolder.icon.setImageResource(node.getIcon()); }if(node.isHideChecked()){ viewHolder.checkBox.setVisibility(View.GONE); }else{ viewHolder.checkBox.setVisibility(View.VISIBLE); setCheckBoxBg(viewHolder.checkBox,node.isChecked()); } viewHolder.label.setText(node.getName()); return convertView; } private final class ViewHolder { ImageView icon; TextView label; CheckBox checkBox; }/*** checkbox是否显示* @param cb* @param isChecked*/ private void setCheckBoxBg(CheckBox cb,boolean isChecked){ if(isChecked){ cb.setBackgroundResource(R.drawable.check_box_bg_check); }else{ cb.setBackgroundResource(R.drawable.check_box_bg); } } } 
自定义TreeListViewAdapter  这个是整个树形结构的一个适配器,这里面主要是实现对Node节点的操作 点击,选中改变 更新等

package com.example.customtreeviewdemo.tree;import java.util.ArrayList; import java.util.List;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.CheckBox; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.ListView; import android.widget.RelativeLayout;/*** tree适配器* @param <T>*/ public abstract class TreeListViewAdapter<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 { /*** 处理node click事件* @param node* @param position*/ void onClick(Node node, int position); /*** 处理checkbox选择改变事件* @param node* @param position* @param checkedNodes*/ void onCheckChange(Node node, int position,List<Node> checkedNodes); }public void setOnTreeNodeClickListener( OnTreeNodeClickListener onTreeNodeClickListener) { this.onTreeNodeClickListener = onTreeNodeClickListener; }/**** @param mTree* @param context* @param datas* @param defaultExpandLevel*默认展开几级树* @throws IllegalArgumentException* @throws IllegalAccessException*/ public TreeListViewAdapter(ListView mTree, Context context, List<T> datas, int defaultExpandLevel, boolean isHide) throws IllegalArgumentException, IllegalAccessException { mContext = context; /*** 对所有的Node进行排序*/ mAllNodes = TreeHelper .getSortedNodes(datas, defaultExpandLevel, isHide); /*** 过滤出可见的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(final int position, View convertView, ViewGroup parent) { final Node node = mNodes.get(position);convertView = getConvertView(node, position, convertView, parent); // 设置内边距 convertView.setPadding(node.getLevel() * 30, 3, 3, 3); if (!node.isHideChecked()) { //获取各个节点所在的父布局 RelativeLayout myView = (RelativeLayout) convertView; //父布局下的CheckBox CheckBox cb = (CheckBox) myView.getChildAt(1); cb.setOnCheckedChangeListener(new OnCheckedChangeListener(){@Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { TreeHelper.setNodeChecked(node, isChecked); List<Node> checkedNodes = new ArrayList<Node>(); for(Node n:mAllNodes){ if(n.isChecked()){ checkedNodes.add(n); } }onTreeNodeClickListener.onCheckChange(node,position,checkedNodes); TreeListViewAdapter.this.notifyDataSetChanged(); }}); }return convertView; }public abstract View getConvertView(Node node, int position, View convertView, ViewGroup parent);/*** 更新* @param isHide*/ public void updateView(boolean isHide){ for(Node node:mAllNodes){ node.setHideChecked(isHide); }this.notifyDataSetChanged(); }} 
node 模型类

package com.example.customtreeviewdemo.bean;public class MyNodeBean { /*** 节点Id*/ private int id; /*** 节点父id*/ private int pId; /*** 节点name*/ private String name; /****/ private String desc; /*** 节点名字长度*/ private long length; public MyNodeBean(int id, int pId, String name) { super(); 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 String getName() { return name; } public void setName(String name) { this.name = name; } public String getDesc() { return desc; } public void setDesc(String desc) { this.desc = desc; } public long getLength() { return length; } public void setLength(long length) { this.length = length; } } 
TreeHelper这个也是核心操作类,主要功能是将业务数据和节点数据进行匹配
package com.example.customtreeviewdemo.tree;import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List;import com.example.customtreeviewdemo.R;public class TreeHelper {/*** 根据所有节点获取可见节点** @param allNodes* @return*/ public static List<Node> filterVisibleNode(List<Node> allNodes) { List<Node> visibleNodes = new ArrayList<Node>(); for (Node node : allNodes) { // 如果为根节点,或者上层目录为展开状态 if (node.isRoot() || node.isParentExpand()) { setNodeIcon(node); visibleNodes.add(node); } } return visibleNodes; }/*** 获取排序的所有节点** @param datas* @param defaultExpandLevel* @return* @throws IllegalArgumentException* @throws IllegalAccessException*/ public static <T> List<Node> getSortedNodes(List<T> datas, int defaultExpandLevel, boolean isHide) throws IllegalAccessException, IllegalArgumentException { List<Node> sortedNodes = new ArrayList<Node>(); // 将用户数据转化为List<Node> List<Node> nodes = convertData2Nodes(datas, isHide); // 拿到根节点 List<Node> rootNodes = getRootNodes(nodes); // 排序以及设置Node间关系 for (Node node : rootNodes) { addNode(sortedNodes, node, defaultExpandLevel, 1); } return sortedNodes; }/*** 把一个节点上的所有的内容都挂上去*/ 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.getChildrenNodes().size(); i++) { addNode(nodes, node.getChildrenNodes().get(i), defaultExpandLeval, currentLevel + 1); } }/*** 获取所有的根节点** @param nodes* @return*/ public static List<Node> getRootNodes(List<Node> nodes) { List<Node> rootNodes = new ArrayList<Node>(); for (Node node : nodes) { if (node.isRoot()) { rootNodes.add(node); } }return rootNodes; }/*** 将泛型datas转换为node** @param datas* @return* @throws IllegalArgumentException* @throws IllegalAccessException*/ public static <T> List<Node> convertData2Nodes(List<T> datas, boolean isHide) throws IllegalAccessException, IllegalArgumentException { List<Node> nodes = new ArrayList<Node>(); Node node = null;for (T t : datas) { int id = -1; int pId = -1; String name = null;Class<? extends Object> clazz = t.getClass(); Field[] declaredFields = clazz.getDeclaredFields(); /*** 与MyNodeBean实体一一对应*/ for (Field f : declaredFields) { if ("id".equals(f.getName())) { f.setAccessible(true); id = f.getInt(t); }if ("pId".equals(f.getName())) { f.setAccessible(true); pId = f.getInt(t); }if ("name".equals(f.getName())) { f.setAccessible(true); name = (String) f.get(t); }if ("desc".equals(f.getName())) { continue; }if ("length".equals(f.getName())) { continue; }if (id == -1 && pId == -1 && name == null) { break; } }node = new Node(id, pId, name); node.setHideChecked(isHide); nodes.add(node); }/*** 比较nodes中的所有节点,分别添加children和parent*/ 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.getId() == m.getpId()) { n.getChildrenNodes().add(m); m.setParent(n); } else if (n.getpId() == m.getId()) { n.setParent(m); m.getChildrenNodes().add(n); } } }for (Node n : nodes) { setNodeIcon(n); } return nodes; }/*** 设置打开,关闭icon** @param node*/ public static void setNodeIcon(Node node) { if (node.getChildrenNodes().size() > 0 && node.isExpand()) { node.setIcon(R.drawable.tree_expand); } else if (node.getChildrenNodes().size() > 0 && !node.isExpand()) { node.setIcon(R.drawable.tree_econpand); } else node.setIcon(-1); }public static void setNodeChecked(Node node, boolean isChecked) { // 自己设置是否选择 node.setChecked(isChecked); /*** 非叶子节点,子节点处理*/ setChildrenNodeChecked(node, isChecked); /** 父节点处理 */ setParentNodeChecked(node); }/*** 非叶子节点,子节点处理*/ private static void setChildrenNodeChecked(Node node, boolean isChecked) { node.setChecked(isChecked); if (!node.isLeaf()) { for (Node n : node.getChildrenNodes()) { // 所有子节点设置是否选择 setChildrenNodeChecked(n, isChecked); } } }/*** 设置父节点选择** @param node*/ private static void setParentNodeChecked(Node node) {/** 非根节点 */ if (!node.isRoot()) { Node rootNode = node.getParent(); boolean isAllChecked = true; for (Node n : rootNode.getChildrenNodes()) { if (!n.isChecked()) { isAllChecked = false; break; } }if (isAllChecked) { rootNode.setChecked(true); } else { rootNode.setChecked(false); } setParentNodeChecked(rootNode); } }} 
核心的代码就是这些,希望对大家有帮助。
DEMO源码:http://xiazai.jb51.net/201611/yuanma/AndroidTreeView(jb51.net).rar
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。