简单介绍

android是不提供树形控件的,假设须要使用树形控件。我们应该怎么做呢?

先看效果



上图是一个明显的树形结构

实现原理

在逻辑上,它们是包括关系。数据结构上是多叉树,这是毋庸置疑的。

可是,显示的时候。我们有必要嵌套ListView或RecyclerView吗?当然没有必要!

  • 每一而Item。在显示的时候,都是平级的,仅仅是它们marginLeft不同而已。
  • 更新marginLeft来体现它们的层级关系。

    marginLeft的值与item在逻辑上的深度有线性关系。

  • 展开一个Item的时候,是动态的加入一系列的item。
  • 收起一个Item的时候。我们是删除一系列的item.

好了。原理已经说明确了。那就看看源代码怎么写吧。

注:

  • 我们以android的文件系统的树形结构为例
  • 为了动画的流畅性,我们使用RecyclerView,注意,ListView在加入和删除item时。是直接突变的。

Code

  • 数据模型ItemData

  1. public class ItemData implements Comparable<ItemData> {
  2. public static final int ITEM_TYPE_PARENT = 0;
  3. public static final int ITEM_TYPE_CHILD = 1;
  4. private String uuid;
  5. private int type;// 显示类型
  6. private String text;
  7. private String path;// 路径
  8. private int treeDepth = 0;// 路径的深度
  9. private List<ItemData> children;
  10. private boolean expand;// 是否展开
  11. ...
  12. }
  • 父节点相应的ViewHolder

  1. /**
  2. * @Author Zheng Haibo
  3. * @PersonalWebsite http://www.mobctrl.net
  4. * @Description
  5. */
  6. public class ParentViewHolder extends BaseViewHolder {
  7. public ImageView image;
  8. public TextView text;
  9. public ImageView expand;
  10. public TextView count;
  11. public RelativeLayout relativeLayout;
  12. private int itemMargin;
  13. public ParentViewHolder(View itemView) {
  14. super(itemView);
  15. image = (ImageView) itemView.findViewById(R.id.image);
  16. text = (TextView) itemView.findViewById(R.id.text);
  17. expand = (ImageView) itemView.findViewById(R.id.expand);
  18. count = (TextView) itemView.findViewById(R.id.count);
  19. relativeLayout = (RelativeLayout) itemView.findViewById(R.id.container);
  20. itemMargin = itemView.getContext().getResources()
  21. .getDimensionPixelSize(R.dimen.item_margin);
  22. }
  23. public void bindView(final ItemData itemData, final int position,
  24. final ItemDataClickListener imageClickListener) {
  25. RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) expand
  26. .getLayoutParams();
  27. params.leftMargin = itemMargin * itemData.getTreeDepth();
  28. expand.setLayoutParams(params);
  29. text.setText(itemData.getText());
  30. if (itemData.isExpand()) {
  31. expand.setRotation(45);
  32. List<ItemData> children = itemData.getChildren();
  33. if (children != null) {
  34. count.setText(String.format("(%s)", itemData.getChildren()
  35. .size()));
  36. }
  37. count.setVisibility(View.VISIBLE);
  38. } else {
  39. expand.setRotation(0);
  40. count.setVisibility(View.GONE);
  41. }
  42. relativeLayout.setOnClickListener(new OnClickListener() {
  43. @Override
  44. public void onClick(View v) {
  45. if (imageClickListener != null) {
  46. if (itemData.isExpand()) {
  47. imageClickListener.onHideChildren(itemData);
  48. itemData.setExpand(false);
  49. rotationExpandIcon(45, 0);
  50. count.setVisibility(View.GONE);
  51. } else {
  52. imageClickListener.onExpandChildren(itemData);
  53. itemData.setExpand(true);
  54. rotationExpandIcon(0, 45);
  55. List<ItemData> children = itemData.getChildren();
  56. if (children != null) {
  57. count.setText(String.format("(%s)", itemData
  58. .getChildren().size()));
  59. }
  60. count.setVisibility(View.VISIBLE);
  61. }
  62. }
  63. }
  64. });
  65. image.setOnLongClickListener(new OnLongClickListener() {
  66. @Override
  67. public boolean onLongClick(View view) {
  68. Toast.makeText(view.getContext(), "longclick",
  69. Toast.LENGTH_SHORT).show();
  70. return false;
  71. }
  72. });
  73. }
  74. @TargetApi(Build.VERSION_CODES.HONEYCOMB)
  75. private void rotationExpandIcon(float from, float to) {
  76. if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
  77. ValueAnimator valueAnimator = ValueAnimator.ofFloat(from, to);
  78. valueAnimator.setDuration(150);
  79. valueAnimator.setInterpolator(new DecelerateInterpolator());
  80. valueAnimator.addUpdateListener(new AnimatorUpdateListener() {
  81. @Override
  82. public void onAnimationUpdate(ValueAnimator valueAnimator) {
  83. expand.setRotation((Float) valueAnimator.getAnimatedValue());
  84. }
  85. });
  86. valueAnimator.start();
  87. }
  88. }
  89. }
  • 子节点相应的ViewHolder

  1. /**
  2. * @Author Zheng Haibo
  3. * @PersonalWebsite http://www.mobctrl.net
  4. * @Description
  5. */
  6. public class ChildViewHolder extends BaseViewHolder {
  7. public TextView text;
  8. public ImageView image;
  9. public RelativeLayout relativeLayout;
  10. private int itemMargin;
  11. private int offsetMargin;
  12. public ChildViewHolder(View itemView) {
  13. super(itemView);
  14. text = (TextView) itemView.findViewById(R.id.text);
  15. image = (ImageView) itemView.findViewById(R.id.image);
  16. relativeLayout = (RelativeLayout) itemView.findViewById(R.id.container);
  17. itemMargin = itemView.getContext().getResources()
  18. .getDimensionPixelSize(R.dimen.item_margin);
  19. offsetMargin = itemView.getContext().getResources()
  20. .getDimensionPixelSize(R.dimen.expand_size);
  21. }
  22. public void bindView(final ItemData itemData, int position) {
  23. RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) image
  24. .getLayoutParams();
  25. params.leftMargin = itemMargin * itemData.getTreeDepth() + offsetMargin;
  26. image.setLayoutParams(params);
  27. text.setText(itemData.getText());
  28. relativeLayout.setOnClickListener(new OnClickListener() {
  29. @Override
  30. public void onClick(View view) {
  31. //TODO
  32. }
  33. });
  34. }
  35. }
  • RecyclerView的Adapter

