Android RecyclerView实现树形列表

2023-11-04

  前段时间公司有个项目,需要展示客户关系的树形列表,当时网上找了一些资料,有些觉得挺复杂的,有些测试下来有bug。最终决定自己解决。

最底下有demo,需要源码的同学可以下载

效果图(带节点的展开与收缩,并且可以实现项的单选,选中项字体为蓝色):

  

 

一、实体类的构建

这个类不多解释,各个属性的含义都在注释上

/**
 * 公司类
 */
public class BaseCompany implements Serializable
{

    private static final long serialVersionUID = 1825913344212097269L;
    /**
     * 公司ID
     */
    private String companyID;
    /**
     * 公司名称
     */
    private String companyName;
    /**
     * 公司的层级:0,1,2,3...  最上级公司为0。UI需根据公司的层级缩进
     */
    private int companyLevel;
    /**
     * 上级公司的ID
     */
    private String parentCompanyID;
    /**
     * 是否有下级公司(默认没有),有子公司需展示 展开/收缩 的箭头
     */
    private boolean hasChildCompany;

    public BaseCompany(){
        hasChildCompany = false;
    }

    public String getCompanyID() {
        return companyID;
    }

    public void setCompanyID(String companyID) {
        this.companyID = companyID;
    }

    public String getCompanyName() {
        return companyName;
    }

    public void setCompanyName(String companyName) {
        this.companyName = companyName;
    }

    public int getCompanyLevel()
    {
        return companyLevel;
    }

    public void setCompanyLevel(int companyLevel)
    {
        this.companyLevel = companyLevel;
    }

    public String getParentCompanyID()
    {
        return parentCompanyID;
    }

    public void setParentCompanyID(String parentCompanyID)
    {
        this.parentCompanyID = parentCompanyID;
    }

    public boolean isHasChildCompany()
    {
        return hasChildCompany;
    }

    public void setHasChildCompany(boolean hasChildCompany)
    {
        this.hasChildCompany = hasChildCompany;
    }

    @Override
    public boolean equals(Object o)
    {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        BaseCompany that = (BaseCompany) o;
        return Objects.equals(companyID, that.companyID);
    }

    @Override
    public int hashCode()
    {
        return Objects.hash(companyID);
    }
}

二、服务端返回的JSON数据格式及解析

{
    "data":[
        {
            "child_customers":[
                {
                    "child_customers":[

                    ],
                    "identifier":"XXXX6-测试12",
                    "key":"0-1",
                    "name":"测试",
                    "superior_customer":"XXXX",
                    "superior_customer_identifier":"XXXX6"
                }
            ],
            "identifier":"XXXX6",
            "key":"0",
            "name":"XXXX",
            "superior_customer":"XXXX",
            "superior_customer_identifier":"XXXX6"
        }
    ],
    "error":0,
    "message":"成功"
}

这里简单说明,child_customers 字段下为子公司列表,identifier字段为id,key字段为web端借助使用实现树形的属性(Android端未使用),name为公司名称,superior_customer字段为上级客户名称,superior_customer_identifier字段为上级客户id。

关于这种不确定层级的解析,我用了递归的方法

下面代码第二行中的 “response 即为上面的JSON字符串,解析后的客户列表在第一行声明的变量 result 中。

List<BaseCompany> result = new ArrayList<>();
JSONObject object = JSON.parseObject(response);
int errorCode = object.getIntValue("error");
String message = object.getString("message");

if(message.equals("成功")){
    JSONArray data = object.getJSONArray("data");
    getCustomerList(result,data,0);

    MessageBean msg = new MessageBean(AppConstants.MessageWhat.GET_CUSTOMER_INFO_SUCCESS);
    msg.obj = result;
    EventBus.getDefault().post(msg);
}



//



    /**
     * 递归解析 客户列表
     * @param cuntomerList 用于存放解析后结果的客户列表
     * @param data  存放客户列表的JSONArray
     * @param level 解析的层级(即客户所处的层级),最上级为0
     * @return
     */
    private static int getCustomerList(List<BaseCompany> cuntomerList,JSONArray data,int level)
    {
        for(int i=0;i<data.size();i++)
        {
            JSONObject company = data.getJSONObject(i);
            BaseCompany baseCompany = new BaseCompany();
            baseCompany.setCompanyID(company.getString("identifier"));
            baseCompany.setCompanyName(company.getString("name"));
            baseCompany.setCompanyLevel(level);
            baseCompany.setParentCompanyID(company.getString("superior_customer_identifier"));
            cuntomerList.add(baseCompany);
            JSONArray childCompanies = company.getJSONArray("child_customers");
            int childCount = getCustomerList(cuntomerList,childCompanies,(level + 1));
            if(childCount == 0)
            {
                //没有子客户
                baseCompany.setHasChildCompany(false);
            } else
            {
                //有子客户
                baseCompany.setHasChildCompany(true);
            }
        }
        return data.size();
    }

三、RecyclerView的适配器,及项的布局

项的布局:item_monitor_list_company.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:background="@drawable/bg_custom_list_item"
    android:id="@+id/ll_item_monitor_company"
    android:gravity="center_vertical"
    android:layout_height="wrap_content">

    <ImageView
        android:id="@+id/iv_spread_level_0"
        android:layout_width="34dp"
        android:layout_height="30dp"
        android:padding="12dp"
        app:srcCompat="@drawable/icon_spread_gray" />

    <TextView
        android:id="@+id/tv_company_name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        style="@style/NormalText"
        android:layout_marginTop="8dp"
        android:layout_marginBottom="8dp"
        android:gravity="center_vertical"
        android:text="TextView" />
</LinearLayout>

适配器:

/**
 * 2019-05-05
 * 新版 客户列表的适配器<br/>
 * 带 选中项变色,带展开与收起
 */
public class MonitorListCustomerAdapter extends RecyclerView.Adapter<BaseViewHolder>
{
    private Context ctx;
    /**
     * 所有客户的列表
     */
    private List<BaseCompany> companyList;
    /**
     * 当前展示的客户列表(未处于收起状态)
     */
    private List<BaseCompany> customerShownList;
    /**
     * 当前选中的客户
     */
    private BaseCompany currentChosenCustomer;

    /**
     * 处于收缩状态的客户id
     * (由用户点击后触发)
     */
    private List<String> shrinkCustomerIdList;


    public MonitorListCustomerAdapter(Context ctx, List<BaseCompany> companyList)
    {
        this.ctx = ctx;
        this.companyList = companyList;
        customerShownList = new ArrayList<>(companyList.size());
        shrinkCustomerIdList = new ArrayList<>();
        //默认选中第一项
        if(companyList != null && companyList.size() > 0)
        {
            currentChosenCustomer = companyList.get(0);
        }
        initCustomerShownList();
    }

    @NonNull
    @Override
    public BaseViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
        return new BaseViewHolder(LayoutInflater.from(ctx).inflate(R.layout.item_monitor_list_company,viewGroup,false));
    }

    @Override
    public void onBindViewHolder(@NonNull BaseViewHolder baseViewHolder, int position)
    {
        final BaseCompany company = customerShownList.get(position);

        LinearLayout llItemMonitorCompany = baseViewHolder.getView(R.id.ll_item_monitor_company);
        TextView tvCompanyName = baseViewHolder.getView(R.id.tv_company_name);
        tvCompanyName.setText(company.getCompanyName());
        ImageView ivSpreadLevel0 = baseViewHolder.getView(R.id.iv_spread_level_0);
        //选中项修改底色
        if(currentChosenCustomer != null && company.equals(currentChosenCustomer))
        {
            tvCompanyName.setTextColor(ctx.getResources().getColor(R.color.theme_blue));
        } else
        {
            tvCompanyName.setTextColor(ctx.getResources().getColor(R.color.black));
        }

        //公司层级:0,1,2,3
        int level = company.getCompanyLevel();
        boolean hasChildCompany = company.isHasChildCompany();

        if(hasChildCompany)
        {
            //有子客户,显示展开/收缩 的图标
            ivSpreadLevel0.setVisibility(View.VISIBLE);
            if(shrinkCustomerIdList.contains(company.getCompanyID()))
            {
                //处于收缩状态,显示收缩对应的图标
                ivSpreadLevel0.setImageResource(R.drawable.bt_arrow_down_gray);
            } else
            {
                //处于展开状态,显示展开对应的图标
                ivSpreadLevel0.setImageResource(R.drawable.bt_arrow_up_gray);
            }
        } else
        {
            //没有子客户,不显示展开/收缩 的图标
            ivSpreadLevel0.setVisibility(View.INVISIBLE);
        }

        //根据客户的层级数值,设置margin。层级越高左边margin数值越大,
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) ivSpreadLevel0.getLayoutParams();
        //此处可做 dp/px  的转换适配不同屏幕,暂时为了方便,不做
        params.setMargins(36*(level + 1) - 24,0,0,0);
        ivSpreadLevel0.setLayoutParams(params);

        //展开/收缩图标的点击事件,点击后收缩变为展开,展开变为收缩
        ivSpreadLevel0.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
                if(shrinkCustomerIdList.contains(company.getCompanyID()))
                {
                    shrinkCustomerIdList.remove(company.getCompanyID());
                } else
                {
                    shrinkCustomerIdList.add(company.getCompanyID());
                }
                initCustomerShownList();
                notifyDataSetChanged();
            }
        });


        //项点击事件,点击后该项即为选中项(需变色),并且触发回调
        llItemMonitorCompany.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
                //设置当前点击项为选中项
                currentChosenCustomer = company;

                //回调
                if(listener != null)
                {
                    listener.onItemClick(company);
                }
                //更新界面
                notifyDataSetChanged();
            }
        });

    }

    /**
     * 初始化待显示的客户列表
     */
    public void initCustomerShownList()
    {
        //处于收缩状态的项的层级(0,1,2,3),暂默认层级不超过1000层。
        //相信也不会碰到超过1000层的情况
        int shrinkLevel = 1000;
        customerShownList.clear();
        int i = 0;
        for(;i<companyList.size();i++)
        {
            BaseCompany customer = companyList.get(i);

            if(shrinkCustomerIdList.contains(customer.getCompanyID()))
            {
                //处于收缩状态的项,该项显示,
                customerShownList.add(customer);
                shrinkLevel = customer.getCompanyLevel();
                //从下一个开始循环
                i++;
                for(;i<companyList.size();i++)
                {
                    if(companyList.get(i).getCompanyLevel() > shrinkLevel)
                    {
                        //下级菜单,全部隐藏
                        continue;
                    } else
                    {
                        //同级或上级,跳出循环,并将下标前移一位,让该对象进入上层的for循环判断,并初始化隐藏层级
                        i--;
                        shrinkLevel = 1000;
                        break;
                    }
                }
            } else
            {
                customerShownList.add(customer);
            }
        }
    }



    @Override
    public int getItemCount()
    {
        return customerShownList.size();
    }


    /**
     * 项点击的回调监听
     */
    private OnItemClickListener listener;
    public void setOnItemClickListener (OnItemClickListener listener)
    {
        this.listener = listener;
    }
    public static interface OnItemClickListener
    {
        public void onItemClick(BaseCompany company);
    }

}

四、接收到JSON解析完成的消息时(步骤二),取出客户列表数据,创建适配器对象(步骤三),并设置给RecyclerView

    @Subscribe(threadMode = ThreadMode.MAIN, priority = 100, sticky = false) //在ui线程执行,优先级为100
    public void onEvent(MessageBean msg)
    {
        switch (msg.what)
        {
            case AppConstants.MessageWhat.GET_CUSTOMER_INFO_SUCCESS:
                if(msg.arg1 != AppConstants.RequestTag.MONITOR_LIST_FRAGMENT)
                {
                    //不是来自该界面的请求结果
                    return;
                }
                List<BaseCompany> result = (List<BaseCompany>) msg.obj;
                customerAdapter = new MonitorListCustomerAdapter(getActivity(),result);
                customerAdapter.setOnItemClickListener(new MonitorListCustomerAdapter.OnItemClickListener()
                {
                    @Override
                    public void onItemClick(BaseCompany company)
                    {
                        //选中公司的名称和id
                        companyName = company.getCompanyName();
                        companyId = company.getCompanyID();
                        //TODO 进行相关操作
                    }
                });
                rvCompany.setAdapter(customerAdapter);
                rvCompany.setLayoutManager(new LinearLayoutManager(getActivity()));

	        //因为有数据的情况下适配器默认选中项为第一项,进行第一项选中情况下该进行的操作
                if(result.size() > 0)
                {
                    //第一项的公司id
                    companyId = result.get(0).getCompanyID();
                    //TODO 进行相关操作
                } else
                {
                    //TODO 进行没有数据的相关操作
                }
                break;

            default:
                break;
        }
    }

核心代码就这些,由于是从项目中抽取出来的,不适宜上传整个项目。

以下是我整理的一份demo,需要源码的可以下载。

https://download.csdn.net/download/qq_34763699/11258097

百度网盘:

链接:https://pan.baidu.com/s/1ohIEbltGpptpaSGM0CVQdw
提取码:nn4w

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android RecyclerView实现树形列表 的相关文章

随机推荐