如何从 Adapter 设置 LiveData?

How to set LiveData from Adapter?

我有两个片段:(1)图书馆片段,(2)书籍片段

图书馆片段通过 RecyclerView 显示所有可用的书籍。用户可以点击每个 RecyclerView 项目,这会将 LiveData 设置为相应的书籍。同时会打开书本碎片,显示那本书的内容

我在库片段 RecyclerView.Adapter 内的 ViewHolder class 中设置了一个 onClickListener。因此,当单击一个项目时,将设置实时数据,然后通过导航组件导航到 Book 片段。 Book Fragment在实时数据上有一个观察者并显示它。

正如您在下面的代码中看到的,我正在将 viewmodel 实例传递给适配器,这是不正确的......?或者是吗?应该怎么做?

库片段代码片段

class LibraryFragment : Fragment() {
    [...]
    private val model: SharedViewModel by activityViewModels()
    private lateinit var gridlayoutManager: GridLayoutManager
    private lateinit var thumbnailAdapter: ThumbnailAdapter
    private lateinit var thumbnailRecyclerView: RecyclerView
    private lateinit var selectedFolder: Uri

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = layoutInflater.inflate(R.layout.fragment_thumbnail_viewer, container, false)
        initRecyclerView(view)
        view.allFolderPicker.setOnClickListener {
            pickFolder()
        }
        view.switchFragment.setOnClickListener {
            findNavController().navigate(R.id.action_allFoldersFragment_to_oneFolderFragment)
        }
        return view
    }

    private fun initRecyclerView(view: View) {
        thumbnailRecyclerView = view.findViewById(R.id.thumbnail_recycler_view)
        gridlayoutManager =
            GridLayoutManager(requireContext(), 3, LinearLayoutManager.VERTICAL, false)
        thumbnailRecyclerView.layoutManager = gridlayoutManager
        thumbnailAdapter = ThumbnailAdapter(model)
        thumbnailAdapter.setThumbnailList(listOf())
        thumbnailRecyclerView.adapter = thumbnailAdapter
    }
    [...]
}

适配器Class代码段

class ThumbnailAdapter(model: SharedViewModel) :
    RecyclerView.Adapter<ThumbnailAdapter.CustomViewHolder>() {

    private var thumbnailList = listOf<Pair<String, File>>()
    private val myModel = model

    inner class CustomViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        private val thumbnail = view.thumbnail
        private val title = view.title

        fun bind(item: Pair<String, File>) {
            val titleToSet = item.first
            val bitmapToSet = Util.uriToBitmap(item.second)
            val resizedBitmapToSet = Bitmap.createScaledBitmap(bitmapToSet, 150, 150, false)
            thumbnail.setImageBitmap(resizedBitmapToSet)
            title.text = titleToSet
            itemView.setOnClickListener {
                val folderWithImages = thumbnailList[adapterPosition].second.parentFile
                folderWithImages?.let {
                    myModel.setLibraryFolderList(it)
                    itemView.findNavController()
                        .navigate(R.id.action_allFoldersFragment_to_oneFolderFragment)
                }
            }
        }
    }
    [...]
}

书籍片段代码片段

[...]

class BookViewFragment : Fragment() {

    private lateinit var viewPager: ViewPager2
    private lateinit var viewPagerAdapter: ViewPager2Adapter
    private val model: SharedViewModel by activityViewModels()


    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.fragment_image_viewer, container, false)
        initRecyclerView(view)
        return view
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        model.selectedBookFile.observe(viewLifecycleOwner, androidx.lifecycle.Observer {
            viewPagerAdapter.setImageList(getImageList(it))
            viewPagerAdapter.notifyDataSetChanged()
        })
    }

    private fun initRecyclerView(view: View) {
        viewPager = view.findViewById(R.id.main_image)
        viewPagerAdapter = ViewPager2Adapter()
        viewPagerAdapter.setImageList(listOf())
        viewPager.adapter = viewPagerAdapter
    }

    [...]
}

你不应该那样做,这里有一个关于 RecyclerView 从显示到处理 clickListener 的课程。

他们是这样定义的:

While the ViewHolder is a great place to listen for clicks, it's not usually the right place to handle them. You should usually handle clicks in the ViewModel, because the ViewModel has access to the data and logic for determining what needs to happen in response to the click.

所以你应该这样做:

适配器:

class ThumbnailAdapter(model: SharedViewModel, val clickListener: ThumbnailListener) :
    RecyclerView.Adapter<ThumbnailAdapter.CustomViewHolder>() {

    private var thumbnailList = listOf<Pair<String, File>>()
    private val myModel = model

    override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
        val item = getItem(position)
        holder.itemView.setOnClickListener {
            listener.onClick(item)
        }
        holder.bind(marsProperty)
    }

    inner class CustomViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        private val thumbnail = view.thumbnail
        private val title = view.title

        fun bind(item: Pair<String, File>) {
            val titleToSet = item.first
            val bitmapToSet = Util.uriToBitmap(item.second)
            val resizedBitmapToSet = Bitmap.createScaledBitmap(bitmapToSet, 150, 150, false)
            thumbnail.setImageBitmap(resizedBitmapToSet)
            title.text = titleToSet
        }
    }

    class ThumbnailListener(val clickListener: (libraryId: Long) -> Unit) {
        fun onClick(val library: Library) = clickListener(library.id)
    }

    [...]
}

ViewModel:

private val _navigateToBook = MutableLiveData<Long>()
val navigateToBook: LiveData<Long>()
   get() = _navigateToBook

fun onLibraryClicked(libraryId: Long) {
    // your stuff
    _navigateToBook.value = libraryId
}

片段:

thumbnailAdapter = ThumbnailAdapter(model, ThumbnailListener { libraryId ->
    viewModel.onLibraryClicked(libraryId)
})

viewModel.navigateToBook.observe(viewLifecycleOwner, Observer { book ->
    book?.let {
        this.findNavController().navigate(YOUR_DESTINATION)
    }
})

这次使用数据绑定设置侦听器的第二种可能性:

xml 项目文件:

<data>

<variable
    name="listener"
    type="Your.Package.To.ThumbnailListener" />


</data>

<FrameLayout
    android:layout_width="match_parent"
    android:layout_height="170dp"
    android:onClick="@{() -> listener.onClick(property)}">

    ...

</FrameLayout>

适配器:

fun bind(item: Pair<String, File>, listener: ThumbnailListener) {
            val titleToSet = item.first
            val bitmapToSet = Util.uriToBitmap(item.second)
            val resizedBitmapToSet = Bitmap.createScaledBitmap(bitmapToSet, 150, 150, false)
            thumbnail.setImageBitmap(resizedBitmapToSet)
            title.text = titleToSet
            itemView.clickListener = listener
        }

只需在 Adapter class 中使用您的 Activity 引用而不是 viewLifecycleOwner 就可以了

class ThumbnailAdapter(

    private val activity: FragmentActivity

) : RecyclerView.Adapter<ThumbnailAdapter.CustomViewHolder>() {

    // ... Other functions

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        model.selectedBookFile.observe(activity, androidx.lifecycle.Observer {
            // Your code goes here
        })
    }

    // ... Other functions

}

我知道现在回答已经太晚了。 但我希望它能帮助其他开发人员寻找类似问题的解决方案。

看看LiveAdapter

你只需要在Gradle中添加最新的依赖即可。

dependencies {
    implementation 'com.github.RaviKoradiya:LiveAdapter:1.3.4'
    // kapt 'com.android.databinding:compiler:GRADLE_PLUGIN_VERSION' // this line only for Kotlin projects
}

并将适配器与您的 RecyclerView 绑定

// Kotlin sample
LiveAdapter(
        data = liveListOfItems,
        lifecycleOwner = this@MainActivity,
        variable = BR.item )
       .map<Header, ItemHeaderBinding>(R.layout.item_header) {
           onBind{

           }
           onClick{

           }
           areContentsTheSame { old: Header, new: Header ->
               return@areContentsTheSame old.text == new.text
           }
           areItemSame { old: Header, new: Header ->
               return@areContentsTheSame old.text == new.text
           }
       }
       .map<Point, ItemPointBinding>(R.layout.item_point) {
           onBind{

           }
           onClick{

           }
           areContentsTheSame { old: Point, new: Point ->
               return@areContentsTheSame old.id == new.id
           }
           areItemSame { old: Header, new: Header ->
               return@areContentsTheSame old.text == new.text
           }
       }
       .into(recyclerview)

就是这样。适配器实现无需额外编写代码,观察LiveData并通知适配器

另外,实现了 DiffUtil,这样只有更改的项目才会在 RecyclerView 上更新,而不是全部更新。