android多级树形列表

举报
再见孙悟空_ 发表于 2022/01/13 00:09:21 2022/01/13
【摘要】 我们开发app过程中,经常会碰到需要 多级列表展示的效果。而android原生sdk中根本没有3级 4级甚至更多级别的列表控件。 所以我们就要自己去实现一个类似treeListView 的控件,下面这个是我项目中的一个效果图,可支持多级列表扩展。            ...

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

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

           

android中有ExpandListView控件,但是这个控件只支持两级列表。对于多级列表如果重写这个不是很好用。

实现这种列表 思想就是递归,构造一个子父级的关系。

话不多说 代码中体会

Activity


  
  1. package com.example.customtreeviewdemo;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. import android.app.Activity;
  5. import android.os.Bundle;
  6. import android.view.View;
  7. import android.view.View.OnClickListener;
  8. import android.widget.Button;
  9. import android.widget.ListView;
  10. import android.widget.Toast;
  11. import com.example.customtreeviewdemo.bean.MyNodeBean;
  12. import com.example.customtreeviewdemo.tree.Node;
  13. import com.example.customtreeviewdemo.tree.TreeListViewAdapter.OnTreeNodeClickListener;
  14. public class MainActivity extends Activity {
  15. private ListView treeLv;
  16. private Button checkSwitchBtn;
  17. private MyTreeListViewAdapter<MyNodeBean> adapter;
  18. private List<MyNodeBean> mDatas = new ArrayList<MyNodeBean>();
  19. //标记是显示Checkbox还是隐藏
  20. private boolean isHide = true;
  21. @Override
  22. protected void onCreate(Bundle savedInstanceState) {
  23. super.onCreate(savedInstanceState);
  24. setContentView(R.layout.activity_main);
  25. initDatas();
  26. treeLv = (ListView) this.findViewById(R.id.tree_lv);
  27. checkSwitchBtn = (Button)this.findViewById(R.id.check_switch_btn);
  28. checkSwitchBtn.setOnClickListener(new OnClickListener(){
  29. @Override
  30. public void onClick(View v) {
  31. if(isHide){
  32. isHide = false;
  33. }else{
  34. isHide = true;
  35. }
  36. adapter.updateView(isHide);
  37. }
  38. });
  39. try {
  40. adapter = new MyTreeListViewAdapter<MyNodeBean>(treeLv, this,
  41. mDatas, 10, isHide);
  42. adapter.setOnTreeNodeClickListener(new OnTreeNodeClickListener() {
  43. @Override
  44. public void onClick(Node node, int position) {
  45. if (node.isLeaf()) {
  46. Toast.makeText(getApplicationContext(), node.getName(),
  47. Toast.LENGTH_SHORT).show();
  48. }
  49. }
  50. @Override
  51. public void onCheckChange(Node node, int position,
  52. List<Node> checkedNodes) {
  53. StringBuffer sb = new StringBuffer();
  54. for (Node n : checkedNodes) {
  55. int pos = n.getId() - 1;
  56. sb.append(mDatas.get(pos).getName()).append("---")
  57. .append(pos + 1).append(";");
  58. }
  59. Toast.makeText(getApplicationContext(), sb.toString(),
  60. Toast.LENGTH_SHORT).show();
  61. }
  62. });
  63. } catch (IllegalArgumentException e) {
  64. e.printStackTrace();
  65. } catch (IllegalAccessException e) {
  66. e.printStackTrace();
  67. }
  68. treeLv.setAdapter(adapter);
  69. }
  70. private void initDatas() {
  71. mDatas.add(new MyNodeBean(1, 0, "中国古代"));
  72. mDatas.add(new MyNodeBean(2, 1, "唐朝"));
  73. mDatas.add(new MyNodeBean(3, 1, "宋朝"));
  74. mDatas.add(new MyNodeBean(4, 1, "明朝"));
  75. mDatas.add(new MyNodeBean(5, 2, "李世民"));
  76. mDatas.add(new MyNodeBean(6, 2, "李白"));
  77. mDatas.add(new MyNodeBean(7, 3, "赵匡胤"));
  78. mDatas.add(new MyNodeBean(8, 3, "苏轼"));
  79. mDatas.add(new MyNodeBean(9, 4, "朱元璋"));
  80. mDatas.add(new MyNodeBean(10, 4, "唐伯虎"));
  81. mDatas.add(new MyNodeBean(11, 4, "文征明"));
  82. mDatas.add(new MyNodeBean(12, 7, "赵建立"));
  83. mDatas.add(new MyNodeBean(13, 8, "苏东东"));
  84. mDatas.add(new MyNodeBean(14, 10, "秋香"));
  85. }
  86. }

