【问题标题】:(Android Kotlin) RecyclerView Adapter methods not called: empty RecyclerView being shown(Android Kotlin) 未调用 RecyclerView 适配器方法:显示空 RecyclerView
【发布时间】:2020-12-01 04:42:08
【问题描述】:

我正在尝试使用 Retrofit2 显示来自虚拟服务器 (json-server) 的 JSON 数据,并将其显示在放置在片段内的 RecyclerView 中。到目前为止,正在发生以下事件(使用日志信息发现):

  1. 主片段CharactersListFragment.kt创建已启动。
  2. 只有getItemCount() 被调用。它返回 0。
  3. OnCreateViewHolder() 和 onBindViewHolder() 未被调用。
  4. 从服务器成功获取数据并传递给适配器(来自CharactersListFragment.kt)

没有编译时错误,我的应用也没有崩溃,只是 RecyclerView 是空的。

这是我的代码文件:

  1. 带有 RecyclerView 的片段布局:fragment_characters_list.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    tools:context=".CharactersListFragment">


    <androidx.recyclerview.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/recycler_view"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"

        />
</androidx.constraintlayout.widget.ConstraintLayout>

  1. RecyclerView 行的布局:fragment_character_recyclerview_list.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView 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:id="@+id/character_card_view"
    android:layout_width="match_parent"
    android:layout_height="300dp"
    android:elevation="15dp"
    app:cardBackgroundColor="@color/violet_forcard"
    app:cardCornerRadius="3dp">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">


        <ImageView
            android:id="@+id/char_thumnail_img"
            android:layout_width="143dp"
            android:layout_height="150dp"
            android:layout_marginStart="24dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_bias="0.106"
            tools:srcCompat="@tools:sample/avatars" />

        <TextView
            android:id="@+id/char_id_txt"
            android:layout_width="190dp"
            android:layout_height="53dp"
            android:layout_marginTop="16dp"
            android:fontFamily="@font/roboto"
            android:text="Character ID: "
            android:textAlignment="viewStart"
            android:textColor="@color/white"
            android:textSize="18sp"
            android:textStyle="bold"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="@+id/char_thumnail_img"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/char_name_txt"
            android:layout_width="190dp"
            android:layout_height="53dp"
            android:layout_marginTop="24dp"
            android:fontFamily="@font/roboto"
            android:text="Name: "
            android:textAlignment="viewStart"
            android:textColor="@color/white"
            android:textSize="18sp"
            android:textStyle="bold"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="@+id/char_thumnail_img"
            app:layout_constraintTop_toBottomOf="@+id/char_id_txt" />

        <TextView
            android:id="@+id/char_descp_txt"
            android:layout_width="359dp"
            android:layout_height="wrap_content"
            android:fontFamily="@font/roboto"
            android:textAlignment="viewStart"
            android:textColor="@color/white"
            android:textSize="14sp"
            android:textStyle="bold"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.461"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_bias="0.918" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
  1. 片段的 Kotlin 类:CharactersListFragment.kt
package com.example.marvelapp

import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response

class CharactersListFragment : Fragment() {
    private lateinit var charsAdapter: CharacterListAdapter
    private lateinit var apiService: APIService
    private var characters: MutableList<Character> = ArrayList()


    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment

        Log.i("INSIDE_FRAGMENT_ONCREATEVIEW", "Fragment creation initiated")
        val v = inflater.inflate(R.layout.fragment_characters_list, container, false)

        apiService = RestClient.client.create(APIService::class.java)

        getCharacterList()

        val recycler_view = v.findViewById(R.id.recycler_view) as RecyclerView
        recycler_view.layoutManager = LinearLayoutManager(activity)
        charsAdapter = CharacterListAdapter()
        recycler_view.adapter = charsAdapter

        return v
    }

    private fun getCharacterList(){
        val call = apiService!!.get_characters()
        call.enqueue(object: Callback<List<Character>> {
            override fun onResponse(
                call: Call<List<Character>>,
                response: Response<List<Character>>
            ) {
                val chars = response.body()
                Log.i("get_character_succeeded_FOR_CALL", chars.toString())
                if (chars != null){
                    characters.addAll(chars!!)
                    Log.i("character_ADD_CHECK_INSIDE_FRAGMENT", characters[0].toString())
                    charsAdapter.submitDataforcharList(characters)
                }

            }

            override fun onFailure(call: Call<List<Character>>, t: Throwable) {
                Log.e("get_character_failed", t.localizedMessage)
            }

        })
    }

}

  1. 适配器的 Kotlin 类:CharacterListAdapter.kt
package com.example.marvelapp

import android.text.Layout
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.fragment_character_recyclerview_list.view.*

class CharacterListAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    private var charItems: List<Character> = ArrayList()

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        Log.i("INSIDE_ONCREATE_VIEWHOLDER", "reached")
        return CharacterListViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.fragment_character_recyclerview_list,
        parent,
        false))
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when(holder){
            is CharacterListViewHolder ->{
                Log.i("VIEWHOLDER_FOUND", "proceeding to bind")
                holder.bind(charItems.get(position))
            }
        }
    }

    override fun getItemCount(): Int {
        Log.i("INSIDE_ADAPTER_GET_ITEM_COUNT", charItems.size.toString())
        return charItems.size
    }

    fun submitDataforcharList (characterlist: List<Character>){
        Log.i("INSIDE_ADAPTER_SUBMIT_DATA", characterlist[0].toString())
        charItems = characterlist
    }

    class CharacterListViewHolder
    constructor(view: View): RecyclerView.ViewHolder(view){
        val char_id = view.findViewById<TextView>(R.id.char_id_txt)
        val char_name = view.findViewById<TextView>(R.id.char_name_txt)
        val char_descp = view.findViewById<TextView>(R.id.char_descp_txt)

        fun bind(character: Character){
            Log.i("INSIDE_VIEWHOLDER_BIND", character.toString())
            val id = Integer.toString(character.charId)
            val id_mess = "Character ID: $id"
            char_id.setText(id_mess)
            val char_name_mess = "Name: ${character.charName}"
            char_name.setText(char_name_mess)
            char_descp.setText(character.charDescp)
        }

    }
}

  1. Character 模型类:
package com.example.marvelapp


data class Character(
    val charId: Int,
    val charName: String,
    val charDescp: String,
){
}

我是一名 android 初学者,并且在这方面已经坚持了很长一段时间,任何帮助将不胜感激。 提前致谢!

【问题讨论】:

  • 在CharactersListFragment 的onCreateView 中,您正在执行getCharacterList()。所以从我对android的理解来看,这不是一个内联调用吗?我的意思是这个方法调用不会阻塞主线程吧?我认为您应该在收到数据后初始化适配器类。
  • 您需要使用 charItems.addAll(characterlist) 而不是 charItems = characterlist 。你还需要在它之后调用notifyDatasetChanged()。
  • 目前您可能正在获取一些数据,并且您只是在更新适配器类中的数据列表,但您没有通知您的 recyclerview 刷新视图以显示新数据。因此,我认为如果您在设置新数据后只在适配器上调用notifyDataSetChanged(),它应该可以工作。但我仍然建议您在收到来自 getCharacterList() 调用的数据后初始化 recyclerview。
  • 非常感谢大家,原来我不得不重新排序getCharacterList()的调用并添加notifyDatasetChanged()

标签: android kotlin android-fragments android-recyclerview


【解决方案1】:

在CharactersListFragment 的onCreateView 中,您正在调用getCharacterList(),然后您继续初始化回收器视图。在getCharacterList 调用完成后,您可能会获取一些数据,并且您只是在更新适配器类中的数据列表,但您并没有通知您的 recyclerview 刷新视图以显示新数据。所以我认为如果你在设置新数据后在适配器上调用notifyDataSetChanged(),它应该可以工作。

解决此问题的另一种方法可能是,如果您在收到来自 getCharacterList() 调用的数据后初始化您的 recyclerview,并在获取数据时只显示一些加载图标来代替 recycler 视图。

【讨论】:

    【解决方案2】:

    getCharacterList() 在 UI 线程(主线程)上调用,但设备和 API 服务器之间的通信(请求和响应)发生在另一个线程中,这意味着不能保证 onResponse() 会早于 recycler_view.adapter = charsAdapter实际上,它大多不会发生。因此,您必须在charsAdapter.submitDataforcharList(characters) 之后调用charsAdapter.notifyDataSetChanged() 才能让recyclerview 表示数据。另一种更优雅的方法是在submitDataforcharList() 中添加notifyDatasetChanged():

    fun submitDataforcharList (characterlist: List<Character>){
            charItems = characterlist
            notifyDatasetChanged()
    }
    

    【讨论】:

    • 我根据您的回答添加了notifyDatasetChanged() 函数,但随后应用程序因java.lang.IndexOutOfBoundsException: Index: 0, Size: 0 而崩溃,因此我将submitDataforcharList() 的调用移至getCharList() 方法中,然后它就起作用了。
    • @SimranSaha 崩溃是由您访问空列表索引 0 处的元素引起的。删除 submitDataforcharList() 内的 Log.i("INSIDE_ADAPTER_SUBMIT_DATA", characterlist[0].toString()) 即可解决问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多