天天看點

Android RecyclerView實作樹形清單

  前段時間公司有個項目,需要展示客戶關系的樹形清單,當時網上找了一些資料,有些覺得挺複雜的,有些測試下來有bug。最終決定自己解決。

最底下有demo,需要源碼的同學可以下載下傳

效果圖(帶節點的展開與收縮,并且可以實作項的單選,選中項字型為藍色):

Android RecyclerView實作樹形清單
Android RecyclerView實作樹形清單

一、實體類的建構

這個類不多解釋,各個屬性的含義都在注釋上

/**
 * 公司類
 */
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