• 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!