Adapter

这个adapter是继承了自己的定义的一个TreeListViewAdapter,核心实现都是在TreeListViewAdapter这个里面


  
  1. package com.example.customtreeviewdemo;
  2. import java.util.List;
  3. import android.content.Context;
  4. import android.view.View;
  5. import android.view.ViewGroup;
  6. import android.widget.CheckBox;
  7. import android.widget.CompoundButton;
  8. import android.widget.CompoundButton.OnCheckedChangeListener;
  9. import android.widget.ImageView;
  10. import android.widget.ListView;
  11. import android.widget.TextView;
  12. import com.example.customtreeviewdemo.tree.Node;
  13. import com.example.customtreeviewdemo.tree.TreeListViewAdapter;
  14. public class MyTreeListViewAdapter<T> extends TreeListViewAdapter<T> {
  15. public MyTreeListViewAdapter(ListView mTree, Context context,
  16. List<T> datas, int defaultExpandLevel,boolean isHide)
  17. throws IllegalArgumentException, IllegalAccessException {
  18. super(mTree, context, datas, defaultExpandLevel,isHide);
  19. }
  20. @SuppressWarnings("unchecked")
  21. @Override
  22. public View getConvertView(Node node, int position, View convertView,
  23. ViewGroup parent) {
  24. ViewHolder viewHolder = null;
  25. if (convertView == null)
  26. {
  27. convertView = mInflater.inflate(R.layout.list_item, parent, false);
  28. viewHolder = new ViewHolder();
  29. viewHolder.icon = (ImageView) convertView
  30. .findViewById(R.id.id_treenode_icon);
  31. viewHolder.label = (TextView) convertView
  32. .findViewById(R.id.id_treenode_name);
  33. viewHolder.checkBox = (CheckBox)convertView.findViewById(R.id.id_treeNode_check);
  34. convertView.setTag(viewHolder);
  35. } else
  36. {
  37. viewHolder = (ViewHolder) convertView.getTag();
  38. }
  39. if (node.getIcon() == -1)
  40. {
  41. viewHolder.icon.setVisibility(View.INVISIBLE);
  42. } else
  43. {
  44. viewHolder.icon.setVisibility(View.VISIBLE);
  45. viewHolder.icon.setImageResource(node.getIcon());
  46. }
  47. if(node.isHideChecked()){
  48. viewHolder.checkBox.setVisibility(View.GONE);
  49. }else{
  50. viewHolder.checkBox.setVisibility(View.VISIBLE);
  51. setCheckBoxBg(viewHolder.checkBox,node.isChecked());
  52. }
  53. viewHolder.label.setText(node.getName());
  54. return convertView;
  55. }
  56. private final class ViewHolder
  57. {
  58. ImageView icon;
  59. TextView label;
  60. CheckBox checkBox;
  61. }
  62. /**
  63. * checkbox是否显示
  64. * @param cb
  65. * @param isChecked
  66. */
  67. private void setCheckBoxBg(CheckBox cb,boolean isChecked){
  68. if(isChecked){
  69. cb.setBackgroundResource(R.drawable.check_box_bg_check);
  70. }else{
  71. cb.setBackgroundResource(R.drawable.check_box_bg);
  72. }
  73. }
  74. }

