what? The interface to get the click position in RecyclerView is abandoned?

This article is published on my WeChat public account at the same time. Scan the QR code at the bottom of the article or search for Guo Lin on WeChat to follow. Every weekday there is an article update.

Good morning, fellow friends. Last week, I saw this message under an article in the public account:

what? Holder.adapterPosition is crossed and not recommended?

"The third line of code" has just been published, and even APIs have been deprecated. I decided to study this problem and write an article for analysis.

If you take a closer look, is this the method that holder.adapterPosition is usually used to get the click position in RecyclerView?

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

This method believes that everyone has used it thousands of times. How could this method be abandoned? So I went to Android's official website to check the documentation. Sure enough, the getAdapterPosition () method was marked as obsolete:

I help you translate this English: this method will be ambiguous when multiple adapters are nested. If you are calling this method in the context of an adapter, you might want to call the getBindingAdapterPosition () method. If the position you want to obtain is as seen in RecyclerView, you should call getAbsoluteAdapterPosition () method.

After reading this explanation, is it still a dumb face? But I have translated as accurately as possible.

After reading this explanation, I can't understand it. Why is this method ambiguous when multiple adapters are nested? The nesting of multiple adapters makes it easy for me to think of nesting RecyclerView in RecyclerView, but it seems that Google has not recommended this practice for a long time, and it is even less likely to abandon the API for this practice.

When I was puzzled, I suddenly remembered that I recommended an article in the public account of the great God next door a few days ago, about Google's new MergeAdapter. Intuition tells me that it may be related to this new feature.

However, MergeAdapter was added in RecyclerView 1.2.0 version, and the latest stable version of RecyclerView on the official website is still 1.1.0. 1.2.0 is still in the alpha stage, not even the beta stage:

The library is not yet stable, but the document is first marked as obsolete, and Google's approach is really a bit impatient.

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

Its main function is very simple, is 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 such a demand, and I have encountered it.

When I was working on the giffun project two years ago, the interface for viewing the details of GIF images was done using RecyclerView.

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

So how can 3 different contents be displayed in one RecyclerView? I used a variety of different viewTypes in the Adapter to achieve:

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)
}

As you can see, according to different positions, different viewTypes are returned. When position is 0, return DETAIL_INFO, which is the gif details area. When position is 1, return one of LOADING_COMMENTS, NO_COMMENT, HOT_COMMENTS, used to display the content of the comment. When position is 2, return to ENTER_COMMENT, which is the comment input box area.

The source code of giffun is completely public, you can view the complete code of this class here:

https://github.com/guolindev/giffun/blob/master/main/src/main/java/com/quxianggif/feeds/adapter/FeedDetailMoreAdapter.kt

So is there any problem with this way of writing? The main problem is that the code coupling is too high. In fact, there is no correlation between these different viewTypes. Writing them into the same Adapter will make this class look bloated, and it will be more difficult to maintain later.

The MergeAdapter appeared to solve this situation. It allows you to write several Adapters that are not associated with business logic separately, and finally merge them together and set them to RecyclerView.

Here I am going to use a very simple example to demonstrate the usage of MergeAdapter.

First, make sure that the version of RecyclerView you are using is not lower than 1.2.0-alpha02, otherwise there is no MergeAdapter class:

dependencies {
    implementation 'androidx.recyclerview:recyclerview:1.2.0-alpha02'
}

Next, create two very simple Adapters, a TitleAdapter and a BodyAdapter, and later we will use MergeAdapter to merge the two Adapters together.

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]
    }

}

This is the simplest implementation of an Adapter, without any logic in it, just to display a line of text. item_view is a simple layout that contains only one TextView control, and the code in it is not shown here.

Then the code of BodyAdapter is as follows:

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]
    }

}

Basically, the copied code is no different from TitleAdapter.

Then we can use it in MainActivity like this:

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
    }
	
}

As you can see, here I have written two methods, generateTitleItems () and generateBodyItems (), which are used to generate data sets for the two adapters. Then create instances of TitleAdapter and BodyAdapter and use MergeAdapter to merge them together. The method of merging is very simple, that is, all the instances of Adapter you want to merge are passed into the construction method of MergeAdapter.

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

Is it very simple? There is almost no difference from the previous usage of RecyclerView.

Now run the program, the effect is shown below:

As you can see, the data in TitleAdapter and BodyAdapter are merged and displayed together, which also means that our MergeAdapter has successfully taken effect.

It's quite easy to understand so far, but next, I will give you a soul torture.

If at this time, I want to monitor the click event of the element in the BodyAdapter, then call the getAdapterPosition () method to get the click position of the element in the BodyAdapter or the click position of the element after the merge?

You will find that this time the getAdapterPosition () method has caused ambiguity, which is the problem described in the opening paragraph.

The solution is of course very simple, Google abandoned the getAdapterPosition () method, but it also provides two methods getBindingAdapterPosition () and getAbsoluteAdapterPosition (). As you can see from the name, one is used to get the position of the element in the currently bound Adapter, and the other is used to get the absolute position of the element in the Adapter.

If you think my explanation above is not clear enough, you can understand it immediately by looking at the following example.

We modify the code in the BodyAdapter to add the code to monitor the click event of the current element, as shown below:

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
    }

    ...
}

As you can see, the getBindingAdapterPosition () method is called here, and the position of the currently clicked element is popped up through Toast.

Run the program, the effect is shown below:

Obviously, the click position we obtained is the position of the element in the BodyAdapter.

Then modify the code in BodyAdapter and replace getBindingAdapterPosition () method with 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
    }

    ...
}

Then re-run the program as follows:

The result shows at a glance that the click position obtained is the position of the element in the merged Adapter.

Finally, sort out the conclusion:

  1. If you are not using MergeAdapter, the effect of getBindingAdapterPosition () and getAbsoluteAdapterPosition () methods are exactly the same.
  2. If you use MergeAdapter, getBindingAdapterPosition () gets the element at the position of the currently bound Adapter, and getAbsoluteAdapterPosition () gets the element at the absolute position of the merged Adapter.

When the article is written here, I will thoroughly analyze the questions raised by the students at the beginning of "Mu Kong". I think this article can also be regarded as an extended article of the first line of code version 3.

In addition, since the first line of code version 3 has been published, all future articles I write will use the Kotlin language, and Java will no longer be used. Friends who want to learn the Kotlin language can consider this book .

Since this is my first attempt to write programming language type content, I was not particularly bottomed, but after seeing the general feedback from the first batch of readers, I am now more convinced of the quality of this book. A group of friends in my QQ group also said that they had learned Kotlin for several rounds before, and none of them had a good description of this book. It made me feel warm.

At the end of the article, as usual, the purchase link of the "first line of code version 3" is given. Friends who need it can click the link below to place an order.

Jingdong purchase address

Dangdang purchase address

Tmall purchase address


Follow my technical public account, there are high-quality technical articles every day.

Scan the QR code below to follow on WeChat:

Published 124 original articles · 20 thousand likes + · 10.54 million views

Guess you like

Origin blog.csdn.net/sinyu890807/article/details/105606409