Android技术问题:如何在RecyclerView中操作JSON数据并实现项点击筛选
Hey,我来一步步帮你解决这两个在Android订餐类APP里超常见的RecyclerView问题!
1. 如何在RecyclerView中获取并设置JSON数据?
整个流程可以拆成准备实体类、获取并解析JSON、绑定RecyclerView适配器这三步,我给你具体的代码示例:
第一步:创建对应JSON结构的实体类
首先得把后端返回的JSON转换成Java/Kotlin的实体类,这样才能方便操作数据。假设你的JSON数据是这样的:
[ { "name": "宫保鸡丁", "rate": 4.8, "amount": 28, "desc": "经典川菜,酸甜适口" }, { "name": "鱼香肉丝", "rate": 4.7, "amount": 26, "desc": "咸香微辣,下饭神器" } ]
那对应的Kotlin实体类可以这么写(自带getter/setter,还支持序列化):
@Parcelize data class FoodItem( val name: String, val rate: Double, val amount: Int, val desc: String ) : Parcelable
如果是Java的话,就写普通的Bean类,记得加构造方法、getter和setter,或者用Lombok简化代码。
第二步:获取并解析JSON数据
我用常用的OkHttp来举例子,你也可以用Retrofit(更适合复杂接口场景):
val okHttpClient = OkHttpClient() val request = Request.Builder() .url("你的JSON接口地址") // 比如http://xxx.com/api/foods .build() okHttpClient.newCall(request).enqueue(object : Callback { override fun onFailure(call: Call, e: IOException) { // 请求失败的处理,比如弹个Toast提示用户 runOnUiThread { Toast.makeText(this@MainActivity, "加载失败", Toast.LENGTH_SHORT).show() } e.printStackTrace() } override fun onResponse(call: Call, response: Response) { response.body?.string()?.let { jsonStr -> // 用Gson解析JSON成实体类列表 val foodList = Gson().fromJson(jsonStr, Array<FoodItem>::class.java).toList() // 必须回到主线程更新UI runOnUiThread { setupRecyclerView(foodList) } } } })
别忘了在build.gradle里添加OkHttp和Gson的依赖哦!
第三步:绑定RecyclerView适配器
先写一个简单的适配器,把数据渲染到列表项里:
class FoodAdapter(private val foodList: List<FoodItem>) : RecyclerView.Adapter<FoodAdapter.FoodViewHolder>() { inner class FoodViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val tvFoodName: TextView = itemView.findViewById(R.id.tv_food_name) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FoodViewHolder { // 加载列表项布局 val itemView = LayoutInflater.from(parent.context) .inflate(R.layout.item_food, parent, false) return FoodViewHolder(itemView) } override fun onBindViewHolder(holder: FoodViewHolder, position: Int) { val food = foodList[position] // 把名称设置到TextView里 holder.tvFoodName.text = food.name } override fun getItemCount() = foodList.size }
然后写setupRecyclerView方法初始化RecyclerView:
private fun setupRecyclerView(foodList: List<FoodItem>) { val rvFood = findViewById<RecyclerView>(R.id.rv_food) rvFood.layoutManager = LinearLayoutManager(this) // 线性布局,你也可以用GridLayoutManager rvFood.adapter = FoodAdapter(foodList) }
2. 点击列表项名称时,展示对应的Rate、Amount等关联数据
这里有三种常见的实现方式,你可以根据APP的交互需求选择:
方式一:在当前列表项展开/收起详情
适合不想跳转页面的轻量展示,修改一下适配器和布局就行:
- 先修改列表项布局
item_food.xml,把详情部分默认隐藏:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="16dp"> <TextView android:id="@+id/tv_food_name" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="18sp" android:textStyle="bold"/> <!-- 详情区域,默认隐藏 --> <LinearLayout android:id="@+id/ll_food_details" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:visibility="gone" android:layout_marginTop="8dp"> <TextView android:id="@+id/tv_food_rate" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="评分:4.8"/> <TextView android:id="@+id/tv_food_amount" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="价格:28元"/> </LinearLayout> </LinearLayout>
- 修改适配器,添加点击事件和展开状态记录:
class FoodAdapter(private val foodList: List<FoodItem>) : RecyclerView.Adapter<FoodAdapter.FoodViewHolder>() { // 记录哪些项是展开状态 private val expandedPositions = mutableSetOf<Int>() inner class FoodViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val tvFoodName: TextView = itemView.findViewById(R.id.tv_food_name) val llFoodDetails: LinearLayout = itemView.findViewById(R.id.ll_food_details) val tvFoodRate: TextView = itemView.findViewById(R.id.tv_food_rate) val tvFoodAmount: TextView = itemView.findViewById(R.id.tv_food_amount) init { // 给名称TextView设置点击事件 tvFoodName.setOnClickListener { val position = adapterPosition if (position != RecyclerView.NO_POSITION) { if (expandedPositions.contains(position)) { // 收起详情 expandedPositions.remove(position) llFoodDetails.visibility = View.GONE } else { // 展开详情并设置数据 expandedPositions.add(position) llFoodDetails.visibility = View.VISIBLE val food = foodList[position] tvFoodRate.text = "评分:${food.rate}分" tvFoodAmount.text = "价格:${food.amount}元" } // 更新当前项的UI notifyItemChanged(position) } } } } override fun onBindViewHolder(holder: FoodViewHolder, position: Int) { val food = foodList[position] holder.tvFoodName.text = food.name // 根据展开状态设置详情区域的可见性 holder.llFoodDetails.visibility = if (expandedPositions.contains(position)) View.VISIBLE else View.GONE // 如果是展开状态,提前设置好数据 if (expandedPositions.contains(position)) { holder.tvFoodRate.text = "评分:${food.rate}分" holder.tvFoodAmount.text = "价格:${food.amount}元" } } // 其他方法不变... }
方式二:跳转到详情页面展示
适合需要展示更多详细信息的场景,比如菜品描述、图片等:
- 给适配器添加点击回调:
class FoodAdapter( private val foodList: List<FoodItem>, private val onItemClick: (FoodItem) -> Unit ) : RecyclerView.Adapter<FoodAdapter.FoodViewHolder>() { inner class FoodViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val tvFoodName: TextView = itemView.findViewById(R.id.tv_food_name) init { tvFoodName.setOnClickListener { val position = adapterPosition if (position != RecyclerView.NO_POSITION) { onItemClick.invoke(foodList[position]) } } } } // 其他方法不变... }
- 设置适配器的时候传入回调,跳转到详情页:
rvFood.adapter = FoodAdapter(foodList) { foodItem -> val intent = Intent(this, FoodDetailActivity::class.java) intent.putExtra("FOOD_ITEM", foodItem) // 因为我们的FoodItem实现了Parcelable,所以可以直接传递 startActivity(intent) }
- 在
FoodDetailActivity里获取数据并展示:
class FoodDetailActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_food_detail) val foodItem = intent.getParcelableExtra<FoodItem>("FOOD_ITEM") foodItem?.let { findViewById<TextView>(R.id.tv_detail_name).text = it.name findViewById<TextView>(R.id.tv_detail_rate).text = "评分:${it.rate}分" findViewById<TextView>(R.id.tv_detail_amount).text = "价格:${it.amount}元" findViewById<TextView>(R.id.tv_detail_desc).text = it.desc } } }
方式三:弹出Dialog展示详情
适合快速查看简单信息的场景,直接在点击事件里创建Dialog:
// 在适配器的ViewHolder里修改点击事件 tvFoodName.setOnClickListener { val position = adapterPosition if (position != RecyclerView.NO_POSITION) { val food = foodList[position] AlertDialog.Builder(itemView.context) .setTitle(food.name) .setMessage("评分:${food.rate}分\n价格:${food.amount}元\n${food.desc}") .setPositiveButton("确定") { dialog, _ -> dialog.dismiss() } .show() } }
内容的提问来源于stack exchange,提问作者Rahul
相关产品推荐
相关产品推荐

