具有RecyclerView的Android动态视图

During several years as an Android Developer, I always had the RecyclerView as a partner for many of the features that I delivered for all the companies that I’ve worked for. But each new feature often leads me to face a harder challenge, which brings me experience and some bugs as well.

在作为Android开发人员的几年中,我一直是RecyclerView的合作伙伴,为我为所服务的所有公司提供的许多功能。 但是每个新功能通常都会使我面临更艰巨的挑战,这也给我带来了经验和一些错误。

One of the greatest challenges that I’ve come across is to bind different Views and ViewHolders in the same List. This way I need to implement the getItemViewType(…) method and manually pass to my RecyclerView’s Adapter all the Integer Types available, and to share views between Screens and Adapters I must manually put ViewHolders on it.

我遇到的最大挑战之一是将不同的View和ViewHolders绑定在同一List中。 这样,我需要实现getItemViewType(…)方法,并将所有可用的整数类型手动传递给RecyclerView的Adapter,并在屏幕和适配器之间共享视图,我必须手动将ViewHolders放在上面。

I’ve come with a solution that solves all (or almost all) the problems we have while dealing with multiple ViewHolders type. In this article, I will explain the Dynamic Structure for RecyclerViews.

我提供了一个解决方案,可以解决在处理多个ViewHolders类型时遇到的所有(或几乎所有)问题。 在本文中,我将解释RecyclerViews的动态结构

Let's talk about code!

让我们谈谈代码!

The first thing We need to talk about is how to let view types dynamic, and the answer is your model!

我们需要谈论的第一件事是如何使视图类型动态化 ,答案就是您的模型!

We can create a Model that tells us the Type of a view and how to cast it. My model has a sample structure with two parameters (Key and Value) that tell the adapter the view type.

我们可以创建一个模型,告诉我们视图的类型以及如何投射视图。 我的模型有一个带有两个参数( KeyValue )的样本结构,这些参数告诉适配器视图类型。

data class SimpleVO(val key: String, val value: Any?)

The key is a String parameter that represents my view type, but the getItemViewType(…) needs to return an Integer, then how can we convert this?

关键是一个代表我的视图类型的String参数,但是getItemViewType(…)需要返回一个Integer,那么我们如何转换它呢?

I created an Enum class that is called DynamicComponent. This class has all my view types and all I have to do is call what type is the key that was passed.

我创建了一个名为DynamicComponent的Enum类。 此类具有我的所有视图类型,我所要做的就是调用传递的键是哪种类型。

enum class DynamicComponent() {
FIRSTCOMPONENT, SECONDCOMPONENT;companion object {
fun getDynamicComponentByName(name: String?): DynamicComponent? = name?.let {
return try
{
valueOf(it.toUpperCase(Locale.getDefault()))
} catch (ignored: Exception) {
null
}
}
}
}

In my adapter, I just need to call this method and get the Enum’s ordinal value.

在我的适配器中,我只需要调用此方法并获取Enum的序数值即可。

override fun getItemViewType(position: Int): Int {
vos[position].let { simpleVO ->
DynamicComponent
.getDynamicComponentByName(simpleVO.key)
?.ordinal
?.let {
return it
}
}
return super
.getItemViewType(position)
}

And that’s it! Now you have a dynamic view type. All you need to do is register your keys in DynamicComponent class, and then create your models with your key and value parameters like this:

就是这样! 现在您有了动态视图类型。 您需要做的就是在DynamicComponent类中注册键,然后使用参数创建模型,如下所示:

val list = 
listOf(
SimpleVO(
DynamicComponent.FIRSTCOMPONENT.name,
/*or just FIRSTCOMPONENT string*/
"any value you want"))

PS: You can retrieve this model from your backend, and delegate the responsibility to choose what view and order will be shown to the user.

PS:您可以从后端检索此模型,并委派责任选择要向用户显示的视图和顺序。

But not all problems are solved! You still have to create your ViewHolders and bind it. For this, there is a solution as well.

但并非所有问题都能解决! 您仍然必须创建ViewHolders并将其绑定。 为此,也有一个解决方案。

If you want your adapter to be really Dynamic you also need to create and bind your holders dynamically. I created a class named ViewRenderer, and it has the responsibility to get and create the correct ViewHolder, and to bind your view. The abstract class looks like this:

