使用RecyclerView写树形结构的TreeRecyclerView

来源:互联网 发布:火星漫游车的人工智能 编辑:程序博客网 时间:2024/06/05 23:55

简介

android是不提供树形控件的,如果需要使用树形控件,我们应该怎么做呢?
先看效果
GIF
上图是一个明显的树形结构

实现原理

在逻辑上,它们是包含关系,数据结构上是多叉树,这是毋庸置疑的。但是,显示的时候,我们有必要嵌套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
原创粉丝点击