- Caused by recycler in Android done right!
- Jakub Minarik
- Silly translation: It doesn’t hurt you
This paper mainly discusses and solves two problems, namely, the loss of nested transverse RecyclerView sliding position when the outer RecyclerView is rolling vertically, and the conflict resolution between horizontal and vertical sliding.
So,Here we go !!!
Build a sample program
Without a lot of translation, you build two adapters and go straight to the implementation.
In vertical sliding RecyclerView nested several transverse sliding RecyclerView, through the latest ConcatAdapter to be realized. Need to understand ConcatAdapter more details can look at this article to do “addition” Adapter – actual MergeAdapter, this article launched for the beta version, the official version has been renamed ConcatAdapter, does not affect the understanding and use.
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var concatAdapter: ConcatAdapter
override fun onCreate(savedInstanceState: Bundle?). {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
initViews()
}
private fun initViews(a) {
//create a populated list of sections
val sections = DataSource.createSections(numberOfSections = 50, itemsPerSection = 25)
//create an instance of ConcatAdapter
concatAdapter = ConcatAdapter()
//create AnimalSectionAdapter for the sections and add to ConcatAdapter
val sectionAdapter = AnimalSectionAdapter(sections)
concatAdapter.addAdapter(sectionAdapter)
//setup the recycler
val linearLayoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false)
binding.recyclerView.run {
layoutManager = linearLayoutManager
adapter = concatAdapter
}
}
}
Copy the code
You can take a quick look at the build process, there’s nothing to say.
Problems arising
Loss of sliding position
Through the figure above, we can intuitively see the loss of the sliding position of transverse RecyclerView when it is re-displayed. To solve this problem, there are two steps. Save the slide position on recycling and restore the slide position on rebinding.
class AnimalSectionAdapter(
/ /...
) {
private valscrollStates: MutableMap<String, Parcelable? > = mutableMapOf()private fun getSectionID(position: Int): String {
return items[position].id
}
override fun onViewRecycled(holder: BaseViewHolder<AnimalSection>) {
super.onViewRecycled(holder)
//save horizontal scroll state
valkey = getSectionID(holder.layoutPosition) scrollStates[key] = holder.itemView.findViewById<RecyclerView>(R.id.titledSectionRecycler).layoutManager? .onSaveInstanceState() }override fun onBindViewHolder(
/ /...
) {
//restore horizontal scroll state
val key = getSectionID(viewHolder.layoutPosition)
val state = scrollStates[key]
if(state ! =null) { titledSectionRecycler.layoutManager? .onRestoreInstanceState(state) }else{ titledSectionRecycler.layoutManager? .scrollToPosition(0)}}/ /...
}
Copy the code
Create a MutableMap in Adapter to persist the state, rewrite the onViewRecycled method to hold the corresponding data, key is the location, Value is the serialized object generated after layoutManager calls onSaveInstanceState(). In the onBindViewHolder method, the position is determined when binding, and if state is not empty, the position is restored, otherwise it slides to the front.
Conflict resolution of horizontal and vertical sliding
The original author directly used other people’s solutions, which can be viewed in the original article. A RecyclerView extension function was defined to add touch and slide handling to solve this problem.
fun RecyclerView.enforceSingleScrollDirection(a) {
val enforcer = SingleScrollDirectionEnforcer()
addOnItemTouchListener(enforcer)
addOnScrollListener(enforcer)
}
Copy the code
Other solutions
Recycling pool
When the external RecyclerView scrolls vertically, the nested horizontal RecyclerView reloads the View on one side, because each nested RecyclerView has its own View Pool. Create a shared View Pool for recyclerViews of the same View type, which can reduce the creation of views and improve the performance of vertical scrolling.
In this example project, it is clearly ok to do so.
class AnimalSectionAdapter(
/ /...
) {
//create an instance of ViewPool
private val viewPool = RecyclerView.RecycledViewPool()
//and set it to each nested recycler when binding
override fun onBindViewHolder(
/ /...
) {
/ /...titledSectionRecycler? .run {//right here
this.setRecycledViewPool(viewPool)
this.layoutManager = layoutManager
this.adapter = AnimalAdapter(item.animals)
}
/ /...}}Copy the code
Set the number of preloads
Can be nested RecyclerView LinearLayoutManager, call setInitialPrefetechItemCount () method to the default could display the number of visible. In vertical sliding, the outer RecyclerView will require the inner RecyclerView to pre-bind, but the inner RecyclerView does not know how many items should be pre-loaded until the inner RecyclerView is visible. Other non-preloaded items are loaded (by default, only two items are preloaded), which can cause performance problems.
You can solve this problem by:
class AnimalSectionAdapter(
/ /...
) {
override fun onBindViewHolder(
/ /...
) {
/ /...
val layoutManager = LinearLayoutManager(itemView.context, LinearLayoutManager.HORIZONTAL, false)
//right here
layoutManager.initialPrefetchItemCount = 4 //estimated number of visible items
valtitledSectionRecycler = itemView.findViewById<RecyclerView>(R.id.titledSectionRecycler) titledSectionRecycler? .run {this.layoutManager = layoutManager
/ /...
}
/ /...}}Copy the code
The level of translation is limited, please understand!