You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从Assets文件夹解析JSON数据并在StickyHeader RecyclerView展示

问题排查及修复方案

1. 核心错误点整理

1.1 JSON解析逻辑完全错误

  • 你的JSON根节点是JSONObject,但代码直接用JSONArray(json)解析,会直接抛出JSONException,你当前仅捕获了IOException,所以解析异常会直接导致程序崩溃或后续设置RecyclerView的代码不执行
  • 循环遍历数组用了闭区间0..jsonArray.length(),数组下标最大为length-1,会触发数组越界异常
  • 没有按日期分组逻辑,无法支撑粘性头部的多类型列表展示

1.2 Adapter逻辑错误

  • getItemViewType直接固定返回TYPE_HEADER,所有项都会被识别为头部,普通项无法正常渲染
  • isHeader固定返回true,粘性头部装饰器逻辑完全失效
  • 数据源仅存储字符串,无法区分头部和普通作业项,也无法提取日期维度做分组

2. 修复步骤

第一步:定义数据结构

首先定义实体类和多类型列表项结构:

// 作业数据实体类
data class Job(
    val id: Int,
    val title: String,
    val startTime: String,
    // 按需补充其他JSON字段
)

// 列表多类型项密封类
sealed class JobsListItem {
    data class DateHeader(val date: String): JobsListItem()
    data class JobEntry(val job: Job): JobsListItem()
}

第二步:修正JSON解析逻辑

修改loadJSONFromAsset方法,补充异常捕获、正确解析、按日期分组:

private fun loadJSONFromAsset() {
    try {
        val inputStream: InputStream = assets.open("test.json")
        val json = inputStream.bufferedReader().use { it.readText() }
        // 先取根JSONObject
        val rootObj = JSONObject(json)
        val dataArray = rootObj.getJSONArray("data")
        val jobList = mutableListOf<Job>()
        // 用左闭右开区间until遍历避免越界
        for (i in 0 until dataArray.length()) {
            val jobObj = dataArray.getJSONObject(i)
            val job = Job(
                id = jobObj.getInt("id"),
                title = jobObj.getString("title"),
                startTime = jobObj.getString("start_time")
                // 补充其他字段解析
            )
            jobList.add(job)
        }
        // 按日期维度分组
        val dateGroupMap = jobList.groupBy { it.startTime.split(" ")[0] }
        // 组装多类型列表数据源
        val listItems = mutableListOf<JobsListItem>()
        dateGroupMap.forEach { (date, jobs) ->
            listItems.add(JobsListItem.DateHeader(date))
            jobs.forEach { listItems.add(JobsListItem.JobEntry(it)) }
        }
        // 初始化RecyclerView
        viewManager = LinearLayoutManager(this)
        recyclerView = findViewById<RecyclerView>(R.id.recycler_view).apply {
            layoutManager = viewManager
            adapter = JobsAdapter(listItems)
        }
        recyclerView.addItemDecoration(
            StickyHeaderItemDecoration(
                recyclerView,
                recyclerView.adapter as JobsAdapter
            )
        )
    } catch (ex: Exception) {
        // 捕获所有异常方便排查
        ex.printStackTrace()
        Toast.makeText(this, "解析出错:${ex.message}", Toast.LENGTH_SHORT).show()
    }
}

第三步:修正Adapter逻辑

修改Adapter的数据源和多类型判断逻辑:

class JobsAdapter(
    private val items: List<JobsListItem>
): RecyclerView.Adapter<RecyclerView.ViewHolder>(), StickyHeaderItemDecoration.StickyHeaderInterface  {

    companion object {
        const val TYPE_HEADER = 0
        const val TYPE_ITEM = 1
    }

    override fun isHeader(itemPosition: Int): Boolean {
        return items[itemPosition] is JobsListItem.DateHeader
    }

    override fun bindHeaderData(header: View, headerPosition: Int) {
        val date = (items[headerPosition] as JobsListItem.DateHeader).date
        header.findViewById<TextView>(R.id.headerView).text = date
    }

    override fun getHeaderLayout(headerPosition: Int): Int {
        return R.layout.row_header
    }

    override fun getHeaderPositionForItem(itemPosition: Int): Int {
        var currentPos = itemPosition
        while (currentPos >= 0) {
            if (isHeader(currentPos)) return currentPos
            currentPos--
        }
        return 0
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return if(viewType == TYPE_HEADER) {
            ViewHolderHeader(LayoutInflater.from(parent.context)
                .inflate(R.layout.row_header, parent, false))
        } else {
            ViewHolder(LayoutInflater.from(parent.context)
                .inflate(R.layout.user_item, parent, false))
        }
    }

    override fun getItemCount() = items.size

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when(val item = items[position]) {
            is JobsListItem.DateHeader -> {
                (holder as ViewHolderHeader).headerView.text = item.date
            }
            is JobsListItem.JobEntry -> {
                (holder as ViewHolder).nameView.text = item.job.title
                // 按需绑定其他作业字段
            }
        }
    }

    override fun getItemViewType(position: Int): Int {
        return if (items[position] is JobsListItem.DateHeader) TYPE_HEADER else TYPE_ITEM
    }

    class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val nameView = view.findViewById<TextView>(R.id.tv_name)
    }
    class ViewHolderHeader(view: View) : RecyclerView.ViewHolder(view) {
        val headerView = view.findViewById<TextView>(R.id.headerView)
    }
}

内容的提问来源于stack exchange,提问作者faiz dev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.03 17:54:02