为了账号安全,请及时绑定邮箱和手机立即绑定

【Android】自定义树形控件

标签:
Android

2016年11月13日 第二篇
Android自定义树形控件

注:根据鸿洋Android自定义任意层级树形控件 编写
效果图
图片描述


节点类(Node.java)

package jfsl.treeviewdemo.utils;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by JFSL on 2016/9/1 15:27.
 */
public class Node
{
    private int id;
    /**
     * 父节点
     */
    private int pId;
    /**
     * 显示的文字内容
     */
    private String name;
    /**
     * 层级
     */
    private int level;
    /**
     * 是否展开
     */
    private boolean isExpand;

    private int iconId;
    /**
     * 父节点
     */
    private Node parent;
    /**
     * 子节点
     */
    private List<Node> children = new ArrayList<Node>();

    public Node(int id,int pId,String name)
    {
        this.id = id;
        this.pId = pId;
        this.name = name;
    }

    public int getId()
    {
        return id;
    }

    public void setId(int id)
    {
        this.id = id;
    }

    public int getpId()
    {
        return pId;
    }

    public void setpId(int pId)
    {
        this.pId = pId;
    }

    public String getName()
    {
        return name;
    }

    public void setName(String name)
    {
        this.name = name;
    }

    /**
     * 获取层级
     * @return
     */
    public int getLevel()
    {
        return parent == null ? 0 : parent.getLevel() + 1;
    }

    public void setLevel(int level)
    {
        this.level = level;
    }

    public boolean isExpand()
    {
        return isExpand;
    }

    /**
     * 设置收缩状态
     * @param expand
     */
    public void setExpand(boolean expand)
    {
        isExpand = expand;
        //设置收缩状态
        if(!isExpand)
        {
            //所有子节点都设置成false
            for(Node node : children)
            {
                node.setExpand(isExpand);
            }
        }
    }

    public int getIconId()
    {
        return iconId;
    }

    public void setIconId(int iconId)
    {
        this.iconId = iconId;
    }

    public Node getParent()
    {
        return parent;
    }

    public void setParent(Node parent)
    {
        this.parent = parent;
    }

    public List<Node> getChildren()
    {
        return children;
    }

    public void setChildren(List<Node> children)
    {
        this.children = children;
    }

    /**
     * 判断是否是根节点
     */
    public boolean isRoot()
    {
        return parent == null;
    }

    /**
     * 判断父节点的展开状态
     */
    public boolean isParentExpand()
    {
        //根节点
        if(parent == null)
            return false;

        return parent.isExpand();
    }
    /**
     * 是否是叶子结点
     */
    public boolean isLeaf()
    {
        return children.size() == 0;
    }

}

节点辅助类(TreeViewHelper.java)

package jfsl.treeviewdemo.utils;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import jfsl.treeviewdemo.R;
import jfsl.treeviewdemo.annotation.TreeNodeId;
import jfsl.treeviewdemo.annotation.TreeNodeLabel;
import jfsl.treeviewdemo.annotation.TreeNodeParentId;

/**
 * Created by JFSL on 2016/9/1 15:25.
 *
 */
public class TreeViewHelper
{

    public static final int ICON_NONE = - 1;
    public static final int CURRENT_LEVEL = 1;

    /**
     * 转换数据
     * @param datas
     * @param <T>
     * @return
     * @throws IllegalAccessException
     */
    public static <T> List<Node> convertDatasToNodes(List<T> datas) throws IllegalAccessException
    {
        List<Node> nodes = new ArrayList<>();
        Node node = null;
        //遍历数据
        for(T t : datas)
        {
            int id = - 1;
            int pId = - 1;
            String label = null;
            /**
             * 反射+注解  获取成员变量的值
             */
            Class clazz = t.getClass();
            Field[] fields = clazz.getDeclaredFields();
            //反射获取值
            for(Field field : fields)
            {
                //获取id
                if(field.getAnnotation(TreeNodeId.class) != null)
                {
                    //设置可见
                    field.setAccessible(true);
                    id = field.getInt(t);
                }
                //获取pId
                if(field.getAnnotation(TreeNodeParentId.class) != null)
                {
                    //设置可见
                    field.setAccessible(true);
                    pId = field.getInt(t);
                }
                //获取label
                if(field.getAnnotation(TreeNodeLabel.class) != null)
                {
                    //设置可见
                    field.setAccessible(true);
                    label = (String)field.get(t);
                }

            }
            //加入节点
            node = new Node(id,pId,label);
            nodes.add(node);
        }
        //设置节点之间的关系
        settingNodeRelation(nodes);
        //设置图标状态
        settingNodeIcon(nodes);

        return nodes;
    }

