android 自定义树形结构组件,【Android】自定义树形控件

2016年11月13日 第二篇

Android自定义树形控件

注:根据鸿洋的Android自定义任意层级树形控件 编写

效果图

AAffA0nNPuCLAAAAAElFTkSuQmCC

节点类(Node.java)

package jfsl.treeviewdemo.utils;

import java.util.ArrayList;

import java.util.List;

/**

* Created by JFSL on 2016/9/1 15:27.

*/

public class Node

{

private int id;

/**

* 父节点

*/

private int pId;

/**

* 显示的文字内容

*/

private String name;

/**

* 层级

*/

private int level;

/**

* 是否展开

*/

private boolean isExpand;

private int iconId;

/**

* 父节点

*/

private Node parent;

/**

* 子节点

*/

private List children = new ArrayList();

public Node(int id,int pId,String name)

{

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;

}

/**

* 获取层级

* @return

*/

public int getLevel()

{

return parent == null ? 0 : parent.getLevel() + 1;

}

public void setLevel(int level)

{

this.level = level;

}

public boolean isExpand()

{

return isExpand;

}

/**

* 设置收缩状态

* @param expand

*/

public void setExpand(boolean expand)

{

isExpand = expand;

//设置收缩状态

if(!isExpand)

{

//所有子节点都设置成false

for(Node node : children)

{

node.setExpand(isExpand);

}

}

}

public int getIconId()

{

return iconId;

}

public void setIconId(int iconId)

{

this.iconId = iconId;

}

public Node getParent()

{

return parent;

}

public void setParent(Node parent)

{

this.parent = parent;

}

public List getChildren()

{

return children;

}

public void setChildren(List children)

{

this.children = children;

}

/**

* 判断是否是根节点

*/

public boolean isRoot()

{

return parent == null;

}

/**

* 判断父节点的展开状态

*/

public boolean isParentExpand()

{

//根节点

if(parent == null)

return false;

return parent.isExpand();

}

/**

* 是否是叶子结点

*/

public boolean isLeaf()

{

return children.size() == 0;

}

}

节点辅助类(TreeViewHelper.java)

package jfsl.treeviewdemo.utils;

import java.lang.reflect.Field;

import java.util.ArrayList;

import java.util.List;

import jfsl.treeviewdemo.R;

import jfsl.treeviewdemo.annotation.TreeNodeId;

import jfsl.treeviewdemo.annotation.TreeNodeLabel;

import jfsl.treeviewdemo.annotation.TreeNodeParentId;

/**

* Created by JFSL on 2016/9/1 15:25.

*

*/

public class TreeViewHelper

{

public static final int ICON_NONE = - 1;

public static final int CURRENT_LEVEL = 1;

/**

* 转换数据

* @param datas

* @param

* @return

* @throws IllegalAccessException

*/

public static List convertDatasToNodes(List datas) throws IllegalAccessException

{

List nodes = new ArrayList<>();

Node node = null;

//遍历数据

for(T t : datas)

{

int id = - 1;

int pId = - 1;

String label = null;

/**

* 反射+注解 获取成员变量的值

*/

Class clazz = t.getClass();

Field[] fields = clazz.getDeclaredFields();

//反射获取值

for(Field field : fields)

{

//获取id

if(field.getAnnotation(TreeNodeId.class) != null)

{

//设置可见

field.setAccessible(true);

id = field.getInt(t);

}

//获取pId

if(field.getAnnotation(TreeNodeParentId.class) != null)

{

//设置可见

field.setAccessible(true);

pId = field.getInt(t);

}

//获取label

if(field.getAnnotation(TreeNodeLabel.class) != null)

{

//设置可见

field.setAccessible(true);

label = (String)field.get(t);

}

}

//加入节点

node = new Node(id,pId,label);

nodes.add(node);

}

//设置节点之间的关系

settingNodeRelation(nodes);

//设置图标状态

settingNodeIcon(nodes);

return nodes;

}

/**

* 设置节点之间的关系

* @param nodes

*/

private static void settingNodeRelation(List nodes)

{

//设置关系

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);

//n的父节点的id等于m节点的id;也就是m节点是n的父节点

if(n.getpId() == m.getId())

{

m.getChildren().add(n);

n.setParent(m);

}

//同理

else if(m.getpId() == n.getId())

{

n.getChildren().add(m);

m.setParent(n);

}

}

}

}

/**

* 设置图标状态

* @param nodes

*/

private static void settingNodeIcon(List nodes)

{

for(Node no : nodes)

{

setNodeIcon(no);

}

}

/**

* 设置节点图标

*

* @param node

*/

