无法在像 playstore 这样的嵌套回收器视图中检索数据

Unable to retrieve data in nested recycler view like playstore

在从 server.I 成功检索并解析 parent recyclerview 的 title 但无法显示水平 recyclerview 后,我试图在嵌套的 recyclerview 中显示数据,例如 play store这是一个 child 布局。

我在视图模型实例化时在 CategortAdapter.kt 中遇到错误,它在下面的 ViewModelProvider(context) 下显示红线:

val viewModel = ViewModelProvider(context).get(CatImagesViewModel::class.java)

以下是我的 JSON 回复:

{
"status": "200",
"message": "Success",
"result": [
    {
        "_id": "60f516fa846e059e2f19c50c",
        "category": "Shirts",
        "sku": [
            {
                "name": "Oxford shirt",
                "brand": "John players",
                "price": "25",
                "color": "Blue",
                "img": "https://firebasestorage.googleapis.com/v0/b/koovs-1ff31.appspot.com/o/shi1.jpg?alt=media&token=64779194-e3b5-484f-a610-c9a20648b64c"
            },
            {
                "name": "Buttoned down",
                "brand": "Gap originals",
                "price": "45",
                "color": "Pink",
                "img": "https://firebasestorage.googleapis.com/v0/b/koovs-1ff31.appspot.com/o/shi2.jpg?alt=media&token=0b207b90-f1bc-4771-b877-809648e6bdc1"
            },
            {
                "name": "Collared",
                "brand": "Arrow",
                "price": "30",
                "color": "White",
                "img": "https://firebasestorage.googleapis.com/v0/b/koovs-1ff31.appspot.com/o/shi3.jpg?alt=media&token=2c1bb3f8-e739-4f09-acbc-aa11fed795e3"
            },
            {
                "name": "Printed",
                "brand": "John players",
                "price": "30",
                "color": "Olive green",
                "img": "https://firebasestorage.googleapis.com/v0/b/koovs-1ff31.appspot.com/o/shi4.jpg?alt=media&token=666f94bf-4769-44fe-a909-3c81ca9262f7"
            },
            {
                "name": "Hoodie",
                "brand": "Levis",
                "price": "44",
                "color": "Yellow",
                "img": "https://firebasestorage.googleapis.com/v0/b/koovs-1ff31.appspot.com/o/shi5.jpg?alt=media&token=65fccef4-a882-4278-b5df-f00eb2785bf1"
            }
        ]
    },
    {
        "_id": "60f51c37846e059e2f19c50f",
        "category": "Shoes",
        "sku": [
            {
                "name": "Sneakers",
                "brand": "Puma",
                "price": "35",
                "color": "Black and white",
                "img": "https://firebasestorage.googleapis.com/v0/b/koovs-1ff31.appspot.com/o/sho1.jpg?alt=media&token=d078988d-9e85-4313-bb4a-c9d46e09f0b9"
            },
            {
                "name": "Running shoe",
                "brand": "Nike",
                "price": "99",
                "color": "Multicolor",
                "img": "https://firebasestorage.googleapis.com/v0/b/koovs-1ff31.appspot.com/o/sho2.jpg?alt=media&token=ed2e7387-3cf6-44df-9f7d-69843eb0bcdf"
            },
            {
                "name": "Yezzy",
                "brand": "Adidas",
                "price": "349",
                "color": "Gray",
                "img": "https://firebasestorage.googleapis.com/v0/b/koovs-1ff31.appspot.com/o/sho3.jpg?alt=media&token=2c37ef76-37bb-4bdd-b36c-dea32857291f"
            },
            {
                "name": "Sneakers",
                "brand": "Puma",
                "price": "79",
                "color": "Black",
                "img": "https://firebasestorage.googleapis.com/v0/b/koovs-1ff31.appspot.com/o/sho4.jpg?alt=media&token=4acd763e-8b93-47cd-ba45-92f34af4cf83"
            },
            {
                "name": "Joyride running",
                "brand": "Nike",
                "price": "80",
                "color": "White",
                "img": "https://firebasestorage.googleapis.com/v0/b/koovs-1ff31.appspot.com/o/sho5.jpg?alt=media&token=e3780dcc-52cb-49d5-9791-e0a44870716c"
            }
        ]
      }
   ]
}

在垂直的 parent 回收器视图中,我想显示标题 category,如 JSON 响应和水平回收器视图中的 sku 所示。

在 child 回收站视图中,我只想显示 sku 数组下的图像。

下面是我的代码:

ApiService.kt

interface ApiService {

@GET("getProducts")
suspend fun getCategories(): Response<Product>

@GET("getProducts")
suspend fun getCatImg(): Response<Product>
}

Product.kt

data class Product(
  val message: String,
  val result: List<Result>,
  val status: String
)

Result.kt

data class Result(
  val _id: String,
  val category: String,
  val sku: List<Sku>
)   

Sku.kt

data class Sku(
  val brand: String,
  val color: String,
  val img: String,
  val name: String,
  val price: String
) 

parent_row.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="12dp">

<TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:id="@+id/catTitle"
    android:textStyle="bold"
    android:textSize="18sp"
    android:textColor="#345"/>

<androidx.recyclerview.widget.RecyclerView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:id="@+id/childRecycler"
    android:layout_below="@+id/catTitle"
    android:layout_marginTop="8dp"/>

</RelativeLayout>

child_row.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">

<androidx.cardview.widget.CardView
    android:layout_width="200dp"
    android:layout_height="200dp"
    app:cardCornerRadius="3dp"
    app:cardUseCompatPadding="true">

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/img"/>

    </LinearLayout>

</androidx.cardview.widget.CardView>

</RelativeLayout>

在下面的适配器中,我正在获取类别标题和 child 回收站视图,因为我已成功检索到标题但无法解析回收站视图:

CategoryAdapter.kt

 class CategoryAdapter(private val context: Context,private val categories:List<Result>): RecyclerView.Adapter<CategoryAdapter.ViewHolder>() {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
   return 
ViewHolder(ParentRowBinding.inflate(LayoutInflater.from(parent.context),parent,false))
}

override fun onBindViewHolder(holder: ViewHolder, position: Int) {

    val model = categories[position]
    holder.binding.catTitle.text = model.category

    holder.binding.childRecycler.setHasFixedSize(true)
    holder.binding.childRecycler.layoutManager = LinearLayoutManager(context)

    val viewModel = ViewModelProvider(context).get(CatImagesViewModel::class.java)

}

override fun getItemCount(): Int {
    return categories.size
}

class ViewHolder(val binding:ParentRowBinding): RecyclerView.ViewHolder(binding.root)
}

CatImagesViewModel.kt

class CatImagesViewModel: ViewModel() {

private var catImageList:MutableLiveData<List<List<Sku>>> = MutableLiveData()

fun getCatImg(): LiveData<List<List<Sku>>>{

    viewModelScope.launch(Dispatchers.IO) {

        val retrofit = RetrofitClient.getRetrofitClient().create(ApiService::class.java)
        val response = retrofit.getCatImg()

        if(response.isSuccessful){

            val skus: MutableList<List<Sku>> = mutableListOf() 
            val cnt = response.body()!!.result.size

            for(i in 0 until cnt){
                skus.add(response.body()!!.result[i].sku)
            }
             catImageList.postValue(skus)
        }
    }

    return catImageList
  }
}

下面是在child回收站视图中解析图像的适配器,它是水平的:

CatImgAdapter.kt

class CatImgAdapter(private val context: Context,private val imgList:List<Sku>): RecyclerView.Adapter<CatImgAdapter.ViewHolder>() {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {

    return ViewHolder(ChildRowBinding.inflate(LayoutInflater.from(parent.context),parent,false))
}

override fun onBindViewHolder(holder: ViewHolder, position: Int) {

    val model = imgList[position]
    Glide.with(context).load(model.img).into(holder.binding.img)
}

override fun getItemCount(): Int {
    return imgList.size
}

data class ViewHolder(val binding:ChildRowBinding): RecyclerView.ViewHolder(binding.root)

}

下面是 ViewModel 用于获取垂直回收站视图的类别标题:

CategoriesViewModel.kt

class CategoriesViewModel: ViewModel() {

private var categoryList: MutableLiveData<List<Result>> = MutableLiveData()

fun getAllCategory(): LiveData<List<Result>> {

    viewModelScope.launch(Dispatchers.IO) {

        val retrofit = RetrofitClient.getRetrofitClient().create(ApiService::class.java)
        val response = retrofit.getCategories()

        if (response.isSuccessful) {
            categoryList.postValue(response.body()!!.result)
        }
    }
    return categoryList
  }
}     

有人告诉我我做错了什么或实现所需布局的最佳方法。

您不应该在 recyclerview adapter 中实例化您的 ViewModel。相反,您需要将数据传递给您的 recyclerview adapter.

您应该观察 Viewactivityfragment)中的 LiveData,然后将此数据(List<Result> 此处)传递给您的recyclerview adapterViewModel 只能在这些 Views.

中使用

如果您需要更多解释,请告诉我,我会尽量详细解释:)

编辑

我也注意到CatImagesViewModel你有

    if(response.isSuccessful){

        val cnt = response.body()!!.result.size

        for(i in 0 until cnt){
            catImageList.postValue(response.body()!!.result[i].sku)
        }
    }

postvalue 会将当前 resultsku 设置为 MutableLiveData,这意味着您将拥有 sku (List<Sku>) 仅来自 1 category,因为每次迭代都会丢弃前一个。

你想做的是,

class CatImagesViewModel: ViewModel() {

  private var catImageList:MutableLiveData<List<List<<Sku>>> = MutableLiveData()

  fun getCatImg(): LiveData<List<List<Sku>>>{

  viewModelScope.launch(Dispatchers.IO) {

      val retrofit = RetrofitClient.getRetrofitClient().create(ApiService::class.java)
      val response = retrofit.getCatImg()

      if(response.isSuccessful){

        val skus: MutableList<List<List<Sku>>> = mutableListOf()
        val cnt = response.body()!!.result.size

        for(i in 0 until cnt){
            skus.add(response.body()!!.result[i].sku)
        }

        catImageList.postValue(skus)
    }
}

    return catImageList
   }
}

最终更新

好的,这是你需要在嵌套的recyclerview中加载项目的代码,如果这些类的名称与你的不同,那么只需创建一个新项目并添加此代码以供参考,我附上了下面应用程序的工作屏幕截图作为提示,希望它现在可以为您解决所有问题:)

MainActivity.kt

class MainActivity : AppCompatActivity() {

    private val viewModel by viewModels<MainViewModel>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        viewModel.getResult()

        val adapter = ParentAdapter(this)
        recyclerview_parent.adapter = adapter

        viewModel.data.observe(this, {

            adapter.setData(it)
        })
    }
}

MainViewModel.kt

class MainViewModel : ViewModel() {

    private val service = RetrofitHelper().retrofit.create(ApiService::class.java)

    private val _data: MutableLiveData<List<Result>> = MutableLiveData()
    val data: LiveData<List<Result>> get() = _data

    fun getResult() {

        viewModelScope.launch {

            val res = service.getProducts()
            _data.postValue(res.body()?.result)
        }
    }
}

ApiService.kt

interface ApiService {

   @GET("getProducts")
   suspend fun getProducts(): Response<Product>
}

ParentAdapter.kt

class ParentAdapter(
    private val context: Context
): RecyclerView.Adapter<ParentAdapter.ParentViewHolder>() {

    private var data: List<Result>? = null

    inner class ParentViewHolder(itemView: View): RecyclerView.ViewHolder(itemView)

    fun setData(data: List<Result>) {
        this.data = data
        notifyDataSetChanged()
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ParentViewHolder {

        return ParentViewHolder(
            LayoutInflater.from(context)
                .inflate(R.layout.item_parent,parent,false)
        )
    }

    override fun onBindViewHolder(holder: ParentViewHolder, position: Int) {

        val item = data?.get(position)

        if (item != null) {

            holder.itemView.category_title.text = item.category

            val adapter = ChildAdapter(context)
            adapter.setData(item.sku)
            holder.itemView.recyclerview_child.adapter = adapter
        }
    }

    override fun getItemCount(): Int {
        return data?.size ?: 0
    }
}

ChildAdapter.kt

class ChildAdapter(
    private val context: Context
) : RecyclerView.Adapter<ChildAdapter.ChildViewHolder>() {

    private var data: List<Sku>? = null

    inner class ChildViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)

    fun setData(data: List<Sku>) {
        this.data = data
        notifyDataSetChanged()
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChildViewHolder {

        return ChildViewHolder(
            LayoutInflater.from(context)
                .inflate(R.layout.item_child, parent, false)
        )
    }

    override fun onBindViewHolder(holder: ChildViewHolder, position: Int) {

        val item = data?.get(position)

        if (item != null) {
            holder.itemView.label.text = item.name

            Glide.with(context)
                .load(item.img)
                .transition(DrawableTransitionOptions.withCrossFade())
                .into(holder.itemView.image)
        }
    }

    override fun getItemCount(): Int {
        return data?.size ?: 0
    }
}

RetrofitHelper.kt

class RetrofitHelper {

    val retrofit = Retrofit.Builder()
        .baseUrl("https://koovs18.herokuapp.com/")
        .addConverterFactory(GsonConverterFactory.create())
        .build()
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ui.MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerview_parent"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="16dp"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
        tools:listitem="@layout/item_parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

item_parent.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/category_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        tools:text="Category Title"
        android:layout_marginStart="4dp"
        android:layout_marginTop="8dp"
        android:textSize="24sp"
        android:textColor="@color/black"
        />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerview_child"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="8dp"
        android:orientation="horizontal"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
        tools:listitem="@layout/item_child"/>

</LinearLayout>

item_child.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <ImageView
        android:id="@+id/image"
        android:layout_width="150dp"
        android:layout_height="160dp"
        android:layout_margin="4dp"/>

    <TextView
        android:id="@+id/label"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        tools:text="Product Label"
        android:gravity="center"
        android:textSize="16sp"
        android:textColor="@color/black"
        android:padding="4dp"/>

</LinearLayout>

Dependencies you would need in build.gradle

def coroutines_version = "1.4.2"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"

def lifecycle_version = "2.3.1"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"

def retrofit_version = "2.9.0"
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"

implementation 'com.squareup.okhttp3:okhttp:4.9.1'

implementation 'com.github.bumptech.glide:glide:4.12.0'
kapt 'com.github.bumptech.glide:compiler:4.12.0'

implementation "androidx.activity:activity-ktx:1.2.4"