自定义TreeListViewAdapter  这个是整个树形结构的一个适配器,这里面主要是实现对Node节点的操作 点击,选中改变 更新等


  
  1. package com.example.customtreeviewdemo.tree;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. import android.content.Context;
  5. import android.view.LayoutInflater;
  6. import android.view.View;
  7. import android.view.ViewGroup;
  8. import android.widget.AdapterView;
  9. import android.widget.AdapterView.OnItemClickListener;
  10. import android.widget.BaseAdapter;
  11. import android.widget.CheckBox;
  12. import android.widget.CompoundButton;
  13. import android.widget.CompoundButton.OnCheckedChangeListener;
  14. import android.widget.ListView;
  15. import android.widget.RelativeLayout;
  16. /**
  17. * tree适配器
  18. * @param <T>
  19. */
  20. public abstract class TreeListViewAdapter<T> extends BaseAdapter {
  21. protected Context mContext;
  22. /**
  23. * 存储所有可见的Node
  24. */
  25. protected List<Node> mNodes;
  26. protected LayoutInflater mInflater;
  27. /**
  28. * 存储所有的Node
  29. */
  30. protected List<Node> mAllNodes;
  31. /**
  32. * 点击的回调接口
  33. */
  34. private OnTreeNodeClickListener onTreeNodeClickListener;
  35. public interface OnTreeNodeClickListener {
  36. /**
  37. * 处理node click事件
  38. * @param node
  39. * @param position
  40. */
  41. void onClick(Node node, int position);
  42. /**
  43. * 处理checkbox选择改变事件
  44. * @param node
  45. * @param position
  46. * @param checkedNodes
  47. */
  48. void onCheckChange(Node node, int position,List<Node> checkedNodes);
  49. }
  50. public void setOnTreeNodeClickListener(
  51. OnTreeNodeClickListener onTreeNodeClickListener) {
  52. this.onTreeNodeClickListener = onTreeNodeClickListener;
  53. }
  54. /**
  55. *
  56. * @param mTree
  57. * @param context
  58. * @param datas
  59. * @param defaultExpandLevel
  60. * 默认展开几级树
  61. * @throws IllegalArgumentException
  62. * @throws IllegalAccessException
  63. */
  64. public TreeListViewAdapter(ListView mTree, Context context, List<T> datas,
  65. int defaultExpandLevel, boolean isHide)
  66. throws IllegalArgumentException, IllegalAccessException {
  67. mContext = context;
  68. /**
  69. * 对所有的Node进行排序
  70. */
  71. mAllNodes = TreeHelper
  72. .getSortedNodes(datas, defaultExpandLevel, isHide);
  73. /**
  74. * 过滤出可见的Node
  75. */
  76. mNodes = TreeHelper.filterVisibleNode(mAllNodes);
  77. mInflater = LayoutInflater.from(context);
  78. /**
  79. * 设置节点点击时,可以展开以及关闭;并且将ItemClick事件继续往外公布
  80. */
  81. mTree.setOnItemClickListener(new OnItemClickListener() {
  82. @Override
  83. public void onItemClick(AdapterView<?> parent, View view,
  84. int position, long id) {
  85. expandOrCollapse(position);
  86. if (onTreeNodeClickListener != null) {
  87. onTreeNodeClickListener.onClick(mNodes.get(position),
  88. position);
  89. }
  90. }
  91. });
  92. }
  93. /**
  94. * 相应ListView的点击事件 展开或关闭某节点
  95. *
  96. * @param position
  97. */
  98. public void expandOrCollapse(int position) {
  99. Node n = mNodes.get(position);
  100. if (n != null)// 排除传入参数错误异常
  101. {
  102. if (!n.isLeaf()) {
  103. n.setExpand(!n.isExpand());
  104. mNodes = TreeHelper.filterVisibleNode(mAllNodes);
  105. notifyDataSetChanged();// 刷新视图
  106. }
  107. }
  108. }
  109. @Override
  110. public int getCount() {
  111. return mNodes.size();
  112. }
  113. @Override
  114. public Object getItem(int position) {
  115. return mNodes.get(position);
  116. }
  117. @Override
  118. public long getItemId(int position) {
  119. return position;
  120. }
  121. @Override
  122. public View getView(final int position, View convertView, ViewGroup parent) {
  123. final Node node = mNodes.get(position);
  124. convertView = getConvertView(node, position, convertView, parent);
  125. // 设置内边距
  126. convertView.setPadding(node.getLevel() * 30, 3, 3, 3);
  127. if (!node.isHideChecked()) {
  128. //获取各个节点所在的父布局
  129. RelativeLayout myView = (RelativeLayout) convertView;
  130. //父布局下的CheckBox
  131. CheckBox cb = (CheckBox) myView.getChildAt(1);
  132. cb.setOnCheckedChangeListener(new OnCheckedChangeListener(){
  133. @Override
  134. public void onCheckedChanged(CompoundButton buttonView,
  135. boolean isChecked) {
  136. TreeHelper.setNodeChecked(node, isChecked);
  137. List<Node> checkedNodes = new ArrayList<Node>();
  138. for(Node n:mAllNodes){
  139. if(n.isChecked()){
  140. checkedNodes.add(n);
  141. }
  142. }
  143. onTreeNodeClickListener.onCheckChange(node,position,checkedNodes);
  144. TreeListViewAdapter.this.notifyDataSetChanged();
  145. }
  146. });
  147. }
  148. return convertView;
  149. }
  150. public abstract View getConvertView(Node node, int position,
  151. View convertView, ViewGroup parent);
  152. /**
  153. * 更新
  154. * @param isHide
  155. */
  156. public void updateView(boolean isHide){
  157. for(Node node:mAllNodes){
  158. node.setHideChecked(isHide);
  159. }
  160. this.notifyDataSetChanged();
  161. }
  162. }

