Android 实现自己定义多级树控件和全选与反选的效果
博文開始之前,首先要感谢大牛:(lmj623565791),本博文是在其博文http://blog.csdn.net/lmj623565791/article/details/40212367基础上进一步的改动而来。
本博文主要是利用ListView实现多级树控件,并通过CheckBox来对各节点的全选与反选的功能,首先来看一下效果:
对于多级树的显示事实上就是通过数据中各个节点的关系,通过不同的缩进来达到树的效果。而数据中主要要把握id,父节点pId。name的关系,来显示其效果。
代码实现例如以下:
一. 布局xml文件
1.主界面activity_main.xml,简单的ListView和一个控制CheckBox切换的Button
<span style="font-family:KaiTi_GB2312;font-size:18px;"><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/check_switch_btn" android:text="CheckBox切换" android:gravity="center" android:layout_marginBottom="10dp" /> <ListView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/tree_lv" /> </LinearLayout> </span>
2.树节点布局list_view.xml. 树节点关闭和打开的Image,旋转框CheckBox,内容TextView
<span style="font-family:KaiTi_GB2312;font-size:18px;"><?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:minHeight="40dip" > <ImageView android:id="@+id/id_treenode_icon" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginLeft="5dp" android:layout_marginRight="5dp" android:src="@drawable/tree_econpand" /> <CheckBox android:id="@+id/id_treeNode_check" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_toRightOf="@id/id_treenode_icon" android:layout_marginLeft="5dp" android:layout_marginRight="5dp" android:background="@drawable/check_box_bg" android:button="@null" android:focusable="false" /> <TextView android:id="@+id/id_treenode_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_toRightOf="@id/id_treeNode_check" android:text="@string/app_name" android:textSize="18sp" /> </RelativeLayout></span>
二.java代码实现
1.实体类MyNodeBean,内容能够依据详细情况增加
<span style="font-family:KaiTi_GB2312;font-size:18px;">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; } } </span>
2.主函数MainActivity.java
<span style="font-family:KaiTi_GB2312;font-size:18px;">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; } //切换checkbox 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) { // TODO Auto-generated method stub 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) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block 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, "秋香")); } } </span>
3.ListView实现适配器MyTreeListViewAdapter.java
该适配器继承TreeListViewAdapter<T>,在该适配器中仅仅实现方法
<span style="font-family:KaiTi_GB2312;font-size:18px;">getConvertView(Node node, int position, View convertView, ViewGroup parent)</span>其它。父类中载入
<span style="font-family:KaiTi_GB2312;font-size:18px;">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); } } } </span>
三、树节点实现的方法
1.树节点实体类Node.java
<span style="font-family:KaiTi_GB2312;font-size:18px;">package com.example.customtreeviewdemo.tree; import java.util.ArrayList; import java.util.List; public class Node { /** * 节点id */ private int id; /** * 父节点id */ private int pId; /** * 是否展开 */ private boolean isExpand = false; private boolean isChecked = false;//是否选中 private boolean isHideChecked = true;//CheckBox是否隐藏 /** * 节点名字 */ private String name; /** * 节点级别 */ private int level; /** * 节点展示图标 */ private int icon; /** * 节点所含的子节点 */ private List<Node> childrenNodes = new ArrayList<Node>(); /** * 节点的父节点 */ private Node parent; public Node() { } public Node(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 boolean isExpand() { return isExpand; } /** * 当父节点收起。其子节点也收起 * @param isExpand */ public void setExpand(boolean isExpand) { this.isExpand = isExpand; if (!isExpand) { for (Node node : childrenNodes) { node.setExpand(isExpand); } } } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getLevel() { return parent == null ? 0 : parent.getLevel() + 1; } public void setLevel(int level) { this.level = level; } public int getIcon() { return icon; } public void setIcon(int icon) { this.icon = icon; } public List<Node> getChildrenNodes() { return childrenNodes; } public void setChildrenNodes(List<Node> childrenNodes) { this.childrenNodes = childrenNodes; } public Node getParent() { return parent; } public void setParent(Node parent) { this.parent = parent; } /** * 推断是否是根节点 * * @return */ public boolean isRoot() { return parent == null; } /** * 推断是否是叶子节点 * * @return */ public boolean isLeaf() { return childrenNodes.size() == 0; } /** * 推断父节点是否展开 * * @return */ public boolean isParentExpand() { if (parent == null) return false; return parent.isExpand(); } public boolean isChecked() { return isChecked; } public void setChecked(boolean isChecked) { this.isChecked = isChecked; } public boolean isHideChecked() { return isHideChecked; } public void setHideChecked(boolean isHideChecked) { this.isHideChecked = isHideChecked; } } </span>2.自己定义继承于BaseAdapter的适配器TreeListViewAdapter<T>
<span style="font-family:KaiTi_GB2312;font-size:18px;">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(); } } </span>
3.Node节点实现帮助类TreeHelper.java
<span style="font-family:KaiTi_GB2312;font-size:18px;">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); } } } </span>
以上就是本文的全部内容。
源代码地址:http://download.csdn.net/detail/a123demi/8109643