zoukankan      html  css  js  c++  java
  • 使用RecyclerView写树形结构的TreeRecyclerView

    简单介绍

    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 莫川

  • 相关阅读:
    应用上架前如何知道自己应用的下载地址?
    Multi-line NSAttributedString with truncated text
    Adding AirDrop File Sharing Feature to Your iOS Apps
    Add sharing to your app via UIActivityViewController
    [原]iOS自带社会化分享框架——Social.framework
    xcode 制作静态库.a文件 详解
    Fiddler怎么对IPhone手机的数据进行抓包分析
    Mac上的抓包工具Charles
    30、准确计算CoreText高度的方法
    keil MDK中如何生成*.bin格式的文件
  • 原文地址:https://www.cnblogs.com/zfyouxi/p/5345066.html
Copyright © 2011-2022 走看看