前段时间公司有个项目,需要展示客户关系的树形列表,当时网上找了一些资料,有些觉得挺复杂的,有些测试下来有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://pan.baidu.com/s/1ohIEbltGpptpaSGM0CVQdw
提取码:nn4w