    /**
     * 设置节点之间的关系
     * @param nodes
     */
    private static void settingNodeRelation(List<Node> nodes)
    {
        //设置关系
        for(int i = 0;i < nodes.size();i++)
        {
            Node n = nodes.get(i);
            for(int j = i + 1;j < nodes.size();j++)
            {
                Node m = nodes.get(j);
                //n的父节点的id等于m节点的id;也就是m节点是n的父节点
                if(n.getpId() == m.getId())
                {
                    m.getChildren().add(n);
                    n.setParent(m);
                }
                //同理
                else if(m.getpId() == n.getId())
                {
                    n.getChildren().add(m);
                    m.setParent(n);
                }
            }
        }
    }
    /**
     * 设置图标状态
     * @param nodes
     */
    private static void settingNodeIcon(List<Node> nodes)
    {
        for(Node no : nodes)
        {
            setNodeIcon(no);
        }
    }
    /**
     * 设置节点图标
     *
     * @param node
     */
    public static void setNodeIcon(Node node)
    {
        //有子节点
        if(node.getChildren().size() > 0)
        {
            //展开
            if(node.isExpand())
            {
                node.setIconId(R.mipmap.icon_expand);
                return;
            }
            //收缩
            node.setIconId(R.mipmap.icon_collapse);

        }
        //没有图标
        else
            node.setIconId(ICON_NONE);
    }

    /**
     * 获取排序后的节点
     *
     * @param datas
     * @param <T>
     * @return
     */
    public static <T> List<Node> getSortedNodes(List<T> datas,int defaultLevel) throws IllegalAccessException
    {
        //已经排序后的节点
        List<Node> newNodes = new ArrayList<>();
        //未排序的节点,只是转换的
        List<Node> oldNodes = convertDatasToNodes(datas);
        /**
         * 类似于采用深度遍历树
         * PS:另外的方法,前序遍历树
         */
        //***********************************************************
        //获取根节点
        List<Node> rootNodes = getRootNodes(oldNodes);
        for(Node node : rootNodes)
        {
            //默认级别为1
            addNode(newNodes,node,defaultLevel,CURRENT_LEVEL);
        }
        //***********************************************************
        return newNodes;
    }

    /**
     * 深度遍历,并添加
     *
     * @param newNodes
     * @param node
     * @param defaultLevel
     * @param currentLevel
     */
    private static void addNode(List<Node> newNodes,Node node,int defaultLevel,int currentLevel)
    {
        //添加节点
        newNodes.add(node);
        //设置展开的级别
        if(defaultLevel >= currentLevel)
        {
            node.setExpand(true);
        }
        //叶子节点,也就是最后了,不用再遍历
        if(node.isLeaf())
            return;
        //遍历子节点
        for(int i = 0;i < node.getChildren().size();i++)
        {
            addNode(newNodes,node.getChildren().get(i),defaultLevel,currentLevel + 1);
        }

    }

    /**
     * 过滤出需要显示的节点
     */
    public static List<Node> filterVisibleNodes(List<Node> nodes)
    {

        List<Node> visibleNodes = new ArrayList<>();

        for(Node node : nodes)
        {
            if(node.isRoot() || node.isParentExpand())
            {
                //设置节点的图标
                setNodeIcon(node);

                visibleNodes.add(node);
            }
        }

        return visibleNodes;
    }

    /**
     * 获取根节点
     *
     * @param oldNodes
     * @return
     */
    private static List<Node> getRootNodes(List<Node> oldNodes)
    {
        List<Node> rootNode = new ArrayList<>();
        for(Node node : oldNodes)
        {
            if(node.isRoot())
            {
                rootNode.add(node);
            }
        }
        return rootNode;
    }

}

文件Bean类,测试的数据类(FileBean.java)

package jfsl.treeviewdemo.bean;

import jfsl.treeviewdemo.annotation.TreeNodeId;
import jfsl.treeviewdemo.annotation.TreeNodeLabel;
import jfsl.treeviewdemo.annotation.TreeNodeParentId;

/**
 * Created by JFSL on 2016/9/1 15:22.
 */
public class FileBean
{
    @TreeNodeId
    private int id;
    @TreeNodeParentId
    private int pId;
    @TreeNodeLabel
    private String name;

    public FileBean(int id,int pId,String name)
    {
        this.id = id;
        this.pId = pId;
        this.name = name;
    }

    public int getId()
    {
        return id;
    }

    public void setId(int id)
    {
        this.id = id;
    }

    public String getName()
    {
        return name;
    }

    public void setName(String name)
    {
        this.name = name;
    }

    @Override
    public String toString()
    {
        return "FileBean{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

注解接口
1.TreeNodeId

package jfsl.treeviewdemo.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TreeNodeId
{
}

2.TreeNodeLabel

package jfsl.treeviewdemo.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TreeNodeLabel
{
}

3.TreeNodeParentId

package jfsl.treeviewdemo.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TreeNodeParentId
{
}

1.数据适配器(父类)(TreeViewAdapter.java)

package jfsl.treeviewdemo.adapter;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ListView;

import java.util.List;

import jfsl.treeviewdemo.utils.Node;
import jfsl.treeviewdemo.utils.TreeViewHelper;

public abstract class TreeViewAdapter<T> extends BaseAdapter
{
    //上下文
    protected Context mContext;
    //所有的节点
    protected List<Node> mAllNodes;
    //显示的节点
    protected List<Node> mVisibleNodes;
    //加载布局
    protected LayoutInflater mInflater;
    //ListView
    protected ListView mListTree;

    /**
     * 自定义回调接口
     */
    private OnTreeNodeClickListener mListener;
    public interface OnTreeNodeClickListener
    {
        void onClick(Node node,int position);
    }

    public void setListener(OnTreeNodeClickListener listener)
    {
        mListener = listener;
    }

    public TreeViewAdapter(Context context,ListView listTree ,List<T> datas,int defaultLevel) throws IllegalAccessException
    {
        mContext = context;

        mAllNodes = TreeViewHelper.getSortedNodes(datas,defaultLevel);
        mVisibleNodes = TreeViewHelper.filterVisibleNodes(mAllNodes);
        mInflater = LayoutInflater.from(mContext);

        mListTree = listTree;
        //设置ListView的点击事件
        mListTree.setOnItemClickListener(new AdapterView.OnItemClickListener()
        {

            @Override
            public void onItemClick(AdapterView<?> adapterView,View view,int position,long id)
            {
                //展开或者收缩
                expandOrCollapse(position);
                //事件回调
                if(mListener != null)
                {
                    mListener.onClick(mVisibleNodes.get(position),position);
                }
            }
        });
    }

    /**
     * 展开或者收缩
     * @param position
     */
    private void expandOrCollapse(int position)
    {
        Node node = mVisibleNodes.get(position);
        if(node != null)
        {
            if(node.isLeaf())
                return;
            //反向选择
            node.setExpand(!node.isExpand());
            //更新数据
            mVisibleNodes = TreeViewHelper.filterVisibleNodes(mAllNodes);
            notifyDataSetChanged();

        }
    }
    @Override
    public int getCount()
    {
        return mVisibleNodes.size();
    }

    @Override
    public Object getItem(int position)
    {
        return mVisibleNodes.get(position);
    }

    @Override
    public long getItemId(int position)
    {
        return position;
    }

    @Override
    public View getView(int position,View view,ViewGroup viewGroup)
    {
        Node node = mVisibleNodes.get(position);
        view = getConvertView(node,position,view,viewGroup);
        //设置距离问题
        view.setPadding(node.getLevel() * 70,3,3,3);
        return view;
    }

    public abstract View getConvertView(Node node,int position,View view,ViewGroup viewGroup);
}

2.树形数据适配器(SimpleTreeAdapter.java)

package jfsl.treeviewdemo.adapter;

import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;

import java.util.List;

import jfsl.treeviewdemo.R;
import jfsl.treeviewdemo.utils.Node;

/**
 * Created by JFSL on 2016/9/1 20:36.
 * 自定义Item的界面
 * 可以根据情况修改
 */
public class SimpleTreeAdapter<T> extends TreeViewAdapter<T>
{
    public SimpleTreeAdapter(Context context,ListView listTree,List<T> datas,int defaultLevel) throws IllegalAccessException
    {
        super(context,listTree,datas,defaultLevel);
    }

    /**
     * 主要用来自定义界面
     * @param node
     * @param position
     * @param view
     * @param viewGroup
     * @return
     */
    @Override
    public View getConvertView(Node node,int position,View view,ViewGroup viewGroup)
    {
        ViewHolder holder = null;
        if(view == null)
        {
            view = mInflater.inflate(R.layout.listview_item_treeview,viewGroup,false);
            holder = new ViewHolder();
            holder.icon = (ImageView)view.findViewById(R.id.id_item_icon);
            holder.text = (TextView)view.findViewById(R.id.id_item_text);

            view.setTag(holder);
        } else
        {
            holder = (ViewHolder)view.getTag();

        }
        //没有图标,也就是没有子节点的数据项
        if(node.getIconId() == - 1)
        {
            holder.icon.setVisibility(View.INVISIBLE);
        }
        //有图标
        else
        {
            holder.icon.setVisibility(View.VISIBLE);
            holder.icon.setImageResource(node.getIconId());
        }
        holder.text.setText(node.getName());
        return view;
    }

    private class ViewHolder
    {
        ImageView icon;
        TextView text;
    }
}

测试的Activity

package jfsl.treeviewdemo;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ListView;

import java.util.ArrayList;
import java.util.List;

import jfsl.treeviewdemo.adapter.SimpleTreeAdapter;
import jfsl.treeviewdemo.bean.FileBean;

/**
 * 主界面
 * @date 2016年9月1日 21:27:11
 * @author JFSL
 *
 */
public class ActivityMain extends AppCompatActivity
{
    //
    private ListView mListView;
    //数据集
    private List<FileBean> mDatas;
    //适配器
    private SimpleTreeAdapter<FileBean> mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        try
        {
            mListView = (ListView)findViewById(R.id.id_listview);
            //初始化模拟数据
            initTestDatas();
            //初始化适配器  默认显示层级 0
            mAdapter = new SimpleTreeAdapter<FileBean>(this,mListView,mDatas,0);
            //绑定适配器
            mListView.setAdapter(mAdapter);
        } catch(IllegalAccessException e)
        {
        }

    }

    /**
     * 初始化模拟数据
     */
    private void initTestDatas()
    {
        mDatas = new ArrayList<>();
        FileBean bean = new FileBean(1,0,"xx小学");
        mDatas.add(bean);
        bean = new FileBean(2,1,"一(1)班");
        mDatas.add(bean);

        bean = new FileBean(201,2,"Android学习");
        mDatas.add(bean);
        bean = new FileBean(211,2,"Java技术");
        mDatas.add(bean);
        bean = new FileBean(221,2,"计算机网络");
        mDatas.add(bean);

        bean = new FileBean(8,0,"xx中学");
        mDatas.add(bean);
        bean = new FileBean(11,8,"九(2)班");
        mDatas.add(bean);

        bean = new FileBean(12,0,"xx中学");
        mDatas.add(bean);
        bean = new FileBean(13,12,"高一(16)班");
        mDatas.add(bean);
        bean = new FileBean(14,12,"高二(13)班");
        mDatas.add(bean);
        bean = new FileBean(15,12,"高三(11)班");
        mDatas.add(bean);

        bean = new FileBean(16,0,"XXXXXX");
        mDatas.add(bean);
        bean = new FileBean(17,16,"大一");
        mDatas.add(bean);
        bean = new FileBean(18,16,"大二");
        mDatas.add(bean);
        bean = new FileBean(19,16,"大三");
        mDatas.add(bean);
        bean = new FileBean(20,19,"Android学习");
        mDatas.add(bean);
        bean = new FileBean(21,19,"Java技术");
        mDatas.add(bean);
        bean = new FileBean(22,21,"计算机网络");
        mDatas.add(bean);
    }
}

1.activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ListView
        android:id="@+id/id_listview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</LinearLayout>

2.listview_item_treeview.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="56dp">
    <ImageView
        android:id="@+id/id_item_icon"
        android:layout_width="56dp"
        android:scaleType="center"
        android:class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="@mipmap/ic_launcher"
        android:layout_height="match_parent"/>
    <TextView
        android:id="@+id/id_item_text"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center_vertical"
        android:layout_marginLeft="4dp"/>
</LinearLayout>

代码地址 http://114.215.186.20/14551100130/treeviewdemo.zip


点击查看更多内容
2人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
JAVA开发工程师
手记
粉丝
14
获赞与收藏
93

关注作者,订阅最新文章

阅读免费教程

感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消