使用RecyclerView写树形结构的TreeRecyclerView
来源:互联网 发布:火星漫游车的人工智能 编辑:程序博客网 时间:2024/06/05 23:55
简介
android是不提供树形控件的,如果需要使用树形控件,我们应该怎么做呢?
先看效果
上图是一个明显的树形结构
实现原理
在逻辑上,它们是包含关系,数据结构上是多叉树,这是毋庸置疑的。但是,显示的时候,我们有必要嵌套ListView或RecyclerView吗?当然没有必要!
- 每一而Item,在显示的时候,都是平级的,只是它们marginLeft不同而已。
- 更新marginLeft来体现它们的层级关系。marginLeft的值与item在逻辑上的深度有线性关系。
- 展开一个Item的时候,是动态的添加一系列的item。
- 收起一个Item的时候,我们是删除一系列的item.
好了,原理已经说明白了,那就看看源码怎么写吧。
注:
- 我们以android的文件系统的树形结构为例
- 为了动画的流畅性,我们使用RecyclerView,注意,ListView在添加和删除item时,是直接突变的。
Code
- 数据模型ItemData
public class ItemData implements Comparable<ItemData> { public static final int ITEM_TYPE_PARENT = 0; public static final int ITEM_TYPE_CHILD = 1; private String uuid; private int type;// 显示类型 private String text; private String path;// 路径 private int treeDepth = 0;// 路径的深度 private List<ItemData> children; private boolean expand;// 是否展开 ...}
- 父节点对应的ViewHolder
/** * @Author Zheng Haibo * @PersonalWebsite http://www.mobctrl.net * @Description */public class ParentViewHolder extends BaseViewHolder { public ImageView image; public TextView text; public ImageView expand; public TextView count; public RelativeLayout relativeLayout; private int itemMargin; public ParentViewHolder(View itemView) { super(itemView); image = (ImageView) itemView.findViewById(R.id.image); text = (TextView) itemView.findViewById(R.id.text); expand = (ImageView) itemView.findViewById(R.id.expand); count = (TextView) itemView.findViewById(R.id.count); relativeLayout = (RelativeLayout) itemView.findViewById(R.id.container); itemMargin = itemView.getContext().getResources() .getDimensionPixelSize(R.dimen.item_margin); } public void bindView(final ItemData itemData, final int position, final ItemDataClickListener imageClickListener) { RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) expand .getLayoutParams(); params.leftMargin = itemMargin * itemData.getTreeDepth(); expand.setLayoutParams(params); text.setText(itemData.getText()); if (itemData.isExpand()) { expand.setRotation(45); List<ItemData> children = itemData.getChildren(); if (children != null) { count.setText(String.format("(%s)", itemData.getChildren() .size())); } count.setVisibility(View.VISIBLE); } else { expand.setRotation(0); count.setVisibility(View.GONE); } relativeLayout.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (imageClickListener != null) { if (itemData.isExpand()) { imageClickListener.onHideChildren(itemData); itemData.setExpand(false); rotationExpandIcon(45, 0); count.setVisibility(View.GONE); } else { imageClickListener.onExpandChildren(itemData); itemData.setExpand(true); rotationExpandIcon(0, 45); List<ItemData> children = itemData.getChildren(); if (children != null) { count.setText(String.format("(%s)", itemData .getChildren().size())); } count.setVisibility(View.VISIBLE); } } } }); image.setOnLongClickListener(new OnLongClickListener() { @Override public boolean onLongClick(View view) { Toast.makeText(view.getContext(), "longclick", Toast.LENGTH_SHORT).show(); return false; } }); } @TargetApi(Build.VERSION_CODES.HONEYCOMB) private void rotationExpandIcon(float from, float to) { if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { ValueAnimator valueAnimator = ValueAnimator.ofFloat(from, to); valueAnimator.setDuration(150); valueAnimator.setInterpolator(new DecelerateInterpolator()); valueAnimator.addUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { expand.setRotation((Float) valueAnimator.getAnimatedValue()); } }); valueAnimator.start(); } }}
- 子节点对应的ViewHolder
/** * @Author Zheng Haibo * @PersonalWebsite http://www.mobctrl.net * @Description */public class ChildViewHolder extends BaseViewHolder { public TextView text; public ImageView image; public RelativeLayout relativeLayout; private int itemMargin; private int offsetMargin; public ChildViewHolder(View itemView) { super(itemView); text = (TextView) itemView.findViewById(R.id.text); image = (ImageView) itemView.findViewById(R.id.image); relativeLayout = (RelativeLayout) itemView.findViewById(R.id.container); itemMargin = itemView.getContext().getResources() .getDimensionPixelSize(R.dimen.item_margin); offsetMargin = itemView.getContext().getResources() .getDimensionPixelSize(R.dimen.expand_size); } public void bindView(final ItemData itemData, int position) { RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) image .getLayoutParams(); params.leftMargin = itemMargin * itemData.getTreeDepth() + offsetMargin; image.setLayoutParams(params); text.setText(itemData.getText()); relativeLayout.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { //TODO } }); }}
- RecyclerView的Adapter
该部分处理item点击之后的展开和收起,实质上就是将其所有的Children节点动态的添加或删除。添加的位置就是item当前的位置。实现代码在onExpandChildren和onHideChildren方法中。
/** * @Author Zheng Haibo * @PersonalWebsite http://www.mobctrl.net * @Description */public class RecyclerAdapter extends RecyclerView.Adapter<BaseViewHolder> { private Context mContext; private List<ItemData> mDataSet; private OnScrollToListener onScrollToListener; public void setOnScrollToListener(OnScrollToListener onScrollToListener) { this.onScrollToListener = onScrollToListener; } public RecyclerAdapter(Context context) { mContext = context; mDataSet = new ArrayList<ItemData>(); } @Override public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = null; switch (viewType) { case ItemData.ITEM_TYPE_PARENT: view = LayoutInflater.from(mContext).inflate( R.layout.item_recycler_parent, parent, false); return new ParentViewHolder(view); case ItemData.ITEM_TYPE_CHILD: view = LayoutInflater.from(mContext).inflate( R.layout.item_recycler_child, parent, false); return new ChildViewHolder(view); default: view = LayoutInflater.from(mContext).inflate( R.layout.item_recycler_parent, parent, false); return new ChildViewHolder(view); } } @Override public void onBindViewHolder(BaseViewHolder holder, int position) { switch (getItemViewType(position)) { case ItemData.ITEM_TYPE_PARENT: ParentViewHolder imageViewHolder = (ParentViewHolder) holder; imageViewHolder.bindView(mDataSet.get(position), position, imageClickListener); break; case ItemData.ITEM_TYPE_CHILD: ChildViewHolder textViewHolder = (ChildViewHolder) holder; textViewHolder.bindView(mDataSet.get(position), position); break; default: break; } } private ItemDataClickListener imageClickListener = new ItemDataClickListener() { @Override public void onExpandChildren(ItemData itemData) { int position = getCurrentPosition(itemData.getUuid()); List<ItemData> children = getChildrenByPath(itemData.getPath(), itemData.getTreeDepth()); if (children == null) { return; } addAll(children, position + 1);// 插入到点击点的下方 itemData.setChildren(children); if (onScrollToListener != null) { onScrollToListener.scrollTo(position + 1); } } @Override public void onHideChildren(ItemData itemData) { int position = getCurrentPosition(itemData.getUuid()); List<ItemData> children = itemData.getChildren(); if (children == null) { return; } removeAll(position + 1, getChildrenCount(itemData) - 1); if (onScrollToListener != null) { onScrollToListener.scrollTo(position); } itemData.setChildren(null); } }; @Override public int getItemCount() { return mDataSet.size(); } private int getChildrenCount(ItemData item) { List<ItemData> list = new ArrayList<ItemData>(); printChild(item, list); return list.size(); } private void printChild(ItemData item, List<ItemData> list) { list.add(item); if (item.getChildren() != null) { for (int i = 0; i < item.getChildren().size(); i++) { printChild(item.getChildren().get(i), list); } } } /** * 根据路径获取子目录或文件 * * @param path * @param treeDepth * @return */ public List<ItemData> getChildrenByPath(String path, int treeDepth) { treeDepth++; try { List<ItemData> list = new ArrayList<ItemData>(); File file = new File(path); File[] children = file.listFiles(); List<ItemData> fileList = new ArrayList<ItemData>(); for (File child : children) { if (child.isDirectory()) { list.add(new ItemData(ItemData.ITEM_TYPE_PARENT, child .getName(), child.getAbsolutePath(), UUID .randomUUID().toString(), treeDepth, null)); } else { fileList.add(new ItemData(ItemData.ITEM_TYPE_CHILD, child .getName(), child.getAbsolutePath(), UUID .randomUUID().toString(), treeDepth, null)); } } Collections.sort(list); Collections.sort(fileList); list.addAll(fileList); return list; } catch (Exception e) { } return null; } /** * 从position开始删除,删除 * * @param position * @param itemCount * 删除的数目 */ protected void removeAll(int position, int itemCount) { for (int i = 0; i < itemCount; i++) { mDataSet.remove(position); } notifyItemRangeRemoved(position, itemCount); } protected int getCurrentPosition(String uuid) { for (int i = 0; i < mDataSet.size(); i++) { if (uuid.equalsIgnoreCase(mDataSet.get(i).getUuid())) { return i; } } return -1; } @Override public int getItemViewType(int position) { return mDataSet.get(position).getType(); } public void add(ItemData text, int position) { mDataSet.add(position, text); notifyItemInserted(position); } public void addAll(List<ItemData> list, int position) { mDataSet.addAll(position, list); notifyItemRangeInserted(position, list.size()); }}
- 在MainActivity中调用
由于使用的是RecyclerView,在动态添加和删除孩子节点时,会有明显的“展开”和“收起”效果。
/** * @Author Zheng Haibo * @PersonalWebsite http://www.mobctrl.net * @Description */public class MainActivity extends Activity { private RecyclerView recyclerView; private RecyclerAdapter myAdapter; private LinearLayoutManager linearLayoutManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); recyclerView = (RecyclerView) findViewById(R.id.recycler_view); linearLayoutManager = new LinearLayoutManager(this); recyclerView.setLayoutManager(linearLayoutManager); recyclerView.getItemAnimator().setAddDuration(100); recyclerView.getItemAnimator().setRemoveDuration(100); recyclerView.getItemAnimator().setMoveDuration(200); recyclerView.getItemAnimator().setChangeDuration(100); myAdapter = new RecyclerAdapter(this); recyclerView.setAdapter(myAdapter); myAdapter.setOnScrollToListener(new OnScrollToListener() { @Override public void scrollTo(int position) { recyclerView.scrollToPosition(position); } }); initDatas(); } private void initDatas() { List<ItemData> list = myAdapter.getChildrenByPath("/", 0); myAdapter.addAll(list, 0); }}
Project
Demo的Github地址:https://github.com/nuptboyzhb/TreeRecyclerView
@Author: Zheng Haibo 莫川
2 0
- 使用RecyclerView写树形结构的TreeRecyclerView
- 一个使用RecyclerView写的树结构效果:TreeRecyclerView
- 树形结构的使用
- 【Ruby】使用Ruby写树形结构
- Android一个RecyclerView实现三级、多级列表(TreeRecyclerView)
- 树形结构--zTree的使用介绍
- 使用递归的方式显示树形结构
- javascript写的工作回溯——树形结构
- javascript如何用递归写一个简单的树形结构
- javascript如何用递归写一个简单的树形结构
- 树形结构的实现
- 树形结构的实现
- 树形结构的定位
- 伟大的树形结构
- 树形结构的cms
- 树形结构的实现
- js的 树形结构
- 使用递归制作树形结构
- SOSO问问
- 3分钟秒懂 物理学家进一步逼近绝对零度
- 怎么才能成为Java架构师
- 继承、封装、多态
- 人脸识别特征脸提取PCA算法
- 使用RecyclerView写树形结构的TreeRecyclerView
- Linux下搭建Android开发环境
- 地球人看不到的奇观:夜空同现三个月亮
- Python - 杨辉三角
- 利用AssetsManager实现在线更新脚本文件lua、js、图片等资源(免去平台审核周期)
- 常量指针与指针常量的区别(转帖)
- SAT历年真题之:作文题目汇总(四)
- spring mvc接收json参数
- WiFi 802.11 a b g n ac标准总结