您的位置:首页 > 其它

自定义树形结构组件—TreeView

2011-11-30 11:50 351 查看
[b]自定义树形结构组件—TreeView[/b]

注:该文章为(男人应似海)原创,如需转载请注明出处!

组件功能

实现树形结构目录效果

效果图





实现方式

用两个树形节点类集合分别去存储所有节点(List<TreeElement> treeElements)和当前显示节点(List<TreeElement> currentElements),当前显示节点集合currentElements中的数据显示在ListView中。当点击含有子节点的节点时(如上图中的A、B、C、CC11)会把相应的子节点从所有节点集合treeElements中找到并添加当前显示节点集合currentElements中在ListView上显示或从currentElements中删除并从ListView刷新数据。

关于数据获取

这里获取树节点数据的方式是在工程资源中assets文件下创建一个本地文件(先在assets文件下创建textRes文件夹,然后在textRes文件夹下创建一个记录数据的文件,这里是treeview_elements.properties)如下图所示。



这个文件以某种格式记录数据参数(和树节点类属性对应),具体请查看下面的代码解释。

treeview_elements.properties文件中数据如下:

#id(s)-level(int)-title(s)-fold(b)-hasChild(b)-hasParent(b)-parentId(s)

1011-1-A-false-true-false-null

1021-2-AA11-false-false-true-1011

1022-2-AA22-false-false-true-1011

1023-2-AA33-false-false-true-1011

1024-2-AA44-false-false-true-1011

2011-1-B-false-true-false-null

2021-2-BB11-false-false-true-2011

2022-2-BB22-false-false-true-2011

2023-2-BB33-false-false-true-2011

2024-2-BB44-false-false-true-2011

3011-1-C-false-true-false-null

3021-2-CC11-false-true-true-3011

3031-3-CCC111-false-false-true-3021

3032-3-CCC222-false-false-true-3021

3033-3-CCC333-false-false-true-3021

以#开始的行为注释行,用于说明数据的含义和属性,其他的每行数据都代表了一个树节点对象实例TreeElement。如1021-2-AA11-false-false-true-1011解释如下:

数据

含义

1021

表示该节点id为1021

2

表示该节点为2级节点

AA11

表示该节点的标题或内容为AA11

false

表示该节点当前是处于为展开状态,即子节点不可见(只针对有子节点的节点)

false

表示该节点没有子节点

true

表示该节点有父节点

1011

表示该节点的父节点是1011

这种方式适合于数据量大且数据固定的情况,当数据较少时我们也可以通过直接new树节点对象来得到树节点集合。如果需要获取网络数据,也可以指定服务器返回特定格式的json数据,然后解析。

代码

TreeElement.java
/**

* 类名:TreeElement.java

* 类描述:树形结构节点类

* @author wader

* 创建时间:2011-11-03 16:32

*/

public class TreeElement {

String id = null;// 当前节点id

String title = null;// 当前节点文字

boolean hasParent = false;// 当前节点是否有父节点

boolean hasChild = false;// 当前节点是否有子节点

boolean childShow = false;// 如果子节点,字节当当前是否已显示

String parentId = null;// 父节点id

int level = -1;// 当前界面层级

boolean fold = false;// 是否处于展开状态

public boolean isChildShow() {

return childShow;

}

public void setChildShow(boolean childShow) {

this.childShow = childShow;

}

public String getId() {

return id;

}

public void setId(String id) {

this.id = id;

}

public String getTitle() {

return title;

}

public void setTitle(String title) {

this.title = title;

}

public boolean isHasParent() {

return hasParent;

}

public void setHasParent(boolean hasParent) {

this.hasParent = hasParent;

}

public boolean isHasChild() {

return hasChild;

}

public void setHasChild(boolean hasChild) {

this.hasChild = hasChild;

}

public String getParentId() {

return parentId;

}

public void setParentId(String parentId) {

this.parentId = parentId;

}

public int getLevel() {

return level;

}

public void setLevel(int level) {

this.level = level;

}

public boolean isFold() {

return fold;

}

public void setFold(boolean fold) {

this.fold = fold;

}

@Override

public String toString() {

return "id:" + this.id + "-level:" + this.level + "-title:"

+ this.title + "-fold:" + this.fold + "-hasChidl:"

+ this.hasChild + "-hasParent:" + this.hasParent + "-parentId:"+ this.parentId;

}

}

TreeViewAdapter.java
/**

* 类名:TreeViewAdapter.java

* 类描述:用于填充数据的类

* @author wader

* 创建时间:2011-11-03 16:32

*/

public class TreeViewAdapter extends BaseAdapter {

class ViewHolder {

ImageView icon;

TextView title;

}

Context context;

ViewHolder holder;

LayoutInflater inflater;

List<TreeElement> elements;

public TreeViewAdapter(Context context, List<TreeElement> elements) {

this.context = context;

this.elements = elements;

}

@Override

public int getCount() {

return elements.size();

}

@Override

public Object getItem(int position) {

return elements.get(position);

}

@Override

public long getItemId(int position) {

return position;

}

@Override

public View getView(int position, View convertView, ViewGroup parent) {

/**

* ---------------------- get holder------------------------

*/

if (convertView == null) {

if (inflater == null) {

inflater = LayoutInflater.from(context);

}

holder = new ViewHolder();

convertView = inflater

.inflate(R.layout.tree_view_item_layout, null);

holder.icon = (ImageView) convertView

.findViewById(R.id.tree_view_item_icon);

holder.title = (TextView) convertView

.findViewById(R.id.tree_view_item_title);

convertView.setTag(holder);

} else {

holder = (ViewHolder) convertView.getTag();

}

/**

* ---------------------- set holder--------------------------

*/

if (elements.get(position).isHasChild()) {// 有子节点,要显示图标

if (elements.get(position).isFold()) {

holder.icon.setImageResource(R.drawable.tree_view_icon_fold);

} else if (!elements.get(position).isFold()) {

holder.icon.setImageResource(R.drawable.tree_view_icon_unfold);

}

holder.icon.setVisibility(View.VISIBLE);

} else {// 没有子节点,要隐藏图标

holder.icon.setImageResource(R.drawable.tree_view_icon_fold);

holder.icon.setVisibility(View.INVISIBLE);

}

holder.icon.setPadding(25 * (elements.get(position).getLevel()), 0, 0, 0);// 根据层级设置缩进

holder.title.setText(elements.get(position).getTitle());

holder.title.setTextSize(40 - elements.get(position).getLevel() * 5); // 根据层级设置字体大小

return convertView;

}

}

TreeView.java
/**

* 类名:TreeView.java

* 类描述:实现树形结构的view

* @author wader

* 创建时间:2011-11-03 16:32

*/

public class TreeView extends ListView implements OnItemClickListener {

String TAG = "TreeView";

List<TreeElement> treeElements = null;// 所有节点集合

List<TreeElement> currentElements = null;// 当前显示的节点集合

List<TreeElement> tempElements = null;// 用于临时存储

List<TreeElement> treeElementsToDel; // 临时存储待删除的节点

TreeViewAdapter adapter = null;// 用于数据填充

LastLevelItemClickListener itemClickCallBack;// 用户点击事件回调

public TreeView(final Context context, AttributeSet attrs) {

super(context, attrs);

Log.d(TAG, "create with TreeView(Context context, AttributeSet attrs)");

treeElements = new ArrayList<TreeElement>();

currentElements = new ArrayList<TreeElement>();

adapter = new TreeViewAdapter(context, currentElements);

this.setAdapter(adapter);

itemClickCallBack = new LastLevelItemClickListener() {

@Override

public void onLastLevelItemClick(int position,TreeViewAdapter adapter) {

Log.d(TAG, "last level element "

+ currentElements.get(position).getTitle()

+ " is clicked");

Toast.makeText(context,

currentElements.get(position).getTitle(), 200).show();

}

};

this.setOnItemClickListener(this);

}

public void initData(Context context, List<TreeElement> treeElements) {

this.treeElements = treeElements;

getFirstLevelElements(context);

adapter.notifyDataSetChanged();

}

/**

* 设置点击事件回调接口

*

* @param itemClickCallBack

*/

public void setLastLevelItemClickCallBack(LastLevelItemClickListener itemClickCallBack) {

this.itemClickCallBack = itemClickCallBack;

}

/**

* 初始化树形结构列表数据,把第一层级的数据添加到currentElements中

*/

public void getFirstLevelElements(Context context) {

Log.d(TAG, "initCurrentElements");

int size = treeElements.size();

Log.d(TAG, "tree elements num is: " + size);

if (currentElements == null) {

currentElements = new ArrayList<TreeElement>();

}

currentElements.clear();

for (int i = 0; i < size; i++) {

if (treeElements.get(i).getLevel() == 1) {

currentElements.add(treeElements.get(i));

Log.d(TAG, "find a first level element: " + treeElements.get(i));

}

}

}

/**

* 从所有节点集合中获取某父节点的子节点集合

*

* @param parentId

* @return

*/

private List<TreeElement> getChildElementsFromAllById(String parentId) {

tempElements = new ArrayList<TreeElement>();

int size = treeElements.size();

for (int i = 0; i < size; i++) {

if (treeElements.get(i).getParentId().equalsIgnoreCase(parentId)) {

tempElements.add(treeElements.get(i));

Log.d(TAG, "find a child element: " + treeElements.get(i));

}

}

return tempElements;

}

/**

* 从当前显示的节点集合中获取某父节点的子节点集合

*

* @param parentId

* @return

*/

private List<TreeElement> getChildElementsFromCurrentById(String parentId) {

Log.d(TAG, "getChildElementsFromCurrentById parentId: " + parentId);

if (tempElements == null) {

tempElements = new ArrayList<TreeElement>();

} else {

tempElements.clear();

}

int size = currentElements.size();

for (int i = 0; i < size; i++) {

if (currentElements.get(i).getParentId().equalsIgnoreCase(parentId)) {

tempElements.add(currentElements.get(i));

Log.d(TAG,

"find a child element to delete: "

+ currentElements.get(i));

}

}

return tempElements;

}

/**

* 删除某父节点的所有子节点集合

*

* @param parentId

* @return

*/

private synchronized boolean delAllChildElementsByParentId(String parentId) {

Log.e(TAG, "delAllChildElementsByParentId: " + parentId);

int size;

TreeElement tempElement = currentElements

.get(getElementIndexById(parentId));

List<TreeElement> childElments = getChildElementsFromCurrentById(parentId);

if (treeElementsToDel == null) {

treeElementsToDel = new ArrayList<TreeElement>();

} else {

treeElementsToDel.clear();

}

size = childElments.size();

Log.e(TAG, "childElments size : " + size);

for (int i = 0; i < size; i++) {

tempElement = childElments.get(i);

if (tempElement.hasChild && tempElement.fold) {

treeElementsToDel.add(tempElement);

}

}

size = treeElementsToDel.size();

Log.e(TAG, "treeElementsToDel size : " + size);

for (int i = size - 1; i >= 0; i--) {

delAllChildElementsByParentId(treeElementsToDel.get(i).getId());

}

delDirectChildElementsByParentId(parentId);

return true;

}

/**

* 删除某父节点的直接子节点集合

*

* @param parentId

* @return

*/

private synchronized boolean delDirectChildElementsByParentId(

String parentId) {

Log.d(TAG, "delDirectChildElementsByParentId(): " + parentId);

boolean success = false;

if (currentElements == null || currentElements.size() == 0) {

Log.d(TAG,

"delChildElementsById() failed,currentElements is null or it's size is 0");

return success;

}

synchronized (currentElements) {

int size = currentElements.size();

Log.d(TAG, "begin delete");

for (int i = size - 1; i >= 0; i--) {

if (currentElements.get(i).getParentId()

.equalsIgnoreCase(parentId)) {

currentElements.get(i).fold = false;// 记得隐藏子节点时把展开状态设为false

currentElements.remove(i);

}

}

}

success = true;

return success;

}

/**

* 根据id查下标

*

* @param id

* @return

*/

private int getElementIndexById(String id) {

int num = currentElements.size();

for (int i = 0; i < num; i++) {

if (currentElements.get(i).getId().equalsIgnoreCase(id)) {

return i;

}

}

return -1;

}

@Override

public void onItemClick(AdapterView<?> arg0, View convertView,

int position, long id) {

TreeElement element = currentElements.get(position);

if (element.isHasChild()) {// 当前节点有子节点时只进行数据显示或隐藏等操作

if (!element.isFold()) {// 当前父节点为未展开状态

currentElements.addAll(position + 1,

this.getChildElementsFromAllById(element.getId()));

} else if (element.fold) {// 当前父节点为展开状态

boolean success = this.delAllChildElementsByParentId(element

.getId());

// boolean success =

// this.delDirectChildElementsByParentId(element

// .getId());

Log.d(TAG, "delete child state: " + success);

if (!success) {

return;

}

}

// 调试信息

// Log.d(TAG, "elements in currentElements:\n");

// for (int i = 0; i < currentElements.size(); i++) {

// Log.d(TAG + i, currentElements.get(i) + "\n");

// }

element.setFold(!element.isFold());// 设置反状态

adapter.notifyDataSetChanged();// 刷新数据显示

} else {// 当前节点有子节点时只进行用户自定义操作

itemClickCallBack.onLastLevelItemClick(position,adapter);

}

}

/**

* 自定义内部接口,用于用户点击最终节点时的事件回调

*/

public interface LastLevelItemClickListener {

public void onLastLevelItemClick(int position,TreeViewAdapter adapter);

}

}

ResManager.java
/**

* 类名:ResManager.java

* @author wader

* 类描述:获取工程中assets目下的文字、图片等资源

* 创建时间:2011-11-29 16:07

*/

public class ResManager {

/**

* 图片格式

*/

private static final String IMAGE_FILE_FORMAT = ".png";

/**

* 文本文件格式

*/

private static final String TEXT_FILE_FORMAT = ".properties";

/**

* 图片存放的路径

*/

public final static String IMAGES_DIR = "images/";

// public final static String IMAGES_DIR_480 = "images_480/";

public final static String TEXTS_DIR = "textRes/";

/**

* 文件路径

*/

private static String filePath = "";

/**

* 从工程资源加载图片资源(路径是assets/images/**.png)

*

* @param fileName

* 图片资源路径

*/

public static Bitmap loadImageRes(Activity activity, int screenWidth,

String fileName) {

Bitmap bitmap = null;

InputStream is = null;

FileInputStream fis = null;

filePath = IMAGES_DIR;

// 这里可以根据分辨率等进行路径区分判断

// if (screenWidth >= 480) {

// filePath = IMAGES_DIR_480;

// }

try {

is = activity.getAssets().open(

filePath + fileName + IMAGE_FILE_FORMAT);

if (is != null) {

bitmap = BitmapFactory.decodeStream(is);

}

} catch (Exception e) {

} finally {

try {

if (is != null) {

is.close();

}

if (fis != null) {

fis.close();

}

} catch (Exception e) {

} finally {

is = null;

fis = null;

}

}

return bitmap;

}

/**

* 从工程资源加载文字资源(路径是:assets/textRes/**.properties)

*

* @param fileName

*/

public static ArrayList<String> loadTextRes(String fileName, Context context) {

filePath = TEXTS_DIR;

return loadProperties(filePath + fileName + TEXT_FILE_FORMAT, context);

}

/**

* 读取配置文件读取配置信息

*

* @param filename

* 配置文件路径

* @return 包含配置信息的hashmap键值对

*/

private static ArrayList<String> loadProperties(String filename,

Context context) {

Log.d("loadProperties", "loadProperties");

ArrayList<String> properties = new ArrayList<String>();

InputStream is = null;

FileInputStream fis = null;

InputStreamReader rin = null;

// 将配置文件放到res/raw/目录下,可以通过以下的方法获取

// is = context.getResources().openRawResource(R.raw.system);

// 这是读取配置文件的第二种方法

// 将配置文件放到assets目录下,可以通过以下的方法获取

// is = context.getAssets().open("system.properties");

// 用来提取键值对的临时字符串

StringBuffer tempStr = new StringBuffer();

// 用来存放读取的每个字符

int ch = 0;

// 用来保存读取的配置文件一行的信息

String line = null;

try {

Log.d("loadProperties", "the filename is: " + filename);

is = context.getAssets().open(filename);

rin = new InputStreamReader(is, "UTF-8");

while (ch != -1) {

tempStr.delete(0, tempStr.length());

while ((ch = rin.read()) != -1) {

if (ch != '\n') {

tempStr.append((char) ch);

} else {

break;

}

}

line = tempStr.toString().trim();

Log.d("loadProperties", "line: " + line);

// 判断读出的那行数据是否有效,#开头的代表注释,如果是注释行那么跳过下面,继续上面操作

if (line.length() == 0 || line.startsWith("#")) {

continue;

}

properties.add(line);

}

} catch (IOException e) {

// LogX.trace("read property file", e.getMessage() + "fail");

} finally {

try {

if (is != null) {

is.close();

}

if (fis != null) {

fis.close();

}

if (null != rin) {

rin.close();

}

} catch (IOException e) {

// LogX.trace("read property file", e.getMessage() + "fail");

} finally {

is = null;

fis = null;

rin = null;

}

}

return properties;

}

}

TreeElementParser.java
/**

* 类名称:TreeElementParser.java

* @author wader

* 类描述:树形组件节点解析类,将树节点类(TreeElement)字符串信息集合解析为属性节点类集合

* 创建时间:2011—11-29 17:36

*/

public class TreeElementParser {

private static final String TAG = "TreeElementParser";

/**

* TreeElement的属性个数,可根据实际情况进行改动

*/

private static final int TREE_ELEMENT_ATTRIBUTE_NUM = 7;

/**

* 把节点字符串信息集合解析成节点集合 这里的解析可根据实际情况进行改动

*

* @param list

* @return

*/

public static List<TreeElement> getTreeElements(List<String> list) {

if (list == null) {

Log.d(TAG,

"the string list getted from solarterm.properties by ResManager.loadTextRes is null");

return null;

}

int elementNum = list.size();

List<TreeElement> treeElements = new ArrayList<TreeElement>();

String info[] = new String[TREE_ELEMENT_ATTRIBUTE_NUM];

for (int i = 0; i < elementNum; i++) {// 读取树形结构节点数

if (treeElements == null) {

treeElements = new ArrayList<TreeElement>();

}

info = list.get(i).split("-");

TreeElement element = new TreeElement();

element.setId(info[0]);

element.setLevel(Integer.valueOf(info[1]));

element.setTitle(info[2]);

element.setFold(Boolean.valueOf(info[3]));

element.setHasChild(Boolean.valueOf(info[4]));

element.setHasParent(Boolean.valueOf(info[5]));

element.setParentId(info[6]);

Log.d(TAG, "add a TreeElement: " + element.toString());

treeElements.add(element);

}

return treeElements;

}

}

Activity布局文件tree_view_layout.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="#669977"

android:orientation="vertical" >

<!-- 这里要写自定义组件的全路径,若果出现类解析错误请查看是否是这里路径问题-->

<com.dev.widget.treeview.view.TreeView

android:id="@+id/tree_view"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

android:cacheColorHint="#669977"

android:divider="#006600"

android:dividerHeight="1dip"

android:padding="10dip" >

</com.dev.widget.treeview.view.TreeView>

</LinearLayout>

ListView的Item布局文件tree_view_item_layout.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:gravity="center_vertical"

android:orientation="horizontal" >

<ImageView

android:id="@+id/tree_view_item_icon"

android:layout_width="wrap_content"

android:layout_height="wrap_content" />

<TextView

android:id="@+id/tree_view_item_title"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_gravity="center_vertical"

android:layout_marginLeft="15dip"

android:text="title" />

</LinearLayout>

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>

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

package="com.dev.widget.treeview"

android:versionCode="1"

android:versionName="1.0" >

<uses-sdk android:minSdkVersion="4" />

<application

android:icon="@drawable/ic_launcher"

android:label="@string/app_name" >

<activity

android:label="@string/activity_name"

android:name=".TreeViewActivity" >

<intent-filter >

<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />

</intent-filter>

</activity>

</application>

</manifest>

Activity界面代码 TreeViewActivity.java

public class TreeViewActivity extends Activity {

private TreeView treeView;

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.tree_view_layout);

treeView = (TreeView) findViewById(R.id.tree_view);

/**

* 这部分可根据实际需要更换树形节点信息获取方式,如获取特定格式的json字符串

*/

List<String> treeElementsString = ResManager.loadTextRes(

"treeview_elements", this);// 读存放在工程assets/textRes目录下得文件资源

/**

* 我们也可以直接new出很多TreeElement实例然后得到树形节点的集合,但如果节点很多的话很不方便

*/

List<TreeElement> treeElements = TreeElementParser

.getTreeElements(treeElementsString);// 解析读出的文件资源内容

LastLevelItemClickListener itemClickCallBack = new LastLevelItemClickListener() {//创建节点点击事件监听

@Override

public void onLastLevelItemClick(int position,

TreeViewAdapter adapter) {

TreeElement element = (TreeElement) adapter.getItem(position);

Toast.makeText(getApplicationContext(), element.getTitle(), 300)

.show();

}

};

treeView.initData(this, treeElements);// 初始化数据

treeView.setLastLevelItemClickCallBack(itemClickCallBack);//设置节点点击事件监听

}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: