为 RecyclerView 片段实现 ListAdapter

Implementing ListAdapter for a RecyclerView Fragment

我已经用普通的 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 的内容:

While using a LiveData is an easy way to provide data to the adapter, it isn't required - you can use submitList(List) when new lists are available.

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

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

  3. 最后,当我 运行 代码处于这种状态时,phone 显示一个空的 RecyclerView。仅 UpdateUI() 被调用,CrimeListAdapter 的函数中的 none 被调用。我不确定这是一个真正的问题还是上述问题的结果。

1.I'm supposed to submit a new list instead of creating a new ListAdapter object each time I update the UI. But crimeRecyclerView.adapter has no .submitList() function. So how do I pass on the new list?

crimeRecyclerView.adapter return RecyclerView.Adapter 类型

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

的子class

在调用该方法之前,您需要从 super 转换为 sub class,就像这样。

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

2.LiveData is still new to me so I'm not quite clear on this. I already observe a LiveData stored in my viewmodel. So what do I observe this time? Or do I just add code to my existing Observer?

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

3.Finally when I run the code in this state, phone shows an empty RecyclerView. Only UpdateUI() gets called, none of CrimeListAdapter's functions get called. I'm not sure if this is a real problem or just the consequence of the above.

使用 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))
    }
}