我们开发app过程中,经常会碰到需要 多级列表展示的效果。而android原生sdk中根本没有3级 4级甚至更多级别的列表控件。

所以我们就要自己去实现一个类似treeListView 的控件,下面这个是我项目中的一个效果图,可支持多级列表扩展。


android多级树形列表_第1张图片           android多级树形列表_第2张图片


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 adapter;private List mDatas = new ArrayList();//标记是显示Checkbox还是隐藏private boolean isHide = true;@Overrideprotected 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(){@Overridepublic void onClick(View v) {if(isHide){isHide = false;}else{isHide = true;}adapter.updateView(isHide);}});try {adapter = new MyTreeListViewAdapter(treeLv, this,mDatas, 10, isHide);adapter.setOnTreeNodeClickListener(new OnTreeNodeClickListener() {@Overridepublic void onClick(Node node, int position) {if (node.isLeaf()) {Toast.makeText(getApplicationContext(), node.getName(),Toast.LENGTH_SHORT).show();}}@Overridepublic void onCheckChange(Node node, int position,List 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 extends TreeListViewAdapter {public MyTreeListViewAdapter(ListView mTree, Context context,List datas, int defaultExpandLevel,boolean isHide)throws IllegalArgumentException, IllegalAccessException {super(mTree, context, datas, defaultExpandLevel,isHide);}@SuppressWarnings("unchecked")@Overridepublic 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  */public abstract class TreeListViewAdapter extends BaseAdapter {protected Context mContext;/** * 存储所有可见的Node */protected List mNodes;protected LayoutInflater mInflater;/** * 存储所有的Node */protected List 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 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 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() {@Overridepublic 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();// 刷新视图}}}@Overridepublic int getCount() {return mNodes.size();}@Overridepublic Object getItem(int position) {return mNodes.get(position);}@Overridepublic long getItemId(int position) {return position;}@Overridepublic 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;//父布局下的CheckBoxCheckBox cb = (CheckBox) myView.getChildAt(1);cb.setOnCheckedChangeListener(new OnCheckedChangeListener(){@Overridepublic void onCheckedChanged(CompoundButton buttonView,boolean isChecked) {TreeHelper.setNodeChecked(node, isChecked);List checkedNodes = new ArrayList();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 filterVisibleNode(List allNodes) {List visibleNodes = new ArrayList();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  List getSortedNodes(List datas,int defaultExpandLevel, boolean isHide)throws IllegalAccessException, IllegalArgumentException {List sortedNodes = new ArrayList();// 将用户数据转化为ListList nodes = convertData2Nodes(datas, isHide);// 拿到根节点List rootNodes = getRootNodes(nodes);// 排序以及设置Node间关系for (Node node : rootNodes) {addNode(sortedNodes, node, defaultExpandLevel, 1);}return sortedNodes;}/** * 把一个节点上的所有的内容都挂上去 */private static void addNode(List 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 getRootNodes(List nodes) {List rootNodes = new ArrayList();for (Node node : nodes) {if (node.isRoot()) {rootNodes.add(node);}}return rootNodes;}/** * 将泛型datas转换为node *  * @param datas * @return * @throws IllegalArgumentException * @throws IllegalAccessException */public static  List convertData2Nodes(List datas, boolean isHide)throws IllegalAccessException, IllegalArgumentException {List nodes = new ArrayList();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);} elsenode.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源码

大家如果有其它问题,可以加入我的qq群:Android开发经验交流群 454430053  互相讨论交流。


更多相关文章

  1. Android界面设计基础:控件焦点4个步骤
  2. android 开发零起步学习笔记(九):android 控制控件的位置和大小及L
  3. 一起写一个Android图片轮播控件
  4. Android中自定义控件的步骤
  5. Android 动态增加控件
  6. [Android] 拍照、截图、保存并显示在ImageView控件中
  7. Android拼图滑块验证码控件
  8. 反思|Android 列表分页组件Paging的设计与实现:系统概述

随机推荐

  1. Android常用的Gradle配置和加速编译
  2. android中实现tab标签切换时的一些效果
  3. Android(安卓)UI 之 ListView
  4. Android下动态链接库.so调用的简单例子
  5. Android实现友盟分享小程序
  6. Android--GestureOverlayView,手势识别
  7. NCalc支持Android和iOS
  8. android GPS
  9. Android Fragment 生命周期及回调方法
  10. Android中判断当前网络是否可用