【Android】自定義樹形控制元件

farsun發表於2021-09-09

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 children = new ArrayList();

    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 getChildren()
    {
        return children;
    }

    public void setChildren(List 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 
     * @return
     * @throws IllegalAccessException
     */
    public static  List convertDatasToNodes(List datas) throws IllegalAccessException
    {
        List 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 nodes)
    {
        //設定關係
        for(int i = 0;i  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 
     * @return
     */
    public static  List getSortedNodes(List datas,int defaultLevel) throws IllegalAccessException
    {
        //已經排序後的節點
        List newNodes = new ArrayList();
        //未排序的節點,只是轉換的
        List oldNodes = convertDatasToNodes(datas);
        /**
         * 類似於採用深度遍歷樹
         * PS:另外的方法,前序遍歷樹
         */
        //***********************************************************
        //獲取根節點
        List 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 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  filterVisibleNodes(List nodes)
    {

        List 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 getRootNodes(List oldNodes)
    {
        List 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 extends BaseAdapter
{
    //上下文
    protected Context mContext;
    //所有的節點
    protected List mAllNodes;
    //顯示的節點
    protected List 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 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 extends TreeViewAdapter
{
    public SimpleTreeAdapter(Context context,ListView listTree,List 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 mDatas;
    //介面卡
    private SimpleTreeAdapter 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(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

2.listview_item_treeview.xml


程式碼地址


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4289/viewspace-2798356/,如需轉載,請註明出處,否則將追究法律責任。

相關文章