android多级树形结构列表(完整代码,可以选择某项)

多级列表是个很常见的功能,发现很多代码都不好扩展,或者由于数据结构设计不好,导致开发维护比较费时间。
11月14号更新:增加了 选择按钮,可以实现选中效果。用于选择。
下面自己写了一个,github链接地址: github地址分享一波。如图所示
示例图
看一下节点的代码,最重要的设计TreePoint

public class TreePoint {
    private String ID;        // 7241,          //账号id
    private String NNAME;     //名称
    private String PARENTID;   // 0,           //父id     0表示根节点
    private String ISLEAF;     //0,            //是否是叶子节点   1为叶子节点
    private int DISPLAY_ORDER; // 1       //同一个级别的显示顺序
    private boolean isExpand = false;  //是否展开了
}

先看下MainActivity

public class MainActivity extends AppCompatActivity {

    private ReasonAdapter adapter;
    private ListView listView;
    private EditText et_filter;
    private List<TreePoint> reasonPointList = new ArrayList<>();
    private HashMap<String, TreePoint> reasonMap = new HashMap<>();


    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView();
        init();
        addListener();
    }


    public void setContentView() {
        setContentView(R.layout.activity_main);
    }


    public void init() {
        adapter = new ReasonAdapter(this, reasonPointList, reasonMap);
        listView =  findViewById(R.id.listView);
        listView.setAdapter(adapter);
        et_filter =  findViewById(R.id.et_filter);
        initData();
    }
    //初始化数据
    //数据特点:TreePoint 之间的关系特点   id是任意唯一的。    如果为根节点 PARENTID  为"0"   如果没有子节点,也就是本身是叶子节点的时候ISLEAF = "1"
    //  DISPLAY_ORDER 是同一级中 显示的顺序
    //如果需要做选中 单选或者多选,只需要给TreePoint增加一个选中的属性,在ReasonAdapter中处理就好了
    private void initData() {
        reasonPointList.clear();
        int id =1000;
        int parentId = 0;
        int parentId2 = 0;
        int parentId3 = 0;
        for(int i=1;i<5;i++){
            id++;
            reasonPointList.add(new TreePoint(""+id,"分类"+i,"" + parentId,"0",i));
            for(int j=1;j<5;j++){
                if(j==1){
                    parentId2 = id;
                }
                id++;
                reasonPointList.add(new TreePoint(""+id,"分类"+i+"_"+j,""+parentId2,"0",j));
                for(int k=1;k<5;k++){
                    if(k==1){
                         parentId3 = id;
                    }
                    id++;
                    reasonPointList.add(new TreePoint(""+id,"分类"+i+"_"+j+"_"+k,""+parentId3,"1",k));
                }
            }
        }
		//这里打乱数据的顺序,模拟数据乱序,重新排序
        Collections.shuffle(reasonPointList);
        updateData();

    }


    public void addListener() {

        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                adapter.onItemClick(position);
            }
        });

        et_filter.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {

            }

            @Override
            public void afterTextChanged(Editable s) {
                searchAdapter(s);
            }
        });
    }

    private void searchAdapter(Editable s) {
        adapter.setKeyword(s.toString());
    }

    //对数据排序 深度优先
    private void updateData() {
    //这里比较重要的是 reasonPointList 这个集合中的内容又使用 reasonMap这个HashMap保存起来,主要是为了利用ArrayList遍历快,HashMap查找快的特征。提高代码的执行效率。
        for (TreePoint reasonTreePoint : reasonPointList) {
            reasonMap.put(reasonTreePoint.getID(), reasonTreePoint);
        }
        Collections.sort(reasonPointList, new Comparator<TreePoint>() {
            @Override
            public int compare(TreePoint lhs, TreePoint rhs) {
                int llevel = TreeUtils.getLevel(lhs, reasonMap);
                int rlevel = TreeUtils.getLevel(rhs, reasonMap);
                if (llevel == rlevel) {
                    if (lhs.getPARENTID().equals(rhs.getPARENTID())) {  //左边小
                        return lhs.getDISPLAY_ORDER() > rhs.getDISPLAY_ORDER() ? 1 : -1;
                    } else {  //如果父辈id不相等
                        //同一级别,不同父辈
                        TreePoint lreasonTreePoint = TreeUtils.getTreePoint(lhs.getPARENTID(), reasonMap);
                        TreePoint rreasonTreePoint = TreeUtils.getTreePoint(rhs.getPARENTID(), reasonMap);
                        return compare(lreasonTreePoint, rreasonTreePoint);  //父辈
                    }
                } else {  //不同级别
                    if (llevel > rlevel) {   //左边级别大       左边小
                        if (lhs.getPARENTID().equals(rhs.getID())) {
                            return 1;
                        } else {
                            TreePoint lreasonTreePoint = TreeUtils.getTreePoint(lhs.getPARENTID(), reasonMap);
                            return compare(lreasonTreePoint, rhs);
                        }
                    } else {   //右边级别大   右边小
                        if (rhs.getPARENTID().equals(lhs.getID())) {
                            return -1;
                        }
                        TreePoint rreasonTreePoint = TreeUtils.getTreePoint(rhs.getPARENTID(), reasonMap);
                        return compare(lhs, rreasonTreePoint);
                    }
                }
            }
        });
        adapter.notifyDataSetChanged();
    }
}

适配器 中通过缩进控制不同的级别

public class ReasonAdapter extends BaseAdapter {
    private Context mcontext;
    private Activity mActivity;
    private List<TreePoint> reasonPointList;
    private String keyword = "";
    private HashMap<String, TreePoint> reasonMap = new HashMap<>();


    public ReasonAdapter(final Context mcontext, List<TreePoint> reasonPointList, HashMap<String, TreePoint> reasonMap) {
        this.mcontext = mcontext;
        this.mActivity = (Activity) mcontext;
        this.reasonPointList = reasonPointList;
        this.reasonMap = reasonMap;
    }


    public void setKeyword(String keyword) {
        this.keyword = keyword;
        //全部闭合,然后展开
        for (TreePoint reasonTreePoint : reasonPointList) {
            reasonTreePoint.setExpand(false);
        }
        if (!"".equals(keyword)) {
            for (TreePoint reasonTreePoint : reasonPointList) {
                if (reasonTreePoint.getNNAME().contains(keyword)) {
                    //含有keyword
                    if ("0".equals(reasonTreePoint.getISLEAF())) {
                        reasonTreePoint.setExpand(true);
                    }
                    //打开所有的父级元素
                    openExpand(reasonTreePoint);
                }
            }
        }
        this.notifyDataSetChanged();

    }

    private void openExpand(TreePoint reasonTreePoint) {
        if ("0".equals(reasonTreePoint.getPARENTID())) {
            reasonTreePoint.setExpand(true);
        } else {
            reasonMap.get(reasonTreePoint.getPARENTID()).setExpand(true);
            openExpand(reasonMap.get(reasonTreePoint.getPARENTID()));
        }
    }


    //第一要准确计算数量
    @Override
    public int getCount() {
        int count = 0;
        for (TreePoint tempPoint : reasonPointList) {
            if ("0".equals(tempPoint.getPARENTID())) {
                count++;
            } else {
                if (getItemIsExpand(tempPoint.getPARENTID())) {
                    count++;
                }
            }
        }
        return count;
    }

    private boolean getItemIsExpand(String ID) {
        for (TreePoint tempPoint : reasonPointList) {
            if (ID.equals(tempPoint.getID())) {
                if (tempPoint.isExpand()) {
                    return true;
                } else {
                    return false;
                }
            }
        }
        return false;
    }

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

    private int convertPostion(int position) {
        int count = 0;
        for (int i = 0; i < reasonPointList.size(); i++) {
            TreePoint treePoint = reasonPointList.get(i);
            if ("0".equals(treePoint.getPARENTID())) {
                count++;
            } else {
                if (getItemIsExpand(treePoint.getPARENTID())) {
                    count++;
                }
            }
            if (position == (count - 1)) {
                return i;
            }
        }
        return 0;
    }

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


    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder;
        if (convertView == null) {
            convertView = LayoutInflater.from(mcontext).inflate(R.layout.adapter_treeview, null);
            holder = new ViewHolder();
            holder.text = (TextView) convertView.findViewById(R.id.text);
            holder.icon = (ImageView) convertView.findViewById(R.id.icon);
            holder.contactitemBtn = (ImageButton) convertView.findViewById(R.id.contactitem_sendmsg);
            holder.contactitemBtn.setVisibility(View.GONE);
            convertView.setTag(holder);
            convertView.setBackgroundColor(0xffffffff);
            convertView.setPadding(DensityUtil.dip2px(mcontext, 10), DensityUtil.dip2px(mcontext, 10), DensityUtil.dip2px(mcontext, 10), DensityUtil.dip2px(mcontext, 10));
        } else {
            holder = (ViewHolder) convertView.getTag();
        }
        TreePoint tempPoint = (TreePoint) getItem(position);

        int level = TreeUtils.getLevel(tempPoint, reasonMap);
        holder.icon.setPadding(25 * level, holder.icon.getPaddingTop(), 0, holder.icon.getPaddingBottom());
        if ("0".equals(tempPoint.getISLEAF())) {
            if (tempPoint.isExpand() == false) {
                holder.icon.setVisibility(View.VISIBLE);
                holder.icon.setImageResource(R.drawable.outline_list_collapse);
            } else {
                holder.icon.setVisibility(View.VISIBLE);
                holder.icon.setImageResource(R.drawable.outline_list_expand);
            }
        } else {
            holder.icon.setVisibility(View.INVISIBLE);
            holder.icon.setImageResource(R.drawable.outline_list_collapse);
        }

        if (keyword != null && !"".equals(keyword) && tempPoint.getNNAME().contains(keyword)) {
            int index = tempPoint.getNNAME().indexOf(keyword);
            int len = keyword.length();
            Spanned temp = Html.fromHtml(tempPoint.getNNAME().substring(0, index)
                    + "<font color=#FF0000>"
                    + tempPoint.getNNAME().substring(index, index + len) + "</font>"
                    + tempPoint.getNNAME().substring(index + len, tempPoint.getNNAME().length()));

            holder.text.setText(temp);
        } else {
            holder.text.setText(tempPoint.getNNAME());
        }
        holder.text.setCompoundDrawablePadding(DensityUtil.dip2px(mcontext, 10));

        holder.contactitemBtn.setSelected(false);
        if (tempPoint.isExpand())
            holder.contactitemBtn.setSelected(true);
        return convertView;
    }

    public void onItemClick(int position) {
        TreePoint reasonTreePoint = (TreePoint) getItem(position);
        if ("1".equals(reasonTreePoint.getISLEAF())) {
            //处理回填
            Toast.makeText(mcontext, getSubmitResult(reasonTreePoint), Toast.LENGTH_SHORT).show();
        } else {  //如果点击的是父类
            if (reasonTreePoint.isExpand()) {
                for (TreePoint tempPoint : reasonPointList) {
                    if (tempPoint.getPARENTID().equals(reasonTreePoint.getID())) {
                        if ("0".equals(reasonTreePoint.getISLEAF())) {
                            tempPoint.setExpand(false);
                        }
                    }
                }
                reasonTreePoint.setExpand(false);
            } else {
                reasonTreePoint.setExpand(true);
            }
        }
        this.notifyDataSetChanged();
    }


    private String getSubmitResult(TreePoint reasonTreePoint) {
        StringBuilder sb = new StringBuilder();
        addResult(reasonTreePoint, sb);
        String result = sb.toString();
        if (result.endsWith("-")) {
            result = result.substring(0, result.length() - 1);
        }
        return result;
    }

    private void addResult(TreePoint reasonTreePoint, StringBuilder sb) {
        if (reasonTreePoint != null && sb != null) {
            sb.insert(0, reasonTreePoint.getNNAME() + "-");
            if (!"0".equals(reasonTreePoint.getPARENTID())) {
                addResult(reasonMap.get(reasonTreePoint.getPARENTID()), sb);
            }
        }
    }


    class ViewHolder {
        TextView text;
        ImageView icon;
        ImageButton contactitemBtn;   //这是个选择的按钮,可以自己适当修改代码实现功能
    }
}

工具类  获取层级,其实这里可以将TreePoint增加一个层级属性,如果层级属性为空,就重新计算,如果不为空,就直接使用,避免滚动listView的时候多次计算。
public class TreeUtils {
    //第一级别为0
    public static int getLevel(TreePoint treePoint,HashMap<String,TreePoint> map){
        if("0".equals(treePoint.getPARENTID())){
            return 0;
        }else{
            return 1+getLevel(getTreePoint(treePoint.getPARENTID(),map),map);
        }
    }

    public static TreePoint getTreePoint(String ID, HashMap<String,TreePoint> map){
        if(map.containsKey(ID)){
            return map.get(ID);
        }
        Log.e("xlc","ID:" + ID);
        return null;
    }
}

以上大致就是所有的代码了。主要是为了实现多级列表。方便扩展。要保证效率。

快速实现Android多级树形列表,这个库是在鸿洋多级树形列表demo中修改而来。解决的问题:支持ID为int类型和String类型。支持多级复选框选中,使用只需一行代码。支持动态更新数据并保持原有展开/关闭状态。支持ListView、RecyclerView。USE我们关联列表树需要有三个必须元素,当前id、父级id即pid,显示的内容。id和pid可以为int或者String以及其他类型。要显示的内容需要包装一下://id pid name  FileNode为实际用的实体Bean对象 mlist.add(new Node("223","0","我也是添加的root节点",new FileNode()));对于ListView,需要继承自TreeListViewAdapter,如:public class SimpleTreeAdapter extends TreeListViewAdapter {     public SimpleTreeAdapter(ListView mTree, Context context, List datas, int defaultExpandLevel, int iconExpand, int iconNoExpand) {         super(mTree, context, datas, defaultExpandLevel, iconExpand, iconNoExpand);     }     public SimpleTreeAdapter(ListView mTree, Context context, List datas,                              int defaultExpandLevel) {         super(mTree, context, datas, defaultExpandLevel);     }     @Override     public View getConvertView(final Node node , int position, View convertView, ViewGroup parent)     {        final ViewHolder viewHolder ;         if (convertView == null) {             convertView = mInflater.inflate(R.layout.list_item, parent, false);             viewHolder = new ViewHolder();             viewHolder.cb = (CheckBox) convertView                     .findViewById(R.id.cb_select_tree);             viewHolder.label = (TextView) convertView                     .findViewById(R.id.id_treenode_label);             viewHolder.icon = (ImageView) convertView.findViewById(R.id.icon);             convertView.setTag(viewHolder);         } else {             viewHolder = (ViewHolder) convertView.getTag();         }         viewHolder.cb.setOnClickListener(new View.OnClickListener() {             @Override             public void onClick(View v) {                 setChecked(node,viewHolder.cb.isChecked());             }         });         if (node.isChecked()){             viewHolder.cb.setChecked(true);         }else {             viewHolder.cb.setChecked(false);         }         if (node.getIcon() == -1) {             viewHolder.icon.setVisibility(View.INVISIBLE);         } else {             viewHolder.icon.setVisibility(View.VISIBLE);             viewHolder.icon.setImageResource(node.getIcon());         }         viewHolder.label.setText(node.getName());         return convertView;     }     private final class ViewHolder     {         ImageView icon;         CheckBox cb;         TextView label;     } }对于RecyclerView,需继承自TreeRecyclerAdapter,如:public class SimpleTreeRecyclerAdapter extends TreeRecyclerAdapter {     public SimpleTreeRecyclerAdapter(RecyclerView mTree, Context context, List datas, int defaultExpandLevel, int iconExpand, int iconNoExpand) {         super(mTree, context, datas, defaultExpandLevel, iconExpand, iconNoExpand);     }     public SimpleTreeRecyclerAdapter(RecyclerView mTree, Context context, List datas, int defaultExpandLevel) {         super(mTree, context, datas, defaultExpandLevel);     }     @Override     public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {         return new MyHoder(View.inflate(mContext, R.layout.list_item,null));     }     @Override     public void onBindViewHolder(final Node node, RecyclerView.ViewHolder holder, int position) {         final MyHoder viewHolder = (MyHoder) holder;         //todo do something         viewHolder.cb.setOnClickListener(new View.OnClickListener() {             @Override             public void onClick(View v) {                 setChecked(node,viewHolder.cb.isChecked());             }         });         if (node.isChecked()){             viewHolder.cb.setChecked(true);         }else {             viewHolder.cb.setChecked(false);         }         if (node.getIcon() == -1) {             viewHolder.icon.setVisibility(View.INVISIBLE);         } else {             viewHolder.icon.setVisibility(View.VISIBLE);             viewHolder.icon.setImageResource(node.getIcon());         }         viewHolder.label.setText(node.getName());     }     class MyHoder extends RecyclerView.ViewHolder{         public CheckBox cb;         public TextView label;         public ImageView icon;         public MyHoder(View itemView) {             super(itemView);             cb = (CheckBox) itemView                     .findViewById(R.id.cb_select_tree);             label = (TextView) itemView                     .findViewById(R.id.id_treenode_label);             icon = (ImageView) itemView.findViewById(R.id.icon);         }     } }初始化: ListView://第一个参数  ListView //第二个参数  上下文 //第三个参数  数据集 //第四个参数  默认展开层级数 0为不展开 //第五个参数  展开的图标 //第六个参数  闭合的图标 mAdapter = new SimpleTreeAdapter(mTree, ListViewActivity.this,                         mDatas, 1,R.mipmap.tree_ex,R.mipmap.tree_ec); mTree.setAdapter(mAdapter);RecyclerView://第一个参数  RecyclerView //第二个参数  上下文 //第三个参数  数据集 //第四个参数  默认展开层级数 0为不展开 //第五个参数  展开的图标 //第六个参数  闭合的图标 mAdapter = new SimpleTreeRecyclerAdapter(mTree, RecyclerViewActivity.this,                 mDatas, 1,R.mipmap.tree_ex,R.mipmap.tree_ec); mTree.setAdapter(mAdapter);添加数据,可以保持原有选中或者展开状态:List mlist = new ArrayList(); mlist.add(new Node("223","0","我也是添加的root节点",new FileNode())); mAdapter.addData(0,mlist);获取选中内容:如果node的isChecked()为true,即为选中状态。StringBuilder sb = new StringBuilder(); //获取排序过的nodes //如果不需要刻意直接用 mDatas既可 final List allNodes = mAdapter.getAllNodes(); for (int i = 0; i < allNodes.size(); i ) {    if (allNodes.get(i).isChecked()){        sb.append(allNodes.get(i).getName() ",");    } } String strNodesName = sb.toString(); if (!TextUtils.isEmpty(strNodesName))    Toast.makeText(this, strNodesName.substring(0, strNodesName.length()-1),Toast.LENGTH_SHORT).show();控制父子之间联动的选中与取消状态,只需调用setChecked方法既可,注意如果在setOnCheckedChangeListener中处理会有问题:因为如果要子节点/父节点选中或者取消需要刷新页面,而刷新页面又会触发viewHolder.cb.setChecked(true/false);的判断从而又会进入setOnCheckedChangeListener,会导致如果父节点选中某些子节点取消不了的情况。//viewHolder.cb 为CheckBox  viewHolder.cb.setOnClickListener(new View.OnClickListener() {             @Override             public void onClick(View v) {                 setChecked(node,viewHolder.cb.isChecked());             }         });
评论 46
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值