node 模型类


  
  1. package com.example.customtreeviewdemo.bean;
  2. public class MyNodeBean {
  3. /**
  4. * 节点Id
  5. */
  6. private int id;
  7. /**
  8. * 节点父id
  9. */
  10. private int pId;
  11. /**
  12. * 节点name
  13. */
  14. private String name;
  15. /**
  16. *
  17. */
  18. private String desc;
  19. /**
  20. * 节点名字长度
  21. */
  22. private long length;
  23. public MyNodeBean(int id, int pId, String name) {
  24. super();
  25. this.id = id;
  26. this.pId = pId;
  27. this.name = name;
  28. }
  29. public int getId() {
  30. return id;
  31. }
  32. public void setId(int id) {
  33. this.id = id;
  34. }
  35. public int getPid() {
  36. return pId;
  37. }
  38. public void setPid(int pId) {
  39. this.pId = pId;
  40. }
  41. public String getName() {
  42. return name;
  43. }
  44. public void setName(String name) {
  45. this.name = name;
  46. }
  47. public String getDesc() {
  48. return desc;
  49. }
  50. public void setDesc(String desc) {
  51. this.desc = desc;
  52. }
  53. public long getLength() {
  54. return length;
  55. }
  56. public void setLength(long length) {
  57. this.length = length;
  58. }
  59. }

