This article is published simultaneously on my wechat public account. You can follow it by searching “Guo Lin” on wechat. The article is updated every weekday.

one

Hello, everyone. Last week, I saw such a message under an article on the official account:

What? Is holder.adapterPosition underlined?

The third Line of Code had only just been published when an API was deprecated, so I decided to take a closer look at the issue and write a quick article on it.

Take a look, holder.adapterPosition is not our usual in RecyclerView used to obtain the click position of the method, commonly written as follows:

holder.itemView.setOnClickListener {
	val position = holder.adapterPosition
	Log.d("TAG", "you clicked position $position")
}
Copy the code

I believe everyone has used this method thousands of times, how can this method be abandoned? I checked the documentation on the Android website, and sure enough, the getAdapterPosition() method was marked deprecated:

This method is ambiguous when multiple adapters are nested. If you are in the context of an adapter to call this method, you may want to invoke getBindingAdapterPosition () method. If you want to get the position is as seen in the RecyclerView, you should call getAbsoluteAdapterPosition () method.

Are you still confused after reading this explanation? But I translated as accurately as I could.

After reading this explanation, I cannot understand why there is ambiguity in this method when multiple Adapters are nested. Nesting multiple Adapters makes me think of nesting recyclerViews in RecyclerView, but it seems that Google has not recommended it for a long time and is unlikely to scrap the API for it.

When I was puzzled, I suddenly remembered an article recommended by the public account of Hongyang Dashen next door a few days ago, which told me that Google launched a new MergeAdapter. Something tells me it might have something to do with this new feature.

But MergeAdapter is in RecyclerView 1.2.0 version of the new, and the official website is currently RecyclerView’s latest stable version or 1.1.0. 1.2.0 is still in alpha, not even in beta:

The document is marked as obsolete before the library is stable, which is a bit impatient for Google to do.

two

So what exactly does MergeAdapter do? I simply read the introduction to understand, because this is the function I have always wanted to pursue ah!

Its main function is simply to merge multiple Adapters together.

You might say, why do I have multiple Adapters in my RecyclerView? That’s because you may not have encountered the need, but I did.

When I was working on Giffun two years ago, RecyclerView was used to create the interface for viewing GIF details.

You may not think of this interface as a RecyclerView, but it is. The content of the interface is mainly divided into 3 parts as shown in the figure above.

So how can you display 3 completely different things in RecyclerView? I used several different viewtypes in the Adapter:

override fun getItemViewType(position: Int) = when (position) {
	0 -> DETAIL_INFO
	1 -> if (commentCount == -1) {
		LOADING_COMMENTS
	} else if (commentCount == 0 || commentCount == -2) {
		NO_COMMENT
	} else {
		HOT_COMMENTS
	}
	2 -> ENTER_COMMENT
	else -> super.getItemViewType(position)
}
Copy the code

As you can see, different viewTypes are returned for different positions. When position is 0, return DETAIL_INFO, the GIF detail area. When position is 1, one of LOADING_COMMENTS, NO_COMMENT, or HOT_COMMENTS is returned to display comments. When position is 2, ENTER_COMMENT, the comment input field, is returned.

The source code for Giffun is fully public. You can view the full code for this class here:

Github.com/guolindev/g…

So what’s wrong with writing it this way? The main problem is that the code is too coupled. There is no correlation between these different viewtypes at all, and writing them all into the same Adapter makes the class look bloated and harder to maintain later.

MergeAdapter is designed to solve this situation. It allows you to write separate Adapters that have no business logic associated with them, and then merge them together and set up RecyclerView.

I’m going to use a very simple example here to demonstrate the use of MergeAdapter.

First, make sure you use RecyclerView at least 1.2.0-Alpha02, otherwise there is no MergeAdapter class:

Dependencies {implementation 'androidx. Recyclerview: recyclerview: 1.2.0 - alpha02'}Copy the code

Next we create two very simple Adapters, a TitleAdapter and a BodyAdapter, which we will later merge with MergeAdapter.

The TitleAdapter code is as follows:

class TitleAdapter(val items: List<String>) : RecyclerView.Adapter<TitleAdapter.ViewHolder>() {

    inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val text: TextView = view.findViewById(R.id.text)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.item_view, parent, false)
        val holder = ViewHolder(view)
        return holder
    }

    override fun getItemCount() = items.size

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.text.text = items[position]
    }

}
Copy the code

This is the simplest implementation of an Adapter, with no logic in it, just to display a line of text. Item_view is a simple layout that contains only one TextView control, the code of which is not shown here.

Then the BodyAdapter code looks like this:

class BodyAdapter(val items: List<String>) : RecyclerView.Adapter<BodyAdapter.ViewHolder>() {

    inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val text: TextView = view.findViewById(R.id.text)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.item_view, parent, false)
        val holder = ViewHolder(view)
        return holder
    }

    override fun getItemCount() = items.size

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.text.text = items[position]
    }

}
Copy the code

It’s basically copied code, no different from TitleAdapter.

three

Then we can use it like this in MainActivity:

class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val titleItems = generateTitleItems() val titleAdapter = TitleAdapter(titleItems) val bodyItems = generateBodyItems() val bodyAdapter = BodyAdapter(bodyItems) val  mergeAdapter = MergeAdapter(titleAdapter, bodyAdapter) recyclerView.layoutManager = LinearLayoutManager(this) recyclerView.adapter = mergeAdapter } private fun generateTitleItems(): List<String> { val list = ArrayList<String>() repeat(5) { index -> list.add("Title $index") } return list } private fun generateBodyItems(): List<String> { val list = ArrayList<String>() repeat(20) { index -> list.add("Body $index") } return list } }Copy the code

As you can see, I’ve written the generateTitleItems() and generateBodyItems() methods to generate data sets for the two Adapters, respectively. You then create instances of TitleAdapter and BodyAdapter and merge them together using MergeAdapter. The way to merge is simply to pass all the Adapter instances you want to merge into the MergeAdapter constructor.

Finally, set MergeAdapter to RecyclerView, the whole process is over.

Is it very simple? It’s almost the same as RecyclerView before.

Now run the program and it will look like the picture below:

As you can see, the data in the TitleAdapter and BodyAdapter are displayed together, indicating that our MergeAdapter has been successfully implemented.

So far it’s been pretty straightforward, but I’m going to give you a soul test.

If, at this point, I want to listen for the click events of elements in the BodyAdapter, then I call getAdapterPosition() to get the click positions of elements in the BodyAdapter, or the click positions of the merged elements.

As you can see, the getAdapterPosition() method is already confusing, which is the problem described in the opening paragraph.

The solution is simple, of course, Google abandoned getAdapterPosition () method, but also provides getBindingAdapterPosition () and getAbsoluteAdapterPosition () these two methods. As the name suggests, one is used to retrieve the element’s position in the current binding Adapter, and the other is used to retrieve the element’s absolute position in the Adapter.

If my explanation above is not clear enough, take a look at the following example and you will see the point in no time.

We modify the BodyAdapter code to add a code that listens for the current element click event, as follows:

class BodyAdapter(val items: List<String>) : RecyclerView.Adapter<BodyAdapter.ViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.item_view, parent, false)
        val holder = ViewHolder(view)
        holder.itemView.setOnClickListener {
            val position = holder.bindingAdapterPosition
            Toast.makeText(parent.context, "You clicked body item $position", Toast.LENGTH_SHORT).show()
        }
        return holder
    }

    ...
}
Copy the code

As you can see, this is called getBindingAdapterPosition () method, and through the Toast pop-up current click the position of the element.

Run the program, the effect is as follows:

Obviously, the click position we get is where the element is in the BodyAdapter.

To revise the BodyAdapter the code, will getBindingAdapterPosition () method for getAbsoluteAdapterPosition () method:

class BodyAdapter(val items: List<String>) : RecyclerView.Adapter<BodyAdapter.ViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.item_view, parent, false)
        val holder = ViewHolder(view)
        holder.itemView.setOnClickListener {
            val position = holder.absoluteAdapterPosition
            Toast.makeText(parent.context, "You clicked body item $position", Toast.LENGTH_SHORT).show()
        }
        return holder
    }

    ...
}
Copy the code

Then re-run the program as follows:

As a result, the click position obtained is where the element is located in the merged Adapter.

four

A final conclusion:

  1. If you don’t use the MergeAdapter, getBindingAdapterPosition () and getAbsoluteAdapterPosition the effect of () method is the same.
  2. If you use the MergeAdapter, getBindingAdapterPosition (found) is the location of the elements in the current binding Adapter, While getAbsoluteAdapterPosition () method got the elements in the combined Adapter absolute position.

This article is written here, it is also the beginning of the “Mukong” students put forward a thorough analysis of the problem, I think this article can also be regarded as an “the first line of code 3rd edition” extension of the article.

By the way, since “The first Line of Code 3rd Edition” has been published, all my future articles will use Kotlin language, Java is no longer used, want to learn Kotlin language friends can consider this book.

Since this was my first attempt to write a programming language, I didn’t know anything about it, but after seeing the overwhelming feedback from my first readers, I now have more confidence in the book’s quality. One of my QQ group friends said that he had learned several rounds of Kotlin before, but it was not as good as this book, which made me feel warm in my heart.

Pay attention to my technical public account “Guo Lin”, there are high-quality technical articles pushed every day.