首页 > 解决方案 > 为 RecyclerView 片段实现 ListAdapter

问题描述

我已经用普通的ViewAdapter成功地完成了这项工作,但我似乎无法让它与ListAdapter一起工作。

这是我完成大部分工作的片段

class CrimeListFragment: Fragment() {

    //Required interface for hosting activities
    interface Callbacks {
        fun onCrimeSelected(crimeId: UUID)
    }

    private var callbacks: Callbacks? = null
    private lateinit var crimeRecyclerView: RecyclerView
    private val crimeListViewModel: CrimeListViewModel by lazy {
        ViewModelProviders.of(this).get(CrimeListViewModel::class.java)
    }

    override fun onAttach(context: Context) {
        super.onAttach(context)

        callbacks = context as Callbacks?
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {

        val view = inflater.inflate(R.layout.fragment_crime_list, container, false)

        crimeRecyclerView =
            view.findViewById(R.id.crime_recycler_view) as RecyclerView
        crimeRecyclerView.layoutManager = LinearLayoutManager(context)
        crimeRecyclerView.adapter = CrimeListAdapter(emptyList())

        return view
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        crimeListViewModel.crimeListLiveData.observe(
            viewLifecycleOwner,
            Observer { crimes ->
                crimes?.let {
                    Log.i(TAG, "Got crimes ${crimes.size}")
                    updateUI(crimes)
                }
            }
        )
    }

    override fun onDetach() {
        super.onDetach()

        callbacks = null
    }

    private fun updateUI(crimes: List<Crime>) {
        crimeRecyclerView.adapter = CrimeListAdapter(crimes)
    }

    companion object {
        fun newInstance(): CrimeListFragment {
            return CrimeListFragment()
        }
    }

    private inner class CrimeHolder(view: View)
        : RecyclerView.ViewHolder(view), View.OnClickListener {

        private lateinit var crime: Crime
        private val titleTextView = itemView.findViewById<TextView>(R.id.crime_title)
        private val dateTextView = itemView.findViewById<TextView>(R.id.crime_date)
        private val solvedImageView = itemView.findViewById<ImageView>(R.id.crime_solved)

        init {
            itemView.setOnClickListener(this)
        }

        fun bind(crime: Crime) {
            this.crime = crime
            titleTextView.text = crime.title
            dateTextView.text = crime.date.toString()
            solvedImageView.visibility = if(crime.isSolved) {
                View.VISIBLE
            } else {
                View.GONE
            }
        }

        override fun onClick(v: View) {
            callbacks?.onCrimeSelected(crime.id)
        }
    }

    private inner class CrimeListAdapter(var crimes: List<Crime>)
        : ListAdapter<Crime, CrimeHolder>(DiffCallback()) {

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CrimeHolder {
            val view =
                layoutInflater.inflate(R.layout.list_item_crime, parent, false)

            return CrimeHolder(view)
        }

        override fun onBindViewHolder(holder: CrimeHolder, position: Int) {
            holder.bind(crimes[position])
        }
    }

    private inner class DiffCallback: DiffUtil.ItemCallback<Crime>() {

        override fun areItemsTheSame(oldItem: Crime, newItem: Crime): Boolean {
            return oldItem.id == newItem.id
        }

        override fun areContentsTheSame(oldItem: Crime, newItem: Crime): Boolean {
            return oldItem == newItem
        }
    }
}

这是片段的视图模型:

class CrimeListViewModel: ViewModel() {

    private val crimeRepository = CrimeRepository.get()
    val crimeListLiveData = crimeRepository.getCrimes() //returns LiveData<List<Crime>>
}

Android 文档中有关于ListAdapter的内容:

虽然使用LiveData是向适配器提供数据的一种简单方法,但这不是必需的 - 您可以在新列表可用时使用submitList(List) 。

  1. 我应该在每次更新 UI 时提交一个新列表,而不是创建一个新的ListAdapter对象。crimeRecyclerView.adapter没有任何.submitList()作用。那么如何传递新列表呢?

  2. LiveData对我来说仍然是新的,所以我对此不太清楚。我已经观察到存储在我的视图模型中的LiveData 。那么这次我观察什么呢?还是我只是将代码添加到我现有的Observer中?

  3. 最后,当我在这种状态下运行代码时,手机显示一个空的RecyclerView。只UpdateUI()被调用,没有任何CrimeListAdapter函数被调用。我不确定这是一个真正的问题还是上述问题的结果。

标签: androidkotlinandroid-recyclerviewandroid-livedataandroid-listadapter

解决方案


1.我应该在每次更新 UI 时提交一个新列表,而不是创建一个新的 ListAdapter 对象。但是 crimeRecyclerView.adapter 没有 .submitList() 函数。那么如何传递新列表呢?

crimeRecyclerView.adapter返回 RecyclerView.Adapter 类型

submitList()是ListAdapter的一个方法,是RecyclerView.Adapter的子类

在调用该方法之前,您需要从超类转换为子类,就像这样。

(crimeRecyclerView.adapter as CrimeListAdapter).submitList(crimes)

2.LiveData 对我来说仍然是新的,所以我对此不太清楚。我已经观察到存储在我的视图模型中的 LiveData。那么这次我观察什么呢?还是我只是将代码添加到我现有的观察者?

你这部分的代码很好,不需要做更多。

3.最后当我在这种状态下运行代码时,手机显示一个空的RecyclerView。只有 UpdateUI() 被调用,CrimeListAdapter 的任何函数都没有被调用。我不确定这是一个真正的问题还是上述问题的结果。

使用 ListAdapter 最好的部分是您不需要向构造函数提供数据列表(在您的情况下是犯罪)。

回到您的代码,您需要更改 3 件事。

// crimeRecyclerView.adapter = CrimeListAdapter(emptyList())
crimeRecyclerView.adapter = CrimeListAdapter()

// crimeRecyclerView.adapter = CrimeListAdapter(crimes)
(crimeRecyclerView.adapter as CrimeListAdapter).submitList(crimes)

//private inner class CrimeListAdapter(var crimes: List<Crime>) :
//    ListAdapter<Crime, CrimeHolder>(DiffCallback()) {
//
//    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CrimeHolder {
//        val view = layoutInflater.inflate(R.layout.list_item_crime, parent, false)
//        return CrimeHolder(view)
//    }
//
//    override fun onBindViewHolder(holder: CrimeHolder, position: Int) {
//        holder.bind(crimes[position])
//    }
//}

private inner class CrimeListAdapter : ListAdapter<Crime, CrimeHolder>(DiffCallback()) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CrimeHolder {
        val view = layoutInflater.inflate(R.layout.list_item_crime, parent, false)
        return CrimeHolder(view)
    }

    override fun onBindViewHolder(holder: CrimeHolder, position: Int) {
        holder.bind(getItem(position))
    }
}

推荐阅读