如何从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
相关产品推荐
相关产品推荐