TreeHelper这个也是核心操作类,主要功能是将业务数据和节点数据进行匹配


  
  1. package com.example.customtreeviewdemo.tree;
  2. import java.lang.reflect.Field;
  3. import java.util.ArrayList;
  4. import java.util.List;
  5. import com.example.customtreeviewdemo.R;
  6. public class TreeHelper {
  7. /**
  8. * 根据所有节点获取可见节点
  9. *
  10. * @param allNodes
  11. * @return
  12. */
  13. public static List<Node> filterVisibleNode(List<Node> allNodes) {
  14. List<Node> visibleNodes = new ArrayList<Node>();
  15. for (Node node : allNodes) {
  16. // 如果为根节点,或者上层目录为展开状态
  17. if (node.isRoot() || node.isParentExpand()) {
  18. setNodeIcon(node);
  19. visibleNodes.add(node);
  20. }
  21. }
  22. return visibleNodes;
  23. }
  24. /**
  25. * 获取排序的所有节点
  26. *
  27. * @param datas
  28. * @param defaultExpandLevel
  29. * @return
  30. * @throws IllegalArgumentException
  31. * @throws IllegalAccessException
  32. */
  33. public static <T> List<Node> getSortedNodes(List<T> datas,
  34. int defaultExpandLevel, boolean isHide)
  35. throws IllegalAccessException, IllegalArgumentException {
  36. List<Node> sortedNodes = new ArrayList<Node>();
  37. // 将用户数据转化为List<Node>
  38. List<Node> nodes = convertData2Nodes(datas, isHide);
  39. // 拿到根节点
  40. List<Node> rootNodes = getRootNodes(nodes);
  41. // 排序以及设置Node间关系
  42. for (Node node : rootNodes) {
  43. addNode(sortedNodes, node, defaultExpandLevel, 1);
  44. }
  45. return sortedNodes;
  46. }
  47. /**
  48. * 把一个节点上的所有的内容都挂上去
  49. */
  50. private static void addNode(List<Node> nodes, Node node,
  51. int defaultExpandLeval, int currentLevel) {
  52. nodes.add(node);
  53. if (defaultExpandLeval >= currentLevel) {
  54. node.setExpand(true);
  55. }
  56. if (node.isLeaf())
  57. return;
  58. for (int i = 0; i < node.getChildrenNodes().size(); i++) {
  59. addNode(nodes, node.getChildrenNodes().get(i), defaultExpandLeval,
  60. currentLevel + 1);
  61. }
  62. }
  63. /**
  64. * 获取所有的根节点
  65. *
  66. * @param nodes
  67. * @return
  68. */
  69. public static List<Node> getRootNodes(List<Node> nodes) {
  70. List<Node> rootNodes = new ArrayList<Node>();
  71. for (Node node : nodes) {
  72. if (node.isRoot()) {
  73. rootNodes.add(node);
  74. }
  75. }
  76. return rootNodes;
  77. }
  78. /**
  79. * 将泛型datas转换为node
  80. *
  81. * @param datas
  82. * @return
  83. * @throws IllegalArgumentException
  84. * @throws IllegalAccessException
  85. */
  86. public static <T> List<Node> convertData2Nodes(List<T> datas, boolean isHide)
  87. throws IllegalAccessException, IllegalArgumentException {
  88. List<Node> nodes = new ArrayList<Node>();
  89. Node node = null;
  90. for (T t : datas) {
  91. int id = -1;
  92. int pId = -1;
  93. String name = null;
  94. Class<? extends Object> clazz = t.getClass();
  95. Field[] declaredFields = clazz.getDeclaredFields();
  96. /**
  97. * 与MyNodeBean实体一一对应
  98. */
  99. for (Field f : declaredFields) {
  100. if ("id".equals(f.getName())) {
  101. f.setAccessible(true);
  102. id = f.getInt(t);
  103. }
  104. if ("pId".equals(f.getName())) {
  105. f.setAccessible(true);
  106. pId = f.getInt(t);
  107. }
  108. if ("name".equals(f.getName())) {
  109. f.setAccessible(true);
  110. name = (String) f.get(t);
  111. }
  112. if ("desc".equals(f.getName())) {
  113. continue;
  114. }
  115. if ("length".equals(f.getName())) {
  116. continue;
  117. }
  118. if (id == -1 && pId == -1 && name == null) {
  119. break;
  120. }
  121. }
  122. node = new Node(id, pId, name);
  123. node.setHideChecked(isHide);
  124. nodes.add(node);
  125. }
  126. /**
  127. * 比较nodes中的所有节点,分别添加children和parent
  128. */
  129. for (int i = 0; i < nodes.size(); i++) {
  130. Node n = nodes.get(i);
  131. for (int j = i + 1; j < nodes.size(); j++) {
  132. Node m = nodes.get(j);
  133. if (n.getId() == m.getpId()) {
  134. n.getChildrenNodes().add(m);
  135. m.setParent(n);
  136. } else if (n.getpId() == m.getId()) {
  137. n.setParent(m);
  138. m.getChildrenNodes().add(n);
  139. }
  140. }
  141. }
  142. for (Node n : nodes) {
  143. setNodeIcon(n);
  144. }
  145. return nodes;
  146. }
  147. /**
  148. * 设置打开,关闭icon
  149. *
  150. * @param node
  151. */
  152. public static void setNodeIcon(Node node) {
  153. if (node.getChildrenNodes().size() > 0 && node.isExpand()) {
  154. node.setIcon(R.drawable.tree_expand);
  155. } else if (node.getChildrenNodes().size() > 0 && !node.isExpand()) {
  156. node.setIcon(R.drawable.tree_econpand);
  157. } else
  158. node.setIcon(-1);
  159. }
  160. public static void setNodeChecked(Node node, boolean isChecked) {
  161. // 自己设置是否选择
  162. node.setChecked(isChecked);
  163. /**
  164. * 非叶子节点,子节点处理
  165. */
  166. setChildrenNodeChecked(node, isChecked);
  167. /** 父节点处理 */
  168. setParentNodeChecked(node);
  169. }
  170. /**
  171. * 非叶子节点,子节点处理
  172. */
  173. private static void setChildrenNodeChecked(Node node, boolean isChecked) {
  174. node.setChecked(isChecked);
  175. if (!node.isLeaf()) {
  176. for (Node n : node.getChildrenNodes()) {
  177. // 所有子节点设置是否选择
  178. setChildrenNodeChecked(n, isChecked);
  179. }
  180. }
  181. }
  182. /**
  183. * 设置父节点选择
  184. *
  185. * @param node
  186. */
  187. private static void setParentNodeChecked(Node node) {
  188. /** 非根节点 */
  189. if (!node.isRoot()) {
  190. Node rootNode = node.getParent();
  191. boolean isAllChecked = true;
  192. for (Node n : rootNode.getChildrenNodes()) {
  193. if (!n.isChecked()) {
  194. isAllChecked = false;
  195. break;
  196. }
  197. }
  198. if (isAllChecked) {
  199. rootNode.setChecked(true);
  200. } else {
  201. rootNode.setChecked(false);
  202. }
  203. setParentNodeChecked(rootNode);
  204. }
  205. }
  206. }

核心的代码就是这些,希望对大家有帮助。

DEMO源码

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

文章来源: wukong.blog.csdn.net,作者:再见孙悟空_,版权归原作者所有,如需转载,请联系作者。

原文链接:wukong.blog.csdn.net/article/details/52275130

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。