让开发更快一步 - 基于 BaseJson 的一键构建列表 UI

624 阅读6分钟

一般情况下,要将网络请求后的数据显示到界面上,至少要经过以下步骤:

Http 网络请求 → 请求解析读取数据格式为 json → 解析 json 转换成 JavaBean → 编写列表项布局 → 编写 ViewHolder class → 编写适配器 Adapter → 交由列表显示到界面

这个过程是否能够简化一下?而 BaseJson 的适配器生成方案就可以轻松解决这个问题,甚至在最为理想的情况下,能够做到以下的步骤:

Http 网络请求 → 请求解析读取数据格式为 json → 根据数据生成 JsonListAdapter → 交由列表显示到界面

要实现这点就必须得做到能够由 jsonArray 的数据与列表项界面元素 View 的对应关系,又得能省去编写 ViewHolder 和 Adapter 的过程,最后得保证不能因为数据的变化造成 app 闪退崩溃,要做到这点,首先尽可能的需要 json 的解析本身能够转换成 List 的子类,这样才能被 Adapter 直接接受,BaseJson 就可以轻松完成这点,BaseJson 可以轻松将 jsonObject 转换为一个 Map 对象 JsonMap,而 JsonArray 则可以转换为一个 List 数据对象 JsonList 可以轻松满足适配器的构建条件,其次 BaseJson 默认支持针对空指针的软处理,也就意味着无论是 JsonMap 还是 JsonList,get 任何不存在或者类型错误的值都会返回空值而非空指针,软件顶多无法显示内容而不会导致崩溃,这也从一定程度上保障了 app 的可靠性,具体的可以移步 BaseJson Github 阅读 ReadMe

构建 JsonListAdapter

首先需要解析 Json,你可以使用 BaseOkHttpV3 直接支持解析返回值为 BaseJson,也可以自行转换 json 文本为一个 JsonList:

//解析一段 json 文本为 JsonList
JsonList list = new JsonList(jsonStr);

解析结果不会为空,这是 BaseJson 的基础,若 list.isEmpty() 则是解析失败。

首先需要确保要构造 JsonListAdapter 的 JsonList 必须是一个内容全部为 [{jsonObject},{jsonObject}] 形式的Json 集合对象,其内部的 jsonObject 会被解析为 JsonMap。

然后通过以下方法构建 JsonListAdapter:

//通过 createAdapter 方法创建 JsonListAdapter
JsonListAdapter adapter = list.createAdapter(me, R.layout.item_my_order_list);
​
//或者,通过构造方法创建 JsonListAdapter
JsonListAdapter adapter = new JsonListAdapter(me, R.layout.item_my_order_list, orderList);

生成 JsonListAdapter 后,可以直接设置给 listView 进行显示:

listView.setAdapter(adapter);

要使 Json 中的内容能够正确显示到了列表项的 UI 中,请在列表项的 xml 布局中,对需要呈现内容对应的 View 增加 android:tag 属性标签,内容和 jsonObject 的 key 对应即可。

例如对于一个 json:

[  {    "name": "zhangsan",    "avatar": "https://www.kongzue.com/example/avatar1.png",    "tip": "normal"  },  {    "name": "lisi",    "avatar": "https://www.kongzue.com/example/avatar2.png",    "tip": "vip"  }]

对应要显示的列表项布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center_vertical"
    android:orientation="horizontal">
​
    <com.facebook.drawee.view.SimpleDraweeView
        android:layout_width="55dp"
        android:layout_height="55dp"
        android:tag="avatar"
        android:layout_marginHorizontal="15dp" />
​
    <TextView
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:layout_weight="1"
        android:tag="name"
        android:gravity="center"
        android:textSize="20dp" />
​
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="100dp"
        android:gravity="center"
        android:tag="tip"
        android:textSize="20dp" />
    
</LinearLayout>

那么列表会在显示时自动适配显示数据到对应的界面 View 中。

TextView 自动绑定: 当数据为 String 时自动设置 setText;当数据为 int 时自动调用并设置为文本资源

ImageView 自动绑定: 当数据为 int 时自动调用并设置为图像资源;当数据为 String 时自动设置图像 Uri

这也就意味着单纯的呈现 jsonArray 数据到列表界面将几乎不需要再写任何 JavaBean、ViewHolder 和 Adapter 的复杂业务代码。

有列表项内的事务要处理

此时又有一个问题,比如我的列表项内部有一些事务需要单独处理,例如有一个 Button 需要单独绑定 click 操作,此时可以通过重写 JsonListAdapter#bindEvents 事件,亦或者通过 jsonListAdapter.setEvents(JsonListAdapterEvents#bindEvents) 接口来实现,例如:

JsonListAdapter adapter = new JsonListAdapter(me, R.layout.item_my_order_list, orderList) {
    /**
     * 绑定事件
     * 除非你要在列表项中处理某些控件的事务,例如单独指定 setOnClickListener,不然不建议使用
     * 原则就是能尽量少的干涉执行过程就尽量少干涉,能提前预处理好就提前预处理好,
     * 别搁运行过程中呼啦啦搞一大堆的逻辑处理,不然不卡你卡谁
     *
     * @param context    上下文
     * @param itemView   列表项根布局
     * @param viewHolder 所有子布局
     * @param data       数据
     * @param index      索引
     * @param dataList   数据集
     */
    @Override
    public void bindEvents(Context context, View itemView, JsonListAdapter.ViewHolder viewHolder, JsonMap data, int index, JsonList dataList) {
      //...
    }
};

请求下来的数据不符合界面呈现需要

若你的接口返回的数据可能需要预处理,个人建议在网络请求的异步线程提前进行数据的预处理,而不要在 adapter 中进行,因为列表滑动的过程中会频繁调去 adapter 的相关事务可能造成掉帧卡顿,因此更推荐提前在异步线程做好准备工作,例如:

//当 JsonList 的数据全都是 JsonMap,且需要对 JsonMap 的数据内容进行预处理,那么可以通过以下方法实现:
JsonList list = ...;
list.preprocessedJsonMapData(new JsonMapPreprocessingEvents() {
    @Override
    public JsonMap processingData(JsonMap originData) {
        originData.set("goods_price_str", "¥" + originData.getString("goods_price"));
        return originData;
    }
});

如果确实需要在适配器中进行数据的转换处理,可以使用以下方法实现:

JsonListAdapter adapter = new JsonListAdapter(me, R.layout.item_my_order_list, orderList) {
    /**
     * 预处理数据
     * 但不建议使用,更推荐在数据加载后,在网络请求或异步线程中完成所有数据的预处理,
     * 否则可能带来列表滑动过程的卡顿和性能问题。
     *
     * @param context    上下文
     * @param itemView   列表项根布局
     * @param viewHolder 所有子布局
     * @param data       数据
     * @param index      索引
     * @param dataList   数据集
     * @return 处理完的数据
     */
    @Override
    public JsonMap preprocessedData(Context context, View itemView, JsonListAdapter.ViewHolder viewHolder, JsonMap data, int index, JsonList dataList){
      //...
    }
};

我有自定义布局,要通过自己的方法设置数据

BaseJson 已经默认实现了针对 TextView 和 ImageView 的数据适配相关事务,但若你有自己的需要,可以通过实现以下接口自行实现一些特殊布局的数据适配工作:

JsonListAdapter adapter = new JsonListAdapter(me, R.layout.item_my_order_list, orderList) {
    /**
     * 设置数据
     * 我们已经预设了两种数据自动绑定的场景:例如,TextView 自动绑定:
     * {当数据为 String 时自动设置 setText;当数据为 int 时自动调用并设置为文本资源}
     * ImageView 自动绑定:
     * {当数据为 int 时自动调用并设置为图像资源;当数据为 String 时自动设置图像 Uri;}
     * 上述过程都是自动支持的,如果你有特殊需求或者其它组件需要设置,可以重写以下方法进行处理
     *
     * @param context  上下文
     * @param tag      子布局 view 的标签
     * @param view     子布局
     * @param data     数据
     * @param index    索引
     * @param dataList 数据集
     */
    @Override
    public void setData(Context context, String tag, View view, JsonMap data, int index, JsonList dataList){
      //...
    }
};

尾巴

BaseJson 旨在节省工作量,快速完成从网络请求、解析到界面数据填充适配的过程,可能还有不完善的地方也将在后续版本更新继续改善,如有建议或问题也可前往 Github 提交 issues,期待与大家的沟通和交流,愿世间没有 bug 和难写的代码。