android 新控件使用
Mobile apps have come a long way in terms of offering a rich user experience and have found new ways of structuring and surfacing information in the limited real-estate of a phone screen. Every app these days has some implementation of a “feed” of sorts that users interact with to consume the app’s content.
在提供丰富的用户体验方面,移动应用程序已经走了很长一段路,并且已经在有限的电话屏幕中找到了结构化和显示信息的新方法。 如今,每个应用程序都有某种“提要”的实现,用户可以与之交互以消费应用程序的内容。
On Android, the de-facto way of implementing a feed/scrollable list of content is a RecyclerView. But as the UI for most apps has become more sophisticated, implementing a complex list containing multiple item types is no longer straightforward without falling back on third-party libraries like Epoxy.
在Android上,实现提要/可滚动内容列表的实际方法是RecyclerView 。 但是,随着大多数应用程序的UI变得越来越复杂,包含多个项目类型的复杂列表的实现不再变得简单,而无需依靠Epoxy等第三方库。
To make handling multiple feed item types whilst still maintaining clean abstractions between them in code, Google recently announced MergeAdapter, a new class available in RecyclerView version 1.2.0-alpha02 onwards. MergeAdapter reduces the friction when you want to “merge” multiple adapters “sequentially” within a RecyclerView.
为了处理多种供稿商品类型,同时仍在代码中保持它们之间的简洁抽象,Google最近发布了MergeAdapter,这是RecyclerView版本1.2.0-alpha02及更高版本中可用的新类。 当您要在RecyclerView中“顺序”“合并”多个适配器时,MergeAdapter可以减少摩擦。
In this short post, we will take a look at how to implement a screen containing a RecyclerView with a fixed header item on top. This UI pattern is quite common in situations where you want to give the user additional context about the information or surface certain unexplored features in the app in a non-intrusive manner as shown below.
在这篇简短的文章中,我们将看一下如何实现一个包含RecyclerView的屏幕,该屏幕顶部带有一个固定的标题项。 在您希望为用户提供有关信息的其他上下文或以非侵入方式显示应用程序中某些未开发功能的情况下,这种UI模式非常常见,如下所示。
The screen we will be implementing is pretty straightforward, it shows members of the mobile team at Buffer along with a helpful header as the first item as shown below.
我们将实现的屏幕非常简单,它在Buffer中显示了移动团队的成员,并在第一项中显示了一个有用的标题,如下所示。
You can download the code for the project here
您可以在此处下载该项目的代码
The header card item is dismissible. Let’s get started building this 🙌
抬头卡项目是不可接受的。 让我们开始构建这个🙌
相依性 (Dependency)
In order to use the MergeAdapter class, you need to ensure you are using the correct RecyclerView dependency. It’s available starting version 1.2.0-alpha02. Open your app level build.gradle file and append the following to your dependencies list
为了使用MergeAdapter类,您需要确保使用正确的RecyclerView依赖项。 从1.2.0-alpha02版本开始可用。 打开应用程序级别的build.gradle文件,并将以下内容添加到依赖项列表中
implementation 'androidx.recyclerview:recyclerview:1.2.0-alpha03'
implementation 'androidx.recyclerview:recyclerview:1.2.0-alpha03'
建立 (Setup)
The screen is divided into two distinct visual element types which will use their own independent adapters
屏幕分为两种不同的视觉元素类型,它们将使用它们自己的独立适配器
The header card which is represented by the WhatsNewAdapter class in code
由代码中的WhatsNewAdapter类表示的头卡
The teammate card which is represented by the BufferooAdapter class in code.
在代码中由BufferooAdapter类表示的队友卡。
There’s nothing out of the ordinary going on in the BufferooAdapter so I won’t cover it here, you can take a look at the code here.
BufferooAdapter中没有任何异常,因此我在这里不介绍它,您可以在此处查看代码。
The WhatsNewAdapter does have a few minor details that are worth looking at.
WhatsNewAdapter确实有一些小细节值得一看。
class WhatsNewAdapter(private val listener: WhatsNewListener) : RecyclerView.Adapter<WhatsNewAdapter.WhatsNewViewHolder>() {
//1
var whatsNew: WhatsNew? = null
//2
private fun hasWhatsNewData(field: WhatsNew?): Boolean {
return !field?.description.isNullOrEmpty()
}
override fun getItemCount() = if (hasWhatsNewData(whatsNew)) 1 else 0
//3
override fun getItemViewType(position: Int): Int = R.layout.item_whats_new
interface WhatsNewListener {
fun onDismiss()
}
}
Instead of using a list of items like you may have used most of the time while using an Adapter, the WhatsNewAdapter uses a single object whatsNew.
WhatsNewAdapter并没有使用您可能在大多数情况下使用适配器时都使用过的项目列表,而是使用单个对象whatsNew。
The hasWhatsNewData() method determines how many items the adapter will display, 1 or 0
hasWhatsNewData()方法确定适配器将显示多少个项目,是1还是0
The getItemViewType() returns the resourceID of the header layout item. This is to ensure that if we want to reuse a certain ViewHolder, the same view type isn’t pointing to different ViewHolders. This is more of a best practice to ensure each view type returns a unique identifier.
getItemViewType()返回标题布局项目的resourceID。 这是为了确保如果我们要重用某个ViewHolder,则相同的视图类型不会指向不同的ViewHolders。 这是确保每种视图类型返回唯一标识符的最佳实践。
Let’s hop over to the MainActivity where we are wiring everything together
让我们跳到MainActivity ,将所有内容连接在一起
class MainActivity : AppCompatActivity(), WhatsNewAdapter.WhatsNewListener {
private lateinit var whatsNewAdapter: WhatsNewAdapter
private lateinit var bufferoosAdapter: BufferoosAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initRecyclerView()
}
//1
private fun initRecyclerView() {
whatsNewAdapter = WhatsNewAdapter(this)
bufferoosAdapter = BufferoosAdapter()
feedRecyclerView.adapter = MergeAdapter(whatsNewAdapter, bufferoosAdapter
}
//3
override fun onDismiss() {
whatsNewAdapter.whatsNew = null
whatsNewAdapter.notifyItemRemoved(0)
}
}
We’re doing a few things in the MainActivity.
我们正在MainActivity中做一些事情。
The initRecyclerView() method instantiates the individual adapters in the normal way and then creates a MergeAdapter instance, passes the two individual adapters, and assigns it to the RecyclerView’s adapter property.
initRecyclerView()方法以常规方式实例化单个适配器,然后创建一个MergeAdapter实例,传递两个单独的适配器,并将其分配给RecyclerView的adapter属性。
It’s worth noting that the order in which you pass the individual adapter instances to the MergeAdapter constructor is the order in which they will be rendered, i.e. the header will be shown first followed by the list of teammates
值得一提的是,在您通过个人适配器实例到MergeAdapter构造的顺序是,他们将要呈现的顺序,即头将显示第一后跟队友名单
The onDismiss() method which is invoked on clicking the Dismiss text on the header, assigns null to the whatsNew object in adapter for the header and calls the notifyItemRemoved method to let the adapter change the list UI accordingly.
单击标题上的Dismiss文本时调用的onDismiss()方法,将null分配给标题的适配器中的whatsNew对象,并调用notifyItemRemoved方法,以使适配器相应地更改列表UI。
This was pretty much it. For most conventional use cases this is all you need to do to set up and take advantage of MergeAdapter.
差不多了。 对于大多数常规用例,这是设置和利用MergeAdapter所需要做的全部工作。
But at this point, you may be wondering.
但是在这一点上,您可能想知道。
Why go through the hassle of using MergeAdapter? There are other ways to build this UI.
为什么要经历使用MergeAdapter的麻烦? 还有其他方法可以构建此UI。
You are absolutely correct. This can be achieved in a variety of different ways.
你是绝对正确的。 这可以通过多种不同方式来实现。
- You could have a single RecyclerView with multiple ViewHolder types i.e. the Heterogeneous Layout approach. 您可以具有多个ViewHolder类型(即异构布局方法)的单个RecyclerView。
- The header does not necessarily need to be a RecyclerView item, it can be a card with a GONE visibility attribute in the parent layout. 标题不一定是RecyclerView项,它可以是父布局中具有GONE可见性属性的卡片。
Both of these approaches are correct and doable but both have tradeoffs in terms of abstraction and flexibility.
这两种方法都是正确且可行的, 但在抽象性和灵活性方面都有权衡。
In the case of having a single RecyclerView with multiple ViewHolders, we lose out on clean abstractions. Consider the scenario where you already have 5 different ViewHolder types in a RecyclerView and your PM reaches out to you asking you to implement a promotional card item that will be on the top of the list which will let users avail discount on your products.
在具有多个ViewHolders的单个RecyclerView的情况下,我们将失去干净的抽象。 考虑以下情况:您在RecyclerView中已经有5种不同的ViewHolder类型,并且您的PM要求您实施促销卡项目,该项目将位于列表的顶部,这将使用户可以在您的产品上享受折扣。
It will definitely be a considerable effort accounting for a new item and it’s positioning in the list, not to mention accounting for the position change upon interaction. It will also unnecessarily introduce the header view type’s logic into the main adapter when it should ideally be isolated away.
考虑到新项目及其在列表中的位置,这绝对是一项相当大的努力,更不用说考虑交互作用时的位置变化了。 当理想情况下,它也将不必要地将头视图类型的逻辑引入主适配器。
You can imagine how quickly the class can get out of control orchestrating different view types.
您可以想象该类在编排不同视图类型时会很快失控。
In the second approach, if the parent Fragment/Activity is already fairly nested and complex with a lot of different things going on in it, adding yet another ViewGroup can get messy and you may unintentionally end up breaking a few things here and there.
在第二种方法中,如果父Fragment / Activity已经相当嵌套且复杂,并且其中发生了许多不同的事情,则添加另一个ViewGroup可能会变得混乱,您可能会无意间在这里和那里破坏一些东西。
By using MergeAdapter you are able to
通过使用MergeAdapter,您可以
- Abstract away the header’s functionality into its own file 将标头的功能抽象到自己的文件中
- Retain a flat view hierarchy in the parent layout because you are able to use multiple adapters in the same RecyclerView. 在父布局中保留平面视图层次结构,因为您可以在同一RecyclerView中使用多个适配器。
Of course, MergeAdapter is not a silver bullet by any means, its best suited for situations where the view types and their positions are known/predetermined.
当然,MergeAdapter绝不是灵丹妙药,它最适合于已知/预定视图类型及其位置的情况。
It also makes it easy to introduce change into one view type without necessarily affecting the others.
它还使将更改引入一种视图类型变得容易,而不必影响其他视图类型。
要避免的陷阱 (Pitfalls to avoid)
There are a couple of key things to keep in mind while working with MergeAdapter
使用MergeAdapter时,需要牢记一些关键事项
- Each adapter passed into the MergeAdapter constructor will maintain its own pool of ViewHolders. If you do however need to use the ViewHolders across multiple adapters you must reuse them. This can be done quite simply by using a MergeAdapter.Config object and setting the isolateViewTypes property to true 传递给MergeAdapter构造函数的每个适配器都将维护自己的ViewHolders池。 但是,如果确实需要跨多个适配器使用ViewHolders,则必须重用它们。 这可以通过使用MergeAdapter.Config对象并将isolateViewTypes属性设置为true来非常简单地完成。
val config = MergeAdapter.Config.Builder().setIsolateViewTypes(true)
.build()
Always prefer specific notify events over notifyDataSetChanged(). Specific notify events are efficient and prevent overdraw and also give you pretty animations out of the box. If you extend the ListAdapter class these will come for free but implementing DiffUtils callbacks is also fairly straightforward. Another reason to avoid using notifyDataSetChanged() is the way in which MergeAdapter behaves. If one of the adapters passed to the MergeAdapter calls notifyDataSetChanged the MergeAdapter will also end up calling notifyDataSetChanged(). So as a general rule of thumb, prefer granular updates over force redraws.
始终 优先使用特定的notify事件,而不是notifyDataSetChanged() 。 特定的通知事件非常有效,可以防止透支,并且还为您提供了精美的动画效果。 如果扩展ListAdapter类,这些将免费提供,但实现DiffUtils回调也相当简单。 避免使用notifyDataSetChanged()的另一个原因是MergeAdapter的行为方式。 如果传递给MergeAdapter的适配器之一调用notifyDataSetChanged,则MergeAdapter也将最终调用notifyDataSetChanged() 。 因此,作为一般经验法则, 与强制重绘相比 , 更喜欢粒度更新 。
If you used ViewHolder.getAdapterPosition() method in the past, to get the position of a ViewHolder item in the adapter, it will no longer work with the MergeAdapter as we’ve “merged” multiple adapters into one. Use ViewHolder.getBindingAdapterPosition()instead and if you are reusing ViewHolders across adapters, make use of ViewHolder.getBindingAdapter() to get the adapter that had last bound a particular ViewHolder.
如果您过去使用ViewHolder.getAdapterPosition()方法来获取ViewHolder项在适配器中的位置,则它将不再与MergeAdapter一起使用,因为我们已将多个适配器“合并”为一个。 请改用ViewHolder.getBindingAdapterPosition() ,如果要在适配器之间重用ViewHolders,请使用ViewHolder.getBindingAdapter()来获取最后绑定特定ViewHolder的适配器。
结论 (Conclusion)
That’s all folks! I hope you enjoyed this short post. Let me know if you have any questions in the comments below. You can find the code for the sample project here. Until next time!
那是所有人! 希望您喜欢这篇短文。 如果您在下面的评论中有任何疑问,请告诉我。 您可以在此处找到示例项目的代码。 直到下一次!
翻译自: https://proandroiddev.com/playing-with-the-new-mergeadapter-on-android-8da514c45ca6
android 新控件使用
所有评论(0)