转自:http://user.qzone.qq.com/314154775/infocenter#!app=2&via=QZ.HashRefresh&pos=1335594212
树型目录是一种直观的列表显示目录方式,在用户界面得到普遍使用,如Windows的资源管理器窗口,Eclipse的Package Explorer等。因此,我们也想在Android应用中使用这种方式实现对各类信息的分层与显示。然而,Android应用层只提供了ListView单层列表和ExpandableListView二级列表,无法直接显示像树型目录这样多层次的列表。那么如何在android应用上实现这树型目录呢。
我们知道ListView显示列表是通过添加适配器Adapter,适配器装配数据,在适配器的getView()方法中实现绘制列表的每一行。因此,我们可以得出,要实现树型目录的展开,收起,只是让适配器装配不同的数据。明白了这些,那么我们来实现树型目录就易如反掌。
首先,先来分析下树型目录的基本元素—目录结点TreeNode,每一个节点都有父节点(根节点除外)和可能存在子节点列表,每一个节点可以是目录也可以是一个文件,每一个节点在树型目录中都有一个等级level,节点有展开,收起状态expanded。
在弄清了目录节点具备的属性及动作后,我们需要为每一个TreeNode对象添加子节点列表childNodes,简单地说就是每一级目录的子目录。我们知道,在java程序中,使用递归遍历目录,因此,在这里我们也使用递归来为每一个节点增加子节点列表。
//parentNode是父节点,parentFile父节点所在的目录。
private void createTree(TreeNode parentNode, File parentFile) {这样,我们为每一个节点都添加了子节点,在适配器中只需将子节点列表添加到适配器的数据列表就可以显示展开目录,数据列表移除子节点列表即可收起目录。代码如下:
if (parentFile.isDirectory()) { // 是目录
parentNode.setIsDirectory(true); //设置节点为目录
if (parentFile.list().length > 0) {
parentNode.setHasChild(true); //该节点有子节点
}
for (File subFile : parentFile.listFiles()) {
TreeNode subNode = new TreeNode(subFile.getName());
subNode.setHasParent(true) //子节点有父节点;
subNode.setLevel(parentNode.getLevel() + 1);//节点等级
parentNode.addChild(subNode); //添加子节点
createTree(subNode, subFile); //递归
}
} else { // 是文件
parentNode.setIsDirectory(false);
parentNode.setHasChild(false);
}
}
if (node.getIsDirectory()) {
if (node.isExpanded()) { // 收起
node.setExpanded(false);
ArrayList<TreeNode> temp = new ArrayList<TreeNode>();
for (int i = position + 1; i < treeNodes.size(); i++) {
if (node.getLevel() >= treeNodes.get(i).getLevel()) {
break;
}
treeNodes.get(i).setExpanded(false);
temp.add(treeNodes.get(i));
}
treeNodes.removeAll(temp);
TreeListAdapter.this.notifyDataSetChanged();
} else { // 展开
node.setExpanded(true);
for (TreeNode childNode : node.getChildNodes()) {
childNode.setExpanded(false);
treeNodes.add(position + 1, childNode);
}
TreeListAdapter.this.notifyDataSetChanged();
}
}
最后,贴上运行的效果图:
完整代码:
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@drawable/background"
android:orientation="vertical" >
<ListView
android:id="@+id/tree_list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:cacheColorHint="#00000000"
android:scrollbars="vertical"
android:scrollingCache="false" />
</LinearLayout>
TreeNode.java
package gwn.develop.widget;
import java.util.ArrayList;
/**
* @功能 节点属性与方法
* **/
public class TreeNode {
private int level; //目录等级
private boolean hasParent; // 是否有父节点
private boolean hasChild; // 是否有子节点
private boolean isDirectory; //是否是目录
private boolean expanded; // 节点展开
private String title; // 节点名称
private ArrayList<TreeNode> childNodes = new ArrayList<TreeNode>(); // 子节点链表
public TreeNode(String title) {
this.title = title;
hasParent = false;
hasChild = false;
isDirectory = false;
this.expanded = false;
}
public String getTitle() {
return title;
}
public boolean getHasParent(){
return this.hasParent;
}
public void setHasParent(boolean hasParent) {
this.hasParent = hasParent;
}
public boolean getHasChild(){
return this.hasChild;
}
public void setHasChild(boolean hasChild) {
this.hasChild = hasChild;
}
public int getLevel() {
return this.level;
}
public void setLevel(int level) {
this.level = level;
}
public ArrayList<TreeNode> getChildNodes() {
return childNodes;
}
public boolean isExpanded() {
return expanded;
}
public void setExpanded(boolean expanded) {
this.expanded = expanded;
}
public void setIsDirectory(boolean isDirectory){
this.isDirectory = isDirectory;
}
public boolean getIsDirectory(){
return this.isDirectory;
}
public void addChild(TreeNode childNode) {
this.hasChild = true;
this.childNodes.add(childNode);
}
}
DataProvider.java
package gwn.develop.widget;
import java.io.File;
import java.util.ArrayList;
/**
* @功能 生成显示到树型目录的节点链表
* **/
public class DataProvider {
//treeNodes适配器装配的节点链表。
private ArrayList<TreeNode> treeNodes = new ArrayList<TreeNode>();
private String rootPath;
public DataProvider(String rootPath) {
this.rootPath = rootPath;
}
// 递归列举目录文件
private void createTree(TreeNode parentNode, File parentFile) {
if (parentFile.isDirectory()) { // 是目录
parentNode.setIsDirectory(true);
if (parentFile.list().length > 0) {
parentNode.setHasChild(true);
}
for (File subFile : parentFile.listFiles()) {
TreeNode subNode = new TreeNode(subFile.getName());
subNode.setHasParent(true);
//当前节点的等级为父节点等级加1
subNode.setLevel(parentNode.getLevel() + 1);
//将当前的子节点添加到父节点的子节点链表
parentNode.addChild(subNode);
//递归遍历
createTree(subNode, subFile);
}
} else { // 是文件
parentNode.setIsDirectory(false);
parentNode.setHasChild(false);
}
}
public ArrayList<TreeNode> getTreeNodes() {
if (treeNodes.size() == 0) {
File rootDir = new File(rootPath);
if (rootDir.isDirectory()) {
TreeNode rootNode = new TreeNode(rootDir.getName());
rootNode.setHasParent(false);
rootNode.setLevel(0);
treeNodes.add(rootNode); // 刚开始列出根目录
createTree(rootNode, rootDir);
}
}
return treeNodes;
}
}
TreeListAdapter.java
package gwn.develop.widget;
import java.util.ArrayList;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
/**
* @功能 构造适配器,并添加事件监听
* **/
public class TreeListAdapter extends BaseAdapter {
private Context context;
private ArrayList<TreeNode> treeNodes;
public TreeListAdapter(Context context, ArrayList<TreeNode> treeNodes) {
super();
this.context = context;
this.treeNodes = treeNodes;
}
@Override
public int getCount() {
return treeNodes.size();
}
@Override
public Object getItem(int position) {
return position;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(final int position, View convertView, ViewGroup arg2) {
ViewHolder holder = new ViewHolder();;
LinearLayout ll = new LinearLayout(context);
ll.setOrientation(LinearLayout.HORIZONTAL);
holder.text = new TextView(context);
holder.icon = new ImageView(context);
final TreeNode node = treeNodes.get(position);
ll.setPadding(50 * node.getLevel(), 5, 5, 5);
holder.text.setPadding(20, 5, 5, 5);
holder.text.setText(node.getTitle());
ll.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View arg0) {
if (node.getIsDirectory()) {
if (node.isExpanded()) { // 收起
node.setExpanded(false);
ArrayList<TreeNode> temp = new ArrayList<TreeNode>();
for (int i = position + 1; i < treeNodes.size(); i++) {
if (node.getLevel() >= treeNodes.get(i).getLevel()) {
//node的子节点已经完全添加到temp中
break;
}
treeNodes.get(i).setExpanded(false);
temp.add(treeNodes.get(i));
}
treeNodes.removeAll(temp);
TreeListAdapter.this.notifyDataSetChanged();
} else { // 展开
node.setExpanded(true);
for (TreeNode childNode : node.getChildNodes()) {
childNode.setExpanded(false);
treeNodes.add(position + 1, childNode);
}
TreeListAdapter.this.notifyDataSetChanged();
}
}
}
});
if (node.getIsDirectory() && (node.isExpanded() == false)) {
holder.icon.setBackgroundResource(R.drawable.node_expand);
} else if (node.getIsDirectory() && (node.isExpanded() == true)) {
holder.icon.setBackgroundResource(R.drawable.node_unexpand);
} else if (!node.getIsDirectory()) {
holder.icon.setVisibility(View.INVISIBLE);
}
ll.addView(holder.icon);
ll.addView(holder.text);
return ll;
}
class ViewHolder {
TextView text;
ImageView icon;
}
}
TreeListViewActivity.java
package gwn.develop.widget;
import java.util.ArrayList;
import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.widget.ListView;
public class TreeListViewActivity extends Activity {
private ListView treeListView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
String rootPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/主目录";
DataProvider dataProvider = new DataProvider(rootPath);
ArrayList<TreeNode> treeNodes = dataProvider.getTreeNodes();
TreeListAdapter adapter = new TreeListAdapter(this, treeNodes);
treeListView = (ListView) this.findViewById(R.id.tree_list);
treeListView.setAdapter(adapter);
}
}
BUG修复:
在适配收起时装配数据有误,node.getLevel() == treeNodes.get(i).getLevel())也将上一级的节点包括进去,造成循环未正常退出,改为node.getLevel() >= treeNodes.get(i).getLevel()),bug解决