如何从 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 上更新,而不是全部更新。
我有两个片段:(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 theViewModel
, because theViewModel
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 上更新,而不是全部更新。