如果您希望适配器真正是动态的,则还需要动态创建和绑定持有人。 我创建了一个名为ViewRenderer的类,它负责获取和创建正确的ViewHolder以及绑定视图。 抽象类如下所示:

abstract class ViewRenderer<VH : RecyclerView.ViewHolder>
(val viewType: Int) {
abstract fun bindView(model: SimpleVO,
holder: VH)

abstract fun createViewHolder(parent: ViewGroup): VH
}

Then, you can implement this abstract class and create your own ViewRenderer and ViewHolder, like this:

然后,您可以实现此抽象类并创建自己的ViewRenderer和ViewHolder,如下所示:

class FirstComponentViewHolder(val textView : TextView) : RecyclerView.ViewHolder(textView)class FirstComponentViewRenderer : ViewRenderer<FirstComponentViewHolder>(DynamicComponent.FIRSTCOMPONENT.ordinal){

override fun bindView(model: SimpleVO,
holder: FirstComponentViewHolder) {
holder.textView.text = model.value as? String
}
override fun createViewHolder(parent: ViewGroup)
: FirstComponentViewHolder =
FirstComponentViewHolder(TextView(parent.context))
}

In createViewHolder(…) method you need to Create your ViewHolder instance as Declared in class constructor. To inflate the view you can just instantiate it, ou use LayoutInflater to get an xml file using the parent context that is received as parameter.

createViewHolder(…)方法中,您需要按照类构造函数中的声明创建ViewHolder实例。 要扩展视图,您可以实例化它,或者使用LayoutInflater使用作为参数接收的父上下文获取xml文件。

In bindView(…) you receive the model and ViewHolder as parameter. Then, you can bind your view as you want. Just access it in the holder and set all the data is needed.

bindView(…)中,您将收到模型和ViewHolder作为参数。 然后,您可以根据需要绑定视图。 只需在保存器中访问它并设置所有数据即可。

But how will the adapter know your renderer? For this, the solution is a list of available renderers that you can register and call in onCreateViewHolder(…) and onBindViewHolder(…). Like this:

但是适配器如何知道您的渲染器? 为此,解决方案是可用渲染器的列表,您可以注册并调用onCreateViewHolder(...)onBindViewHolder(...) 。 像这样:

override fun onCreateViewHolder(parent: ViewGroup,
viewType: Int): RecyclerView.ViewHolder =
renderers.get(viewType)?.let {
return it
.createViewHolder(parent)
} ?: EmptyViewHolder(FrameLayout(parent.context))override fun onBindViewHolder(holder: RecyclerView.ViewHolder,
position: Int) {
vos[position]
.let { simpleVO ->
DynamicComponent
.getDynamicComponentByName(simpleVO.key)
?.ordinal
?.let {
renderers
.get(it)?.bindView(simpleVO,
holder)
}
}
}

But to get a renderer you need to register first. Create a registration method like this:

但是要获得渲染器,您需要先注册。 创建这样的注册方法:

var renderers = SparseArray<ViewRenderer<RecyclerView.ViewHolder>>()fun registerRenderer(renderer: ViewRenderer<*>) {
if (renderers.get(renderer.viewType) == null)
renderers.put(renderer.viewType,
renderer as ViewRenderer<RecyclerView.ViewHolder>)
}

To call it, just create a ViewRenderer Instance, and call Adapters registerRenderer(…) method like this:

要调用它,只需创建一个ViewRenderer实例,然后像这样调用Adapters registerRenderer(…)方法:

val dynamicAdapter = DynamicAdapter(listOf())val viewRenderer = FirstComponentViewRenderer()
dynamicAdapter.registerRenderer(viewRenderer)

You can register all renderers you want, and every renderer will handle ViewHolder’s creation and binding.

您可以注册所需的所有渲染器,每个渲染器将处理ViewHolder的创建和绑定。

结论 (Conclusion)

With this structure, you can now use a single adapter for any screen and view component you need, and just create your ViewRenderes and Views as I have shown above. You can use Frameworks like DataBinding to empower this solution too.

通过这种结构,您现在可以为所需的任何屏幕和视图组件使用单个适配器,只需创建您的ViewRenderesViews即可,如上所示。 您也可以使用诸如DataBinding之类的框架来实现此解决方案。

Any doubt let me know in comments please. Thanks!!

如有任何疑问,请在评论中通知我。 谢谢!!

翻译自: https://medium.com/gustavo-santorio/android-dynamic-views-with-recyclerview-c2974c96a85f

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值