public static void setNodeIcon(Node node)

{

//有子节点

if(node.getChildren().size() > 0)

{

//展开

if(node.isExpand())

{

node.setIconId(R.mipmap.icon_expand);

return;

}

//收缩

node.setIconId(R.mipmap.icon_collapse);

}

//没有图标

else

node.setIconId(ICON_NONE);

}

/**

* 获取排序后的节点

*

* @param datas

* @param

* @return

*/

public static List getSortedNodes(List datas,int defaultLevel) throws IllegalAccessException

{

//已经排序后的节点

List newNodes = new ArrayList<>();

//未排序的节点,只是转换的

List oldNodes = convertDatasToNodes(datas);

/**

* 类似于采用深度遍历树

* PS:另外的方法,前序遍历树

*/

//***********************************************************

//获取根节点

List rootNodes = getRootNodes(oldNodes);

for(Node node : rootNodes)

{

//默认级别为1

addNode(newNodes,node,defaultLevel,CURRENT_LEVEL);

}

//***********************************************************

return newNodes;

}

/**

* 深度遍历,并添加

*

* @param newNodes

* @param node

* @param defaultLevel

* @param currentLevel

*/

private static void addNode(List newNodes,Node node,int defaultLevel,int currentLevel)

{

//添加节点

newNodes.add(node);

//设置展开的级别

if(defaultLevel >= currentLevel)

{

node.setExpand(true);

}

//叶子节点,也就是最后了,不用再遍历

if(node.isLeaf())

return;

//遍历子节点

for(int i = 0;i < node.getChildren().size();i++)

{

addNode(newNodes,node.getChildren().get(i),defaultLevel,currentLevel + 1);

}

}

/**

* 过滤出需要显示的节点

*/

public static List filterVisibleNodes(List nodes)

{

List visibleNodes = new ArrayList<>();

for(Node node : nodes)

{

if(node.isRoot() || node.isParentExpand())

{

//设置节点的图标

setNodeIcon(node);

visibleNodes.add(node);

}

}

return visibleNodes;

}

/**

* 获取根节点

*

* @param oldNodes

* @return

*/

private static List getRootNodes(List oldNodes)

{

List rootNode = new ArrayList<>();

for(Node node : oldNodes)

{

if(node.isRoot())

{

rootNode.add(node);

}

}

return rootNode;

}

}

文件Bean类,测试的数据类(FileBean.java)

package jfsl.treeviewdemo.bean;

import jfsl.treeviewdemo.annotation.TreeNodeId;

import jfsl.treeviewdemo.annotation.TreeNodeLabel;

import jfsl.treeviewdemo.annotation.TreeNodeParentId;

/**

* Created by JFSL on 2016/9/1 15:22.

*/

public class FileBean

{

@TreeNodeId

private int id;

@TreeNodeParentId

private int pId;

@TreeNodeLabel

private String name;

public FileBean(int id,int pId,String name)

{

this.id = id;

this.pId = pId;

this.name = name;

}

public int getId()

{

return id;

}

public void setId(int id)

{

this.id = id;

}

public String getName()

{

return name;

}

public void setName(String name)

{

this.name = name;

}

@Override

public String toString()

{

return "FileBean{" +

"id=" + id +

", name='" + name + '\'' +

'}';

}

}

注解接口

1.TreeNodeId

package jfsl.treeviewdemo.annotation;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

@Target(ElementType.FIELD)

@Retention(RetentionPolicy.RUNTIME)

public @interface TreeNodeId

{

}

2.TreeNodeLabel

package jfsl.treeviewdemo.annotation;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

@Target(ElementType.FIELD)

@Retention(RetentionPolicy.RUNTIME)

public @interface TreeNodeLabel

{

}

3.TreeNodeParentId

package jfsl.treeviewdemo.annotation;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

@Target(ElementType.FIELD)

@Retention(RetentionPolicy.RUNTIME)

public @interface TreeNodeParentId

{

}

1.数据适配器(父类)(TreeViewAdapter.java)

package jfsl.treeviewdemo.adapter;

import android.content.Context;

import android.view.LayoutInflater;

import android.view.View;

import android.view.ViewGroup;

import android.widget.AdapterView;

import android.widget.BaseAdapter;

import android.widget.ListView;

import java.util.List;

import jfsl.treeviewdemo.utils.Node;

import jfsl.treeviewdemo.utils.TreeViewHelper;

public abstract class TreeViewAdapter extends BaseAdapter

{

//上下文

protected Context mContext;

//所有的节点

protected List mAllNodes;

//显示的节点

protected List mVisibleNodes;

//加载布局

protected LayoutInflater mInflater;

//ListView

protected ListView mListTree;

/**

* 自定义回调接口

*/

private OnTreeNodeClickListener mListener;

public interface OnTreeNodeClickListener

{

void onClick(Node node,int position);

}

public void setListener(OnTreeNodeClickListener listener)

{

mListener = listener;

}

public TreeViewAdapter(Context context,ListView listTree ,List datas,int defaultLevel) throws IllegalAccessException

{

mContext = context;

mAllNodes = TreeViewHelper.getSortedNodes(datas,defaultLevel);

mVisibleNodes = TreeViewHelper.filterVisibleNodes(mAllNodes);

mInflater = LayoutInflater.from(mContext);

mListTree = listTree;

//设置ListView的点击事件

mListTree.setOnItemClickListener(new AdapterView.OnItemClickListener()

{

@Override

public void onItemClick(AdapterView> adapterView,View view,int position,long id)

{

//展开或者收缩

expandOrCollapse(position);

//事件回调

if(mListener != null)

{

mListener.onClick(mVisibleNodes.get(position),position);

}

}

});

}

/**

* 展开或者收缩

* @param position

*/

private void expandOrCollapse(int position)

{

Node node = mVisibleNodes.get(position);

if(node != null)

{

if(node.isLeaf())

return;

//反向选择

node.setExpand(!node.isExpand());

//更新数据

mVisibleNodes = TreeViewHelper.filterVisibleNodes(mAllNodes);

notifyDataSetChanged();

}

}

@Override

public int getCount()

{

return mVisibleNodes.size();

}

@Override

public Object getItem(int position)

{

return mVisibleNodes.get(position);

}

@Override

public long getItemId(int position)

{

return position;

}

@Override

public View getView(int position,View view,ViewGroup viewGroup)

{

Node node = mVisibleNodes.get(position);

view = getConvertView(node,position,view,viewGroup);

//设置距离问题

view.setPadding(node.getLevel() * 70,3,3,3);

return view;

}

public abstract View getConvertView(Node node,int position,View view,ViewGroup viewGroup);

}

2.树形数据适配器(SimpleTreeAdapter.java)

package jfsl.treeviewdemo.adapter;

import android.content.Context;

import android.view.View;

import android.view.ViewGroup;

import android.widget.ImageView;

import android.widget.ListView;

import android.widget.TextView;

import java.util.List;

import jfsl.treeviewdemo.R;

import jfsl.treeviewdemo.utils.Node;

/**

* Created by JFSL on 2016/9/1 20:36.

* 自定义Item的界面

* 可以根据情况修改

*/

public class SimpleTreeAdapter extends TreeViewAdapter

{

public SimpleTreeAdapter(Context context,ListView listTree,List datas,int defaultLevel) throws IllegalAccessException

{

super(context,listTree,datas,defaultLevel);

}

/**

* 主要用来自定义界面

* @param node

* @param position

* @param view

* @param viewGroup

* @return

*/

@Override

public View getConvertView(Node node,int position,View view,ViewGroup viewGroup)

{

ViewHolder holder = null;

if(view == null)

{

view = mInflater.inflate(R.layout.listview_item_treeview,viewGroup,false);

holder = new ViewHolder();

holder.icon = (ImageView)view.findViewById(R.id.id_item_icon);

holder.text = (TextView)view.findViewById(R.id.id_item_text);

view.setTag(holder);

} else

{

holder = (ViewHolder)view.getTag();

}

//没有图标,也就是没有子节点的数据项

if(node.getIconId() == - 1)

{

holder.icon.setVisibility(View.INVISIBLE);

}

//有图标

else

{

holder.icon.setVisibility(View.VISIBLE);

holder.icon.setImageResource(node.getIconId());

}

holder.text.setText(node.getName());

return view;

}

private class ViewHolder

{

ImageView icon;

TextView text;

}

}

测试的Activity

package jfsl.treeviewdemo;

import android.support.v7.app.AppCompatActivity;

import android.os.Bundle;

import android.widget.ListView;

import java.util.ArrayList;

import java.util.List;

import jfsl.treeviewdemo.adapter.SimpleTreeAdapter;

import jfsl.treeviewdemo.bean.FileBean;

/**

* 主界面

* @date 2016年9月1日 21:27:11

* @author JFSL

*

*/

public class ActivityMain extends AppCompatActivity

{

//

private ListView mListView;

//数据集

private List mDatas;

//适配器

private SimpleTreeAdapter mAdapter;

@Override

protected void onCreate(Bundle savedInstanceState)

{

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

try

{

mListView = (ListView)findViewById(R.id.id_listview);

//初始化模拟数据

initTestDatas();

//初始化适配器 默认显示层级 0

mAdapter = new SimpleTreeAdapter(this,mListView,mDatas,0);

//绑定适配器

mListView.setAdapter(mAdapter);

} catch(IllegalAccessException e)

{

}

}

/**

* 初始化模拟数据

*/

private void initTestDatas()

{

mDatas = new ArrayList<>();

FileBean bean = new FileBean(1,0,"xx小学");

mDatas.add(bean);

bean = new FileBean(2,1,"一(1)班");

mDatas.add(bean);

bean = new FileBean(201,2,"Android学习");

mDatas.add(bean);

bean = new FileBean(211,2,"Java技术");

mDatas.add(bean);

bean = new FileBean(221,2,"计算机网络");

mDatas.add(bean);

bean = new FileBean(8,0,"xx中学");

mDatas.add(bean);

bean = new FileBean(11,8,"九(2)班");

mDatas.add(bean);

bean = new FileBean(12,0,"xx中学");

mDatas.add(bean);

bean = new FileBean(13,12,"高一(16)班");

mDatas.add(bean);

bean = new FileBean(14,12,"高二(13)班");

mDatas.add(bean);

bean = new FileBean(15,12,"高三(11)班");

mDatas.add(bean);

bean = new FileBean(16,0,"XXXXXX");

mDatas.add(bean);

bean = new FileBean(17,16,"大一");

mDatas.add(bean);

bean = new FileBean(18,16,"大二");

mDatas.add(bean);

bean = new FileBean(19,16,"大三");

mDatas.add(bean);

bean = new FileBean(20,19,"Android学习");

mDatas.add(bean);

bean = new FileBean(21,19,"Java技术");

mDatas.add(bean);

bean = new FileBean(22,21,"计算机网络");

mDatas.add(bean);

}

}

1.activity_main.xml

xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="match_parent"

android:layout_height="match_parent">

android:id="@+id/id_listview"

android:layout_width="match_parent"

android:layout_height="match_parent"/>

2.listview_item_treeview.xml

xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="match_parent"

android:layout_height="56dp">

android:id="@+id/id_item_icon"

android:layout_width="56dp"

android:scaleType="center"

android:class="lazyload" src="https://img-blog.csdnimg.cn/2022010620315055724.png" data-original="@mipmap/ic_launcher"

android:layout_height="match_parent"/>

android:id="@+id/id_item_text"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:gravity="center_vertical"

android:layout_marginLeft="4dp"/>

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
快速实现Android多级树形列表,这个库是在鸿洋多级树形列表demo中修改而来。解决的问题:支持ID为int类型和String类型。支持多级复选框选中,使用只需一行代码。支持动态更新数据并保持原有展开/关闭状态。支持ListView、RecyclerView。USE我们关联列表需要有三个必须元素,当前id、父级id即pid,显示的内容。id和pid可以为int或者String以及其他类型。要显示的内容需要包装一下://id pid name  FileNode为实际用的实体Bean对象 mlist.add(new Node("223","0","我也是添加的root节点",new FileNode()));对于ListView,需要继承自TreeListViewAdapter,如:public class SimpleTreeAdapter extends TreeListViewAdapter {     public SimpleTreeAdapter(ListView mTree, Context context, List datas, int defaultExpandLevel, int iconExpand, int iconNoExpand) {         super(mTree, context, datas, defaultExpandLevel, iconExpand, iconNoExpand);     }     public SimpleTreeAdapter(ListView mTree, Context context, List datas,                              int defaultExpandLevel) {         super(mTree, context, datas, defaultExpandLevel);     }     @Override     public View getConvertView(final Node node , int position, View convertView, ViewGroup parent)     {        final ViewHolder viewHolder ;         if (convertView == null) {             convertView = mInflater.inflate(R.layout.list_item, parent, false);             viewHolder = new ViewHolder();             viewHolder.cb = (CheckBox) convertView                     .findViewById(R.id.cb_select_tree);             viewHolder.label = (TextView) convertView                     .findViewById(R.id.id_treenode_label);             viewHolder.icon = (ImageView) convertView.findViewById(R.id.icon);             convertView.setTag(viewHolder);         } else {             viewHolder = (ViewHolder) convertView.getTag();         }         viewHolder.cb.setOnClickListener(new View.OnClickListener() {             @Override             public void onClick(View v) {                 setChecked(node,viewHolder.cb.isChecked());             }         });         if (node.isChecked()){             viewHolder.cb.setChecked(true);         }else {             viewHolder.cb.setChecked(false);         }         if (node.getIcon() == -1) {             viewHolder.icon.setVisibility(View.INVISIBLE);         } else {             viewHolder.icon.setVisibility(View.VISIBLE);             viewHolder.icon.setImageResource(node.getIcon());         }         viewHolder.label.setText(node.getName());         return convertView;     }     private final class ViewHolder     {         ImageView icon;         CheckBox cb;         TextView label;     } }对于RecyclerView,需继承自TreeRecyclerAdapter,如:public class SimpleTreeRecyclerAdapter extends TreeRecyclerAdapter {     public SimpleTreeRecyclerAdapter(RecyclerView mTree, Context context, List datas, int defaultExpandLevel, int iconExpand, int iconNoExpand) {         super(mTree, context, datas, defaultExpandLevel, iconExpand, iconNoExpand);     }     public SimpleTreeRecyclerAdapter(RecyclerView mTree, Context context, List datas, int defaultExpandLevel) {         super(mTree, context, datas, defaultExpandLevel);     }     @Override     public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {         return new MyHoder(View.inflate(mContext, R.layout.list_item,null));     }     @Override     public void onBindViewHolder(final Node node, RecyclerView.ViewHolder holder, int position) {         final MyHoder viewHolder = (MyHoder) holder;         //todo do something         viewHolder.cb.setOnClickListener(new View.OnClickListener() {             @Override             public void onClick(View v) {                 setChecked(node,viewHolder.cb.isChecked());             }         });         if (node.isChecked()){             viewHolder.cb.setChecked(true);         }else {             viewHolder.cb.setChecked(false);         }         if (node.getIcon() == -1) {             viewHolder.icon.setVisibility(View.INVISIBLE);         } else {             viewHolder.icon.setVisibility(View.VISIBLE);             viewHolder.icon.setImageResource(node.getIcon());         }         viewHolder.label.setText(node.getName());     }     class MyHoder extends RecyclerView.ViewHolder{         public CheckBox cb;         public TextView label;         public ImageView icon;         public MyHoder(View itemView) {             super(itemView);             cb = (CheckBox) itemView                     .findViewById(R.id.cb_select_tree);             label = (TextView) itemView                     .findViewById(R.id.id_treenode_label);             icon = (ImageView) itemView.findViewById(R.id.icon);         }     } }初始化: ListView://第一个参数  ListView //第二个参数  上下文 //第三个参数  数据集 //第四个参数  默认展开层级数 0为不展开 //第五个参数  展开的图标 //第六个参数  闭合的图标 mAdapter = new SimpleTreeAdapter(mTree, ListViewActivity.this,                         mDatas, 1,R.mipmap.tree_ex,R.mipmap.tree_ec); mTree.setAdapter(mAdapter);RecyclerView://第一个参数  RecyclerView //第二个参数  上下文 //第三个参数  数据集 //第四个参数  默认展开层级数 0为不展开 //第五个参数  展开的图标 //第六个参数  闭合的图标 mAdapter = new SimpleTreeRecyclerAdapter(mTree, RecyclerViewActivity.this,                 mDatas, 1,R.mipmap.tree_ex,R.mipmap.tree_ec); mTree.setAdapter(mAdapter);添加数据,可以保持原有选中或者展开状态:List mlist = new ArrayList(); mlist.add(new Node("223","0","我也是添加的root节点",new FileNode())); mAdapter.addData(0,mlist);获取选中内容:如果node的isChecked()为true,即为选中状态。StringBuilder sb = new StringBuilder(); //获取排序过的nodes //如果不需要刻意直接用 mDatas既可 final List allNodes = mAdapter.getAllNodes(); for (int i = 0; i < allNodes.size(); i ) {    if (allNodes.get(i).isChecked()){        sb.append(allNodes.get(i).getName() ",");    } } String strNodesName = sb.toString(); if (!TextUtils.isEmpty(strNodesName))    Toast.makeText(this, strNodesName.substring(0, strNodesName.length()-1),Toast.LENGTH_SHORT).show();控制父子之间联动的选中与取消状态,只需调用setChecked方法既可,注意如果在setOnCheckedChangeListener中处理会有问题:因为如果要子节点/父节点选中或者取消需要刷新页面,而刷新页面又会触发viewHolder.cb.setChecked(true/false);的判断从而又会进入setOnCheckedChangeListener,会导致如果父节点选中某些子节点取消不了的情况。//viewHolder.cb 为CheckBox  viewHolder.cb.setOnClickListener(new View.OnClickListener() {             @Override             public void onClick(View v) {                 setChecked(node,viewHolder.cb.isChecked());             }         });

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值