收藏本站 收藏本站
积木网首页 - 软件测试 - 常用手册 - 站长工具 - 技术社区
首页 > Android > Android基础 > 正文

首页 - PHP - 数据库 - 操作系统 - 游戏开发 - JS - Android - MySql - Redis - MongoDB - Win8 - Shell编程 - DOS命令 - jQuery - CSS样式 - Python - Perl

Access - Oracle - DB2 - SQLServer - MsSql2008 - MsSql2005 - Sqlite - PostgreSQL - node.js - extjs - JavaScript vbs - Powershell - Ruby

使用RecyclerView写树形结构的TreeRecyclerView

简介

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

版权声明:本文为博主原创文章,未经博主允许不得转载。

android:ViewPager与FragmentPagerAdapter
关键点ViewPager的滑动监听,自动定时滑动,滑动时间的设置。CodeMainActivitypackagenet.mobctrl.viewpager;importjava.lang.reflect.Field;importandroid.os.Bundle;importandroid.os.Han

Android:跟手滑动的布局ViewGroup
跟手滑动很多开发者对布局的跟手滑动不太了解,在此就举一个例子,看一个RelativeLayout的滑动显示原理无论是跟手滑动,还是弹入弹出动画,本质上都

Android:自定义控件的一些注意点
自定义控件的几个注意点如果自定义View需要设置一个不变的背景图,为了提高效率,你可以在构造方法中直接设置背景图片。无需在onDraw中绘制Bitmapback

本周排行

更新排行

强悍的草根IT技术社区,这里应该有您想要的! 友情链接:b2b电子商务
Copyright © 2010 Gimoo.Net. All Rights Rreserved  京ICP备05050695号