前言:
前段时间由于要构建一个树形结构的控件一直找不到头绪,然后看到了hongyang大神的一篇博客,才逐渐找到思路,直接利用ListView来构建树形控件。整体来说,树形控件也就是一个ListView样式的展示形式,只要处理好每个层级的内边距,以及点击item展开收缩时adapter中数据的顺序,这样就好做处理了。 以下是项目的演示以及整体结构的展示:第一步:
首先是构建node的实体,node中有自己的id,以及上一级的id,它所属上一级的node,是否展开等.public class Node {代码中都有注释,相信自己看下很容易理解的,我这里做的处理是每个item都是默认不展开的,并且层级只有3层。
//每个层级各自的id
private String id;
//每个层级的名字
private String name;
//树的层级
private String level;
//是否有子类
private boolean isHasChild;
//parentID
private String parentID;
//节点所包含的子类
private List<Node> childList = new ArrayList<Node>();
//是否展开
private boolean isExpand = false;
//是否请求过
private boolean isRequest = false;
//父级Node
private Node parent;
//第3level请求图片所需要的参数
//private PictureParams params;
public Node(){
}
public Node(String level, String name, String id, String parentID){
this.level = level;
this.name = name;
this.id = id;
this.parentID = parentID;
}
public String getParentID() {
return parentID;
}
public void setParentID(String parentID) {
this.parentID = parentID;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Node getParent() {
return parent;
}
public void setParent(Node parent) {
this.parent = parent;
}
public List<Node> getChilds(){
return childList;
}
public void setChild(Node child){
childList.add(child);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLevel() {
return level;
}
public void setLevel(String level) {
this.level = level;
}
public boolean isHasChild() {
return isHasChild;
}
public void setHasChild(boolean isHasChild) {
this.isHasChild = isHasChild;
}
public boolean isExpand() {
return isExpand;
}
public boolean isRequest() {
return isRequest;
}
public void setRequest(boolean isRequest) {
this.isRequest = isRequest;
}
//这里是获取图片的参数信息我就注释了,没做处理(把图片参数封装成一个实体)
//public PictureParams getParams() {
//return params;
//}
//
//public void setParams(PictureParams params) {
//this.params = params;
//}
//设置item的展开状态
public void setExpand(boolean isExpand) {
this.isExpand = isExpand;
if(!isExpand){
for(Node node : childList){
node.setExpand(isExpand);
}
}
}
//item的parent是否为展开状态
public boolean isParentExpand(){
if(parent == null){
return false;
}
return parent.isExpand();
}
}
第二步:
构建我们ListView的adapter,通过对服务端拿到的数据进行处理,然后展现到listview中。public abstract class TreeListViewAdapter<T> extends BaseAdapter {这里运用到了泛型,这样就可以支持不同类型的服务端的数据,当然也可以写死,但是这样就不方便以后的重构了,adapter整体的思路就是把服务端传来的List<T>转换成我们Lsit<Node>,然后通过每个node的层级以及子类是否展开来展现到listview上,adapter中运用到了TreeHelper的工具类,用于处理实体转换成Node,以及过滤出需要显示的node。 TreeHelper中的代码:
protected Context mContext;
// 存储所有可见的node
protected List<Node> mVISNodes;
// 存储 所有的Node
protected List<Node> mAllNodes;
public LayoutInflater mInflater;
// 当前item中的Node
protected Node currentNode;
private TreeNodeOnclickListener treeNodeOnclickListener = null;
public TreeListViewAdapter(ListView mTree, Context context) {
mInflater = LayoutInflater.from(context);
mTree.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
if (treeNodeOnclickListener != null) {
treeNodeOnclickListener.onclick(mVISNodes.get(position), position);
}
//开启线程获取数据的时候,这个expandOfNodes()方法会先执行,
//然后再执行treeNodeOnclickListener回调的onclick方法,
//所以第二次加载的数据直接调用addNewDatas()
expandOfNodes(position);
}
});
}
public interface TreeNodeOnclickListener {
void onclick(Node node, int position);
}
public void setTreeNodeOnclickListener(
TreeNodeOnclickListener treeNodeOnclickListener) {
this.treeNodeOnclickListener = treeNodeOnclickListener;
}
@Override
public int getCount() {
// TODO Auto-generated method stub
return mVISNodes.size();
}
@Override
public Object getItem(int position) {
return mVISNodes.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
currentNode = mVISNodes.get(position);
convertView = getConvertView(convertView, currentNode, position, parent);
// 设置内边距
convertView.setPadding(Integer.parseInt(currentNode.getLevel()) * 30, 3, 3, 3);
return convertView;
}
//点击item时控制整个树的显示
private void expandOfNodes(int position){
Node node = mVISNodes.get(position);
if(!node.getLevel() .equals("3")){
node.setExpand(!node.isExpand());
//把所有的node数据过滤拿到需要显示的item
mVISNodes = TreeHelper.filterVisibleNode(mAllNodes);
notifyDataSetChanged();
}
}
// 首次从服务端拿到数据,然后转成Node(初始化数据)
public void setDatas(List<T> datas)
throws IllegalAccessException, IllegalArgumentException {
List<Node> list = new ArrayList<Node>();
// 把从服务端拿到的数据源转换成Node
mAllNodes = TreeHelper.getSortedNodes(datas);
//把所有的node数据过滤拿到需要显示的item
mVISNodes = TreeHelper.filterVisibleNode(mAllNodes);
notifyDataSetChanged();
}
//点击第一层或者第二层时加载数据,然后更新listview
public void addNewDatas(List<T> datas, String selectedID) throws IllegalAccessException, IllegalArgumentException{
List<Node> list = TreeHelper.newBeanToNode(datas);
int parentID = -1;
Node selectedNode = null;
for(Node scNode : mAllNodes){
if(scNode.getId().equals(selectedID)){
selectedNode = scNode;
}
}
//由于网络延迟原因,所以这里直接设置被选中的Node为展开状态
selectedNode.setExpand(true);
for(Node mNewNode : list){
selectedNode.getChilds().add(mNewNode);
mNewNode.setParent(selectedNode);
}
mAllNodes.addAll(list);
mAllNodes = TreeHelper.reorderNodes(mAllNodes);
mVISNodes = TreeHelper.filterVisibleNode(mAllNodes);
notifyDataSetChanged();
}
//当前被选中的node
public Node getCurrentNode() {
return currentNode;
}
public abstract View getConvertView(View convertView, Node node,
int position, ViewGroup parent);
}
public class TreeHelper {
public static <T> List<Node> getSortedNodes(List<T> datas) throws IllegalAccessException, IllegalArgumentException {
List<Node> result = new ArrayList<Node>();
//将服务端拿到的实体 转成Node
List<Node> nodes = convertedDataToNode(datas);
//拿到第一级目录
List<Node> rootNodes = getRootNodes(nodes);
//排序,及设置Node之间的关系
for(Node mNode : rootNodes){
addNodes(result, mNode);
}
return result;
}
public static <T> List<Node> convertedDataToNode(List<T> datas) throws IllegalAccessException, IllegalArgumentException{
List<Node> result = new ArrayList<Node>();
Node node = null;
for(T t : datas){
String name = null;
String level = null;
String systemID = null;
String parentID = null;
Class<? extends Object> clazz = t.getClass();
Field[] fields = clazz.getDeclaredFields();
for(Field field : fields){
//层级值获取
if(field.getAnnotation(TreeNodeLevel.class) != null){
field.setAccessible(true);
level = (String) field.get(t);
}
//层级name获取
if(field.getAnnotation(TreeNodeLabel.class) != null){
field.setAccessible(true);
name = (String) field.get(t);
}
////层级id获取
if(field.getAnnotation(TreeNodeSystemID.class) != null){
field.setAccessible(true);
systemID = (String) field.get(t);
}
//层级parentid获取
if(field.getAnnotation(TreeNodeParentID.class) != null){
field.setAccessible(true);
parentID = (String) field.get(t);
}
if(level != null && name != null && systemID != null && parentID != null){
break;
}
}
node = new Node(level, name, systemID, parentID);
result.add(node);
}
/**
* 设置Node间,父子关系;让每两个节点都比较一次,即可设置其中的关系
*/
for (int i = 0; i < result.size(); i++)
{
Node n = result.get(i);
for (int j = i + 1; j < result.size(); j++)
{
Node m = result.get(j);
if (m.getParentID().equals(n.getId()))
{
n.getChilds().add(m);
m.setParent(n);
} else if (m.getId().equals(n.getParentID()))
{
m.getChilds().add(n);
n.setParent(m);
}
}
}
return result;
}
//获取level为1时的节点
public static List<Node> getRootNodes(List<Node> datas){
List<Node> result = new ArrayList<Node>();
for(Node node : datas){
if(node.getLevel().equals("1")){
result.add(node);
}
}
return result;
}
// 过滤出显示的Node
public static List<Node> filterVisibleNode(List<Node> nodes) {
List<Node> result = new ArrayList<Node>();
for (Node node : nodes) {
// level为1时默认显示,或者上层目录为展开状态״̬
if(node.getLevel().equals("1") || node.getParent().isExpand()){
result.add(node);
}
}
return result;
}
public static void addNodes(List<Node> nodes, Node node){
nodes.add(node);
if(node.getLevel().equals("3")){
return;
}
if(node.getChilds().size() != 0){
for(int i = 0; i < node.getChilds().size(); i++){
addNodes(nodes, node.getChilds().get(i));
}
}
}
//新增数据转成Node
public static <T> List<Node> newBeanToNode(List<T> datas) throws IllegalAccessException, IllegalArgumentException{
List<Node> list = new ArrayList<Node>();
Node node = null;
for(T t : datas){
String name = null;
String level = null;
String systemID = null;
String parentID = null;
//图片参数信息字段
//String writid = null;
//String fileName = null;
//String reportName = null;
//String cllx = null;
Class<? extends Object> clazz = t.getClass();
Field[] fields = clazz.getDeclaredFields();
for(Field field : fields){
//层级值获取
if(field.getAnnotation(TreeNodeLevel.class) != null){
field.setAccessible(true);
level = (String) field.get(t);
}
//层级名字获取
if(field.getAnnotation(TreeNodeLabel.class) != null){
field.setAccessible(true);
name = (String) field.get(t);
}
//层级ID获取
if(field.getAnnotation(TreeNodeSystemID.class) != null){
field.setAccessible(true);
systemID = (String) field.get(t);
}
//层级parentid获取
if(field.getAnnotation(TreeNodeParentID.class) != null){
field.setAccessible(true);
parentID = (String) field.get(t);
}
//第三层图片获取--writid
//if(field.getAnnotation(TreeNodeWritid.class) != null){
//field.setAccessible(true);
//writid = (String) field.get(t);
//}
//第三层图片获取--cllx
//if(field.getAnnotation(TreeNodeCllx.class) != null){
//field.setAccessible(true);
//cllx = (String) field.get(t);
//}
//第三层图片获取--filename
//if(field.getAnnotation(TreeNodeFileName.class) != null){
//field.setAccessible(true);
//fileName = (String) field.get(t);
//}
//第三层图片获取--reportName
//if(field.getAnnotation(TreeNodeReportName.class) != null){
//field.setAccessible(true);
//reportName = (String) field.get(t);
//}
//if(level != null && name != null && systemID != null
//&& parentID != null && cllx != null
//&& fileName != null && reportName != null
//&& writid != null){
//break;
//}
if(level != null && name != null && systemID != null && parentID != null){
break;
}
}
node = new Node(level, name, systemID, parentID);
list.add(node);
}
return list;
}
public static List<Node> reorderNodes(List<Node> datas){
List<Node> result = new ArrayList<Node>();
//拿到第一级目录
List<Node> rootNodes = getRootNodes(datas);
//排序,及设置Node之间的关系
//这里出 了变化
for(Node mNode : rootNodes){
addNodes(result, mNode);
}
return result;
}
}
图片信息我就注释了,我自己项目中是点击第三层item,然后展现出图片,这里紧急提供的只是一个思路,自己可以根据自己的项目来定义。 从TreeHelper中可以发现,这里是把服务端拿到的List<T>中的每个实体通过注解来获取实体的字段信息,然后再生成Node。所以我这里模拟服务端返回的实体为OBean:
public class OBean {基本上就是每个item中的层级,自己的id,名字,以及上一层级的id。注解这里我就只贴其中一个的代码了,因为都差不多:
@TreeNodeLevel
private String level;
@TreeNodeLabel
private String name;
@TreeNodeSystemID
private String id;
@TreeNodeParentID
private String fid;
/**
* 以下是图片字段的信息,这里没做处理,仅仅只提供个思路。
* */
//@TreeNodeWritid
//private String WRITID;
//
//@TreeNodeFileName
//private String fileName;
//
//@TreeNodeCllx
//private String cllx;
//
//@TreeNodeReportName
//private String reportname;
public OBean(String level, String name, String id, String fid){
this.level = level;
this.name = name;
this.id = id;
this.fid = fid;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getLevel() {
return level;
}
public void setLevel(String level) {
this.level = level;
}
public String getLabel() {
return name;
}
public void setLabel(String name) {
this.name = name;
}
public String getParentID() {
return fid;
}
public void setParentID(String fid) {
this.fid = fid;
}
}
@Target(ElementType.FIELD)这样一来基本就完成了,只要写个类继承我们的TreeListViewAdapter,重写其中getcontentView的方法,然后在activity中把ListView关联就可以了。 TreeSimpleAdapter的代码:
@Retention(RetentionPolicy.RUNTIME)
public @interface TreeNodeSystemID {
}
//这个类仅仅是继承TreeListViewAdapter重写下getConvertView来设置item的View的展现
public class TreeSimpleAdapter<T> extends TreeListViewAdapter<T>{
public TreeSimpleAdapter(ListView mTree, Context context) {
super(mTree, context);
}
@Override
public View getConvertView(View convertView, Node node, int position,
ViewGroup parent) {
ViewHolder viewHolder= null;
if(convertView == null){
convertView = mInflater.inflate(R.layout.tree_list_item, parent, false);
viewHolder = new ViewHolder();
viewHolder.image = (ImageView) convertView.findViewById(R.id.id_treenode_icon);
viewHolder.text = (TextView) convertView.findViewById(R.id.id_treenode_label);
convertView.setTag(viewHolder);
}else{
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.text.setText(node.getName());
return convertView;
}
class ViewHolder{
private ImageView image;
private TextView text;
}
}
MainActivity的代码:
public class MainActivity extends Activity {
private ListView listView;
private TreeSimpleAdapter mAdapter;
private List<OBean> list;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findView();
initValues();
buildView();
}
private void findView() {
listView = (ListView) findViewById(R.id.listview);
}
public void initValues() {
mAdapter = new TreeSimpleAdapter<OBean>(listView, MainActivity.this);
}
private void buildView() {
try {
//初始化数据,第一次加载时调用添加数据的方法
mAdapter.setDatas(initDatas());
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
mAdapter.setTreeNodeOnclickListener(new TreeNodeOnclickListener() {
@Override
public void onclick(Node node, int position) {
// TODO Auto-generated method stub
String level = node.getLevel();
if(level.equals("1")){
//如果node下面没有展开项 则从后台获取数据,这里仅仅是模拟下
if(node.getChilds().size() == 0){
try {
List<OBean> secondNodes = new ArrayList<OBean>();
secondNodes.add(new OBean("2", "bb", "12", node.getId()));
secondNodes.add(new OBean("2", "bb", "13", node.getId()));
secondNodes.add(new OBean("2", "bb", "14", node.getId()));
secondNodes.add(new OBean("2", "bb", "15", node.getId()));
mAdapter.addNewDatas(secondNodes, node.getId());
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}else if(level.equals("2")){
//点击第二层加载第三层的node数据
if(node.getChilds().size() == 0){
try {
List<OBean> thirdNodes = new ArrayList<OBean>();
thirdNodes.add(new OBean("3", "cc", "16", node.getId()));
thirdNodes.add(new OBean("3", "cc", "17", node.getId()));
thirdNodes.add(new OBean("3", "cc", "18", node.getId()));
thirdNodes.add(new OBean("3", "cc", "19", node.getId()));
mAdapter.addNewDatas(thirdNodes, node.getId());
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}else if(level.equals("3")){
//这里一般都是开启另一个页面或者是加载获取图片,这里就不做模拟了
}
}
});
listView.setAdapter(mAdapter);
}
private List<OBean> initDatas(){
//模拟从服务端首次获取的数据
list = new ArrayList<OBean>();
list.add(new OBean("1", "aa", "1", "0"));
list.add(new OBean("1", "aa", "2", "0"));
list.add(new OBean("1", "aa", "3", "0"));
list.add(new OBean("1", "aa", "4", "0"));
list.add(new OBean("1", "aa", "5", "0"));
list.add(new OBean("1", "aa", "6", "0"));
list.add(new OBean("1", "aa", "7", "0"));
list.add(new OBean("1", "aa", "8", "0"));
list.add(new OBean("2", "bb", "9", "0"));
list.add(new OBean("2", "bb", "10", "1"));
list.add(new OBean("2", "bb", "11", "1"));
return list;
}
}
MainActivity中的布局代码仅仅就是一个ListView:、
<RelativeLayout 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:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.ljx.ui.MainActivity" >
<ListView
android:id="@+id/listview"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
</ListView>
</RelativeLayout>
以及item的代码,仅仅只是一个imageView以及一个textView这个都可以根据自己的项目来改变的.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
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_ec" />
<TextView
android:id="@+id/id_treenode_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toRightOf="@id/id_treenode_icon"
android:text="@string/app_name"
android:textSize="18dip" />
</RelativeLayout>
其实整体来说无非也就是对于整个ListView中数据的展现做出改变,处理好这个展现的关系就很容易理解如何构建树形结构的控件了。PS:项目中的思路是借鉴了hongyang大神的博客,可以去看看他的树形结构控件的构建。