前段時間公司有個項目,需要展示客戶關系的樹形清單,當時網上找了一些資料,有些覺得挺複雜的,有些測試下來有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