该部分处理item点击之后的展开和收起,实质上就是将其全部的Children节点动态的加入或删除。加入的位置就是item当前的位置。实现代码在onExpandChildren和onHideChildren方法中。


  1. /**
  2. * @Author Zheng Haibo
  3. * @PersonalWebsite http://www.mobctrl.net
  4. * @Description
  5. */
  6. public class RecyclerAdapter extends RecyclerView.Adapter<BaseViewHolder> {
  7. private Context mContext;
  8. private List<ItemData> mDataSet;
  9. private OnScrollToListener onScrollToListener;
  10. public void setOnScrollToListener(OnScrollToListener onScrollToListener) {
  11. this.onScrollToListener = onScrollToListener;
  12. }
  13. public RecyclerAdapter(Context context) {
  14. mContext = context;
  15. mDataSet = new ArrayList<ItemData>();
  16. }
  17. @Override
  18. public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
  19. View view = null;
  20. switch (viewType) {
  21. case ItemData.ITEM_TYPE_PARENT:
  22. view = LayoutInflater.from(mContext).inflate(
  23. R.layout.item_recycler_parent, parent, false);
  24. return new ParentViewHolder(view);
  25. case ItemData.ITEM_TYPE_CHILD:
  26. view = LayoutInflater.from(mContext).inflate(
  27. R.layout.item_recycler_child, parent, false);
  28. return new ChildViewHolder(view);
  29. default:
  30. view = LayoutInflater.from(mContext).inflate(
  31. R.layout.item_recycler_parent, parent, false);
  32. return new ChildViewHolder(view);
  33. }
  34. }
  35. @Override
  36. public void onBindViewHolder(BaseViewHolder holder, int position) {
  37. switch (getItemViewType(position)) {
  38. case ItemData.ITEM_TYPE_PARENT:
  39. ParentViewHolder imageViewHolder = (ParentViewHolder) holder;
  40. imageViewHolder.bindView(mDataSet.get(position), position,
  41. imageClickListener);
  42. break;
  43. case ItemData.ITEM_TYPE_CHILD:
  44. ChildViewHolder textViewHolder = (ChildViewHolder) holder;
  45. textViewHolder.bindView(mDataSet.get(position), position);
  46. break;
  47. default:
  48. break;
  49. }
  50. }
  51. private ItemDataClickListener imageClickListener = new ItemDataClickListener() {
  52. @Override
  53. public void onExpandChildren(ItemData itemData) {
  54. int position = getCurrentPosition(itemData.getUuid());
  55. List<ItemData> children = getChildrenByPath(itemData.getPath(),
  56. itemData.getTreeDepth());
  57. if (children == null) {
  58. return;
  59. }
  60. addAll(children, position + 1);// 插入到点击点的下方
  61. itemData.setChildren(children);
  62. if (onScrollToListener != null) {
  63. onScrollToListener.scrollTo(position + 1);
  64. }
  65. }
  66. @Override
  67. public void onHideChildren(ItemData itemData) {
  68. int position = getCurrentPosition(itemData.getUuid());
  69. List<ItemData> children = itemData.getChildren();
  70. if (children == null) {
  71. return;
  72. }
  73. removeAll(position + 1, getChildrenCount(itemData) - 1);
  74. if (onScrollToListener != null) {
  75. onScrollToListener.scrollTo(position);
  76. }
  77. itemData.setChildren(null);
  78. }
  79. };
  80. @Override
  81. public int getItemCount() {
  82. return mDataSet.size();
  83. }
  84. private int getChildrenCount(ItemData item) {
  85. List<ItemData> list = new ArrayList<ItemData>();
  86. printChild(item, list);
  87. return list.size();
  88. }
  89. private void printChild(ItemData item, List<ItemData> list) {
  90. list.add(item);
  91. if (item.getChildren() != null) {
  92. for (int i = 0; i < item.getChildren().size(); i++) {
  93. printChild(item.getChildren().get(i), list);
  94. }
  95. }
  96. }
  97. /**
  98. * 依据路径获取子文件夹或文件
  99. *
  100. * @param path
  101. * @param treeDepth
  102. * @return
  103. */
  104. public List<ItemData> getChildrenByPath(String path, int treeDepth) {
  105. treeDepth++;
  106. try {
  107. List<ItemData> list = new ArrayList<ItemData>();
  108. File file = new File(path);
  109. File[] children = file.listFiles();
  110. List<ItemData> fileList = new ArrayList<ItemData>();
  111. for (File child : children) {
  112. if (child.isDirectory()) {
  113. list.add(new ItemData(ItemData.ITEM_TYPE_PARENT, child
  114. .getName(), child.getAbsolutePath(), UUID
  115. .randomUUID().toString(), treeDepth, null));
  116. } else {
  117. fileList.add(new ItemData(ItemData.ITEM_TYPE_CHILD, child
  118. .getName(), child.getAbsolutePath(), UUID
  119. .randomUUID().toString(), treeDepth, null));
  120. }
  121. }
  122. Collections.sort(list);
  123. Collections.sort(fileList);
  124. list.addAll(fileList);
  125. return list;
  126. } catch (Exception e) {
  127. }
  128. return null;
  129. }
  130. /**
  131. * 从position開始删除,删除
  132. *
  133. * @param position
  134. * @param itemCount
  135. * 删除的数目
  136. */
  137. protected void removeAll(int position, int itemCount) {
  138. for (int i = 0; i < itemCount; i++) {
  139. mDataSet.remove(position);
  140. }
  141. notifyItemRangeRemoved(position, itemCount);
  142. }
  143. protected int getCurrentPosition(String uuid) {
  144. for (int i = 0; i < mDataSet.size(); i++) {
  145. if (uuid.equalsIgnoreCase(mDataSet.get(i).getUuid())) {
  146. return i;
  147. }
  148. }
  149. return -1;
  150. }
  151. @Override
  152. public int getItemViewType(int position) {
  153. return mDataSet.get(position).getType();
  154. }
  155. public void add(ItemData text, int position) {
  156. mDataSet.add(position, text);
  157. notifyItemInserted(position);
  158. }
  159. public void addAll(List<ItemData> list, int position) {
  160. mDataSet.addAll(position, list);
  161. notifyItemRangeInserted(position, list.size());
  162. }
  163. }
  • 在MainActivity中调用

因为使用的是RecyclerView,在动态加入和删除孩子节点时。会有明显的“展开”和“收起”效果。


  1. /**
  2. * @Author Zheng Haibo
  3. * @PersonalWebsite http://www.mobctrl.net
  4. * @Description
  5. */
  6. public class MainActivity extends Activity {
  7. private RecyclerView recyclerView;
  8. private RecyclerAdapter myAdapter;
  9. private LinearLayoutManager linearLayoutManager;
  10. @Override
  11. protected void onCreate(Bundle savedInstanceState) {
  12. super.onCreate(savedInstanceState);
  13. setContentView(R.layout.activity_main);
  14. recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
  15. linearLayoutManager = new LinearLayoutManager(this);
  16. recyclerView.setLayoutManager(linearLayoutManager);
  17. recyclerView.getItemAnimator().setAddDuration(100);
  18. recyclerView.getItemAnimator().setRemoveDuration(100);
  19. recyclerView.getItemAnimator().setMoveDuration(200);
  20. recyclerView.getItemAnimator().setChangeDuration(100);
  21. myAdapter = new RecyclerAdapter(this);
  22. recyclerView.setAdapter(myAdapter);
  23. myAdapter.setOnScrollToListener(new OnScrollToListener() {
  24. @Override
  25. public void scrollTo(int position) {
  26. recyclerView.scrollToPosition(position);
  27. }
  28. });
  29. initDatas();
  30. }
  31. private void initDatas() {
  32. List<ItemData> list = myAdapter.getChildrenByPath("/", 0);
  33. myAdapter.addAll(list, 0);
  34. }
  35. }

Project

Demo的Github地址:https://github.com/nuptboyzhb/TreeRecyclerView

@Author: Zheng Haibo 莫川

使用RecyclerView写树形结构的TreeRecyclerView的更多相关文章

  1. js文章列表的树形结构输出

    文章表设计成这样了 后端直接给了无任何处理的json数据,现在要前端实现树形结构的输出,其实后端处理更简单写,不过既然来了就码出来 var doclist = [{ "id": 1 ...

  2. [从产品角度学EXCEL 02]-EXCEL里的树形结构

    这是<从产品角度学EXCEL>系列第三篇. 前言请看: 0 为什么要关注EXCEL的本质 1 excel是怎样运作的 或者你可以去微信公众号@尾巴说数 获得连载目录. 本文仅由尾巴本人发布 ...

  3. C# EasyUI树形结构权限管理模块

    最近悟出来一个道理,在这儿分享给大家:学历代表你的过去,能力代表你的现在,学习代表你的将来. 十年河东十年河西,莫欺少年穷 学无止境,精益求精 本节和大家探讨下C#使用EasyUI树形结构/Tree构 ...

  4. Atitit 常见的树形结构 红黑树  二叉树   B树 B+树  Trie树 attilax理解与总结

    Atitit 常见的树形结构 红黑树  二叉树   B树 B+树  Trie树 attilax理解与总结 1.1. 树形结构-- 一对多的关系1 1.2. 树的相关术语: 1 1.3. 常见的树形结构 ...

  5. 使用ztree.js,受益一生,十分钟学会使用tree树形结构插件

    看到ztree.js,这几个字眼,毋庸置疑,那肯定就是tree树形结构了,曾经的swing年代有jtree,后来jquery年代有jstree和treeview,虽然我没写过,但是我见过,一些小功能做 ...

  6. jQuery+zTree加载树形结构菜单

    jQuery+zTree加载树形结构菜单 由于项目中需要设计树形菜单功能,经过一番捣腾之后,终于给弄出来了,所以便记下来,也算是学习zTree的一个总结吧. zTree的介绍: 1.zTree 是利用 ...

  7. Qt QTreeWidget 树形结构实现(转)

    Qt中实现树形结构可以使用QTreeWidget类,也可以使用QTreeView类,QTreeWidget继承自QTreeView类.树形效果如下图所示: 这是怎么实现的呢?还有点击节点时会有相应的事 ...

  8. 树形结构部门的 sqlserver 排序

    树形结构部门的 sqlserver 排序 因为要实现部门排序功能,而且要考虑部门的层级,直接用 sql 排序是不行的,所以写个 sql function 来支持. 首先部门表:company CREA ...

  9. [SQL Server]树形结构的创建

    对于SQL Server来说,构建显示一个树形结构不是一件容易的事情,逻辑构造能力不是它的强项.不过也不是说它没有能力干这个事情,只要换一种思维方式就可以理解它的工作原理. 例如,现在有一张表的内容如 ...

随机推荐

  1. 宏HASH_INSERT

    调用 方法 HASH_INSERT(lock_t, hash, lock_sys->rec_hash,lock_rec_fold(space, page_no), lock); /******* ...

  2. BZOJ2055: 80人环游世界

    题解: 总算A掉了,各种蛋疼... int main() { freopen("input.txt","r",stdin); freopen("out ...

  3. spring+springMVC+JPA配置详解(使用缓存框架ehcache)

    SpringMVC是越来越火,自己也弄一个Spring+SpringMVC+JPA的简单框架. 1.搭建环境. 1)下载Spring3.1.2的发布包:Hibernate4.1.7的发布包(没有使用h ...

  4. apache开源项目--Camel

    Apache Camel 是一个非常强大的基于规则的路由以及媒介引擎,该引擎提供了一个基于POJO的 企业应用模式(Enterprise Integration Patterns)的实现,你可以采用其 ...

  5. apache开源项目--HIVE

    Hive是一个基于Hadoop的数据仓库平台.通过hive,我们可以方便地进行ETL的工作.hive定义了一个类似于SQL的查询语言:HQL,能 够将用户编写的QL转化为相应的Mapreduce程序基 ...

  6. Ejabberd源码解析前奏--集群

    一.如何工作 一个XMPP域是由一个或多个ejabberd节点伺服的. 这些节点可能运行在通过网络连接的不同机器上. 它们都必须有能力连接到所有其它节点的4369端口, 并且必须有相同的 magic ...

  7. HTML入门基础教程相关知识

    HTML入门基础教程 html是什么,什么是html通俗解答: html是hypertext markup language的缩写,即超文本标记语言.html是用于创建可从一个平台移植到另一平台的超文 ...

  8. nopcommerce商城系统--源代码结构和架构

    这个文档是让开发者了解nopcommerce解决方案结构的指南.这是新的nopcommerce开发者学习nopcommerce代码的相关文档.首先,nopCommerce源代码是很容易得到的.它是一个 ...

  9. Android调试工具及方法

    转自:http://www.cnblogs.com/feisky/archive/2010/01/01/1637566.html Logcat Dump一份系统消息的日志.这些消息包括模拟器抛出错误时 ...

  10. 序列化框架性能对比(kryo、hessian、java、protostuff)

    简介:   优点 缺点 Kryo 速度快,序列化后体积小 跨语言支持较复杂 Hessian 默认支持跨语言 较慢 Protostuff 速度快,基于protobuf 需静态编译 Protostuff- ...