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

如何在Android Studio中为RecyclerView设置异构JSON数据

如何在Android Studio中实现异构RecyclerView展示你的油价JSON数据

嘿,我来帮你搞定这个异构RecyclerView的需求!首先咱们得明确目标:你的JSON数据是同一日期下不同城市、不同公司的柴油/汽油价格,咱们可以把列表做成两种异构Item:一种是「城市标题项」(用来区分不同城市),另一种是「油价详情项」(展示具体的公司、油品类型和价格)。接下来一步步实现:

1. 创建数据模型类

咱们用密封类(Kotlin)或者父类+子类(Java)来统一两种Item类型,这样Adapter能轻松区分它们:

Kotlin 版本

// 基类,统一RecyclerView的所有Item类型
sealed class PriceListItem {
    // 城市标题Item
    data class CityTitle(val cityName: String) : PriceListItem()
    // 油价详情Item
    data class PriceDetail(
        val date: String,
        val price: Double,
        val company: String,
        val type: String
    ) : PriceListItem()
}

// 解析原始JSON用的实体类
data class RawPrice(
    val date: String,
    val price: Double,
    val company: String,
    val city: String,
    val type: String
)

Java 版本(可选)

如果你用Java,可以写一个父类,然后两个子类,再加上getItemType()方法来区分:

// 基类
public abstract class PriceListItem {
    public static final int TYPE_CITY_TITLE = 0;
    public static final int TYPE_PRICE_DETAIL = 1;
    public abstract int getItemType();
}

// 城市标题Item
public class CityTitleItem extends PriceListItem {
    private String cityName;
    public CityTitleItem(String cityName) { this.cityName = cityName; }
    public String getCityName() { return cityName; }
    @Override public int getItemType() { return TYPE_CITY_TITLE; }
}

// 油价详情Item
public class PriceDetailItem extends PriceListItem {
    private String date;
    private double price;
    private String company;
    private String type;
    // 构造器、getter省略
    @Override public int getItemType() { return TYPE_PRICE_DETAIL; }
}

// 原始JSON解析用的实体类
public class RawPrice {
    private String date;
    private double price;
    private String company;
    private String city;
    private String type;
    // 构造器、getter、setter省略
}

2. 解析JSON并生成异构列表

先把原始JSON解析成RawPrice列表,然后按城市分组,再依次添加「城市标题Item」和该城市下的所有「油价详情Item」:

Kotlin 示例(用Gson解析)

// 假设你已经拿到了JSON字符串jsonString
val gson = Gson()
val rawPrices = gson.fromJson(jsonString, Array<RawPrice>::class.java).toList()

// 按城市分组
val groupedByCity = rawPrices.groupBy { it.city }

// 构建异构列表
val heterogeneousList = mutableListOf<PriceListItem>()
groupedByCity.forEach { (city, prices) ->
    // 先添加城市标题
    heterogeneousList.add(PriceListItem.CityTitle(city))
    // 再添加该城市下的所有油价详情
    prices.forEach { price ->
        heterogeneousList.add(
            PriceListItem.PriceDetail(
                date = price.date,
                price = price.price,
                company = price.company,
                type = price.type
            )
        )
    }
}

3. 实现多类型RecyclerView Adapter

Adapter需要区分两种Item类型,创建对应的ViewHolder并绑定数据:

Kotlin 版本

class PriceAdapter(private val items: List<PriceListItem>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    companion object {
        private const val TYPE_CITY_TITLE = 0
        private const val TYPE_PRICE_DETAIL = 1
    }

    // 区分当前Item的类型
    override fun getItemViewType(position: Int): Int {
        return when (items[position]) {
            is PriceListItem.CityTitle -> TYPE_CITY_TITLE
            is PriceListItem.PriceDetail -> TYPE_PRICE_DETAIL
        }
    }

    // 创建对应的ViewHolder
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        return when (viewType) {
            TYPE_CITY_TITLE -> {
                val view = inflater.inflate(R.layout.item_city_title, parent, false)
                CityTitleViewHolder(view)
            }
            TYPE_PRICE_DETAIL -> {
                val view = inflater.inflate(R.layout.item_price_detail, parent, false)
                PriceDetailViewHolder(view)
            }
            else -> throw IllegalArgumentException("未知的Item类型")
        }
    }

    // 绑定数据到ViewHolder
    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (val item = items[position]) {
            is PriceListItem.CityTitle -> (holder as CityTitleViewHolder).bind(item)
            is PriceListItem.PriceDetail -> (holder as PriceDetailViewHolder).bind(item)
        }
    }

    override fun getItemCount() = items.size

    // 城市标题ViewHolder
    inner class CityTitleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        private val tvCity: TextView = itemView.findViewById(R.id.tv_city_title)
        fun bind(item: PriceListItem.CityTitle) {
            tvCity.text = item.cityName
        }
    }

    // 油价详情ViewHolder
    inner class PriceDetailViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        private val tvCompany: TextView = itemView.findViewById(R.id.tv_company)
        private val tvType: TextView = itemView.findViewById(R.id.tv_fuel_type)
        private val tvPrice: TextView = itemView.findViewById(R.id.tv_price)
        private val tvDate: TextView = itemView.findViewById(R.id.tv_date)

        fun bind(item: PriceListItem.PriceDetail) {
            tvCompany.text = item.company
            tvType.text = item.type
            tvPrice.text = String.format("%.2f ₹", item.price) // 按需求格式化价格
            tvDate.text = item.date
        }
    }
}

4. 创建Item布局文件

需要两个布局文件分别对应两种Item:

item_city_title.xml(城市标题布局)

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/tv_city_title"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="16dp"
    android:background="#f5f5f5"
    android:textSize="18sp"
    android:textStyle="bold"/>

item_price_detail.xml(油价详情布局)

<?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:padding="16dp"
    android:orientation="horizontal"
    android:gravity="center_vertical">

    <TextView
        android:id="@+id/tv_company"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="wrap_content"
        android:textSize="16sp"/>

    <TextView
        android:id="@+id/tv_fuel_type"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="wrap_content"
        android:textSize="16sp"
        android:gravity="center"/>

    <TextView
        android:id="@+id/tv_price"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="wrap_content"
        android:textSize="16sp"
        android:gravity="end"/>

    <TextView
        android:id="@+id/tv_date"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="12sp"
        android:paddingStart="8dp"
        android:textColor="#666"/>
</LinearLayout>

5. 配置RecyclerView

最后在Activity/Fragment里初始化RecyclerView并设置Adapter:

// 假设你的RecyclerView id是rv_price_list
val rvPriceList = findViewById<RecyclerView>(R.id.rv_price_list)
rvPriceList.layoutManager = LinearLayoutManager(this)
rvPriceList.adapter = PriceAdapter(heterogeneousList)

额外提示

  • 如果需要按公司再分组(比如同一城市下先显示Bharat Petroleum的所有油品,再显示Indian Oil的),可以在分组城市后,再对每个城市的列表按company分组,然后依次添加公司标题Item和油品详情Item,这样就变成三层异构了。
  • 解析JSON时可以用Moshi、Retrofit等其他库,根据你的项目习惯选择。
  • 可以给RecyclerView添加分割线,让异构列表的层级更清晰。

内容的提问来源于stack exchange,提问作者Anand Gaur

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 07:16:57