前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >android多级树形列表

android多级树形列表

作者头像
再见孙悟空_
发布2023-02-10 19:33:43
2.2K0
发布2023-02-10 19:33:43
举报

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

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

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

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

话不多说 代码中体会

Activity

代码语言:javascript
复制
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这个里面

代码语言:javascript
复制
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节点的操作 点击,选中改变 更新等

代码语言:javascript
复制
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 模型类

代码语言:javascript
复制
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这个也是核心操作类,主要功能是将业务数据和节点数据进行匹配

代码语言:javascript
复制
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源码

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016-11-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档