drakeet / MultiType

Flexible multiple types for Android RecyclerView.
Apache License 2.0
5.76k stars 749 forks source link

Feature Request: Add data mapper #199

Closed sablib closed 3 years ago

sablib commented 6 years ago

按照现有的使用方式,在注册 ViewBinder 的时候, ViewBinder内的数据类型必须和调用 register 方法时参数类型一致。 这里希望可以添加一种方法,使得在注册的时候可以对数据类型进行映射,实现以下功能。

data class PostWrapper(val post: Post)

adapter.register(PostWrapper::class.java)
    .withDataMapper { wrapper -> wrapper.post }
    .to(PostViewBinder())

这样如果我有多种数据类型在展示的话,就可以使用 sealed class 将多种类型包起来,

sealed class ItemType {
    data class Banners(val banners: List<RealBannerType>: ItemType()
    data class Post(val post: RealPostType): ItemType()
}

这样各个页面就不必把数据都写为 List<Any> 了,然后 ViewBinder 部分还是可以在多个页面间共用。

drakeet commented 6 years ago

感谢建议,其实 MultiType 旧版曾经提供了 flatten adapter 来进行 item map,后来由于诸多原因,其中最主要的原因是,使得 item 和 binder 映射关系变得隐秘,失去了简单性和直观性,因此此功能已经被删除了。不过你的再度提议也有道理,可以考虑。

drakeet commented 6 years ago

根据你的建议,map 动作在注册链中,可以一定程度缓解映射直观性下降问题,十分不错的建议,近期如果有时间,我们可以实现一个版本看看。

drakeet commented 5 years ago

你好,我最近尝试了一番,这个需求难以实现,而且其实是个伪需求。 对于你举的例子:

adapter.register(PostWrapper::class.java)
    .withDataMapper { wrapper -> wrapper.post }
    .to(PostViewBinder())

如果要做到类型安全,那么到 .to(PostViewBinder()) 前,输入的类型必须确定。而实际情况是,.withDataMapper { wrapper -> wrapper.post } 要获得入参,得推迟到 link 阶段,因为这里的 wrapper 是鲜活的实例。这就导致了我们无法在注册阶段构建出新的 OneToManyBuilder<Post>,因此出现矛盾。

另外,实际上 MultiType 原本就支持你所说的「不必把数据都写为 List<Any> 」,对于 sealed class 也是支持的,示例如下:

class SealedActivity : MenuBaseActivity() {

  val items = ArrayList<ItemType>()

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_list)
    val recyclerView = findViewById<RecyclerView>(R.id.list)
    val adapter = MultiTypeAdapter(items)
    adapter.register(Post::class, PostViewBinder())
    items.add(Post("This is a sub class of sealed ItemType"))

    recyclerView.adapter = adapter
  }
}

sealed class ItemType
data class Banners(val size: Int) : ItemType()
data class Post(val title: String) : ItemType()

class PostViewBinder : ItemViewBinder<Post, PostViewBinder.ViewHolder>() {

  override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup): ViewHolder {
    return ViewHolder(inflater.inflate(R.layout.item_post, parent, false))
  }

  override fun onBindViewHolder(holder: ViewHolder, item: Post) {
    holder.setData(item)
  }

  class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

    val title: TextView = itemView.findViewById(R.id.title)

    fun setData(post: Post) {
      title.text = post.title
    }
  }
}

可以见到,我们完全可以声明 val items = ArrayList<ItemType>() 而投入其子类实例,这符合你说的「不必把数据都写为 List<Any> 」。

实际上,MultiType 不止于此,如果我们能够继承上面的 Post,那么我们甚至可以往 items 中投入 Post 的子类实例,并且只要注册 adapter.register(Post::class, PostViewBinder()) 即可,不必再注册这个 Post 的子类,因为 MultiType 如果找不到注册的类型,会默认通过 isAssignableFrom 去寻找实例的父类及其 binder。这么设计的好处是,一切都是类型安全的,如果你需要对于这个 Post 的子类提供更具体的 binder,应该去显式注册它。

因此,如果没有更多讨论,这个 issue 将在近期关闭。

sablib commented 5 years ago

我上面设想的主要是下面这种场景

sealed class ItemType1 {
    data class Banners(val banners: List<RealBannerType>): ItemType1()
    data class Post(val post: RealPostType): ItemType1()
}
sealed class ItemType2 {
    data class Ad(val Ad: RealAdType): ItemType2()
    data class Post(val post: RealPostType): ItemType2()
}
class PostViewBinder : ItemViewBinder<RealPostType, PostViewBinder.ViewHolder>() {
  ......
  class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) 
}

像这样如果多个地方有各自的 ItemType,各自有一个 RealPostTypewrapper Post,如果 post 的展示是完全一样的,那么如果 binder部分能够写为 ItemViewBinder<RealPostType, ...> 的话就可以在各个地方使用。

drakeet commented 3 years ago

根据我多年使用 MultiType 的经历看来,我自己没有这方面的需求,而且这个 issue 长年来也没有其他人请求支持,因此将关闭。