Phantom
Phantom

Reputation: 1028

Synchronize scrolling on multiple RecyclerViews in adapter

I want to implement an horizontal RecyclerView inside a vertical RecyclerView.

The end result should be like this:

enter image description here

So, for each element in the vertical RecyclerView, I need another one in an horizontal way. Kind of like a school schedule, with the Day in the left, and the actual schedule on the right, scroll-able horizontally.

I managed to achieve this, by putting an RecyclerView inside of the first RecyclerView item. Everything works perfectly, but all the horizontal RecyclerViews are scrolling separately. What I want to do is make all the horizontal RecyclerViews to sync and scroll at the same time. How can I achieve this?

The way I set the adapter and the horizontal RecyclerView inside the onBindViewHolder method of the vertical adapter is this:

scheduleAdapter = new ScheduleAdapter(context, data);
holder.scheduleRecyclerView.setAdapter(scheduleAdapter);
holder.scheduleRecyclerView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false));

Upvotes: 7

Views: 4283

Answers (2)

phuclt
phuclt

Reputation: 31

In MainActivity have recyclerView vertical:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        verticalRecyclerView.layoutManager = LinearLayoutManager(this)

        val onScrollListener = object: RecyclerView.OnScrollListener() {
            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                super.onScrolled(recyclerView, dx, dy)
                (recyclerView.adapter as? Adapter)?.matchOffset()
            }
        }

        verticalRecyclerView.addOnScrollListener(onScrollListener)
        verticalRecyclerView.adapter = Adapter(verticalRecyclerView)
    }
}

In Adapter have list recyclerView horizontal:

class Adapter(private val parentRV: RecyclerView): RecyclerView.Adapter<CustomViewHolder>() {

    var horizontalRecyclerViews = mutableListOf<RecyclerView>()
    var absoluteOffset: Int? = null
    //numberOfItems
    override fun getItemCount(): Int {
        return 25
    }

    fun matchOffset(offset: Int? = absoluteOffset) {
        offset?.let { offsetValue ->
            horizontalRecyclerViews.forEach { recyclerView ->
                val currentOffset = recyclerView.computeHorizontalScrollOffset()
                if (offsetValue != currentOffset) {
                    recyclerView.scrollBy(offsetValue-currentOffset, 0)
                }
            }
        }
    }


    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomViewHolder {
        val layoutInflator = LayoutInflater.from(parent.context)
        val cellForRow = layoutInflator.inflate(R.layout.item_recycler_view, parent, false)
        return  CustomViewHolder(cellForRow)
    }

    override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
        holder.view.horizontalRecyclerView.layoutManager = LinearLayoutManager(holder.view.context, LinearLayoutManager.HORIZONTAL, false)

        val onTouchListener = object: RecyclerView.OnItemTouchListener {
            override fun onTouchEvent(p0: RecyclerView, p1: MotionEvent) {
            }
            override fun onInterceptTouchEvent(p0: RecyclerView, p1: MotionEvent): Boolean {
                if (p1.action == MotionEvent.ACTION_UP) {
                    absoluteOffset = p0.computeHorizontalScrollOffset()
                    return true
                }
                return false
            }
            override fun onRequestDisallowInterceptTouchEvent(p0: Boolean) {
            }
        }

        val onScrollListener = object: RecyclerView.OnScrollListener() {
            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                super.onScrolled(recyclerView, dx, dy)
                val value = recyclerView.computeHorizontalScrollOffset()
                matchOffset(value)
            }
        }


        val child = BlockAdapter()
        holder.view.horizontalRecyclerView.tag = position
        holder.view.horizontalRecyclerView.clearOnScrollListeners()
        holder.view.horizontalRecyclerView.addOnItemTouchListener(onTouchListener)
        holder.view.horizontalRecyclerView.addOnScrollListener(onScrollListener)
        holder.view.horizontalRecyclerView.adapter = child
        horizontalRecyclerViews.add(holder.view.horizontalRecyclerView)
    }
}

class BlockAdapter(): RecyclerView.Adapter<CustomViewHolder>() {

    //numberOfItems
    override fun getItemCount(): Int {
        return 30
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomViewHolder {
        val layoutInflator = LayoutInflater.from(parent.context)
        val cellForRow = layoutInflator.inflate(R.layout.item_block, parent, false)
        return  CustomViewHolder(cellForRow)
    }

    override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
        holder.view.block.setBackgroundColor(
                if (position % 2 == 0) {
                    Color.BLUE
                } else {
                    Color.RED
                }
        )
        holder.view.block.setTextColor(Color.WHITE)
        holder.view.block.text = position.toString()
    }
}

class CustomViewHolder(val view: View): RecyclerView.ViewHolder(view) {

    init {
    }

}

Upvotes: 1

ziLk
ziLk

Reputation: 3200

Each recyclerview should add the below scroll listener.

m_jParentRecyclerViewLayoutManager is the parent RecyclerView whose items has a recyclerview.

RecyclerView.OnScrollListener scrollListener = new RecyclerView.OnScrollListener {

    @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);

            scrollAllRecyclerView(recyclerView, dx, dy);
        }

        private void scrollAllRecyclerView(RecyclerView recyclerView, int dx, int dy) {

                // Scroll children RecyclerViews except the recyclerView that is listened.
                for (int i = 0; i < m_jParentRecyclerViewLayoutManager.getChildCount(); i++) {
                    RecyclerView child = (RecyclerView) m_jParentRecyclerViewLayoutManager.getChildAt(i);
                    if (child != recyclerView) {
                        scroll(child, dx, dy);
                    }
                }
            }
        }

        private void scroll(RecyclerView recyclerView, int dx, int dy) {
            recyclerView.removeOnScrollListener(this);
            recyclerView.scrollBy(dx, dy);
            recyclerView.addOnScrollListener(this);
        }

EDIT : In your parent recyclerview adapter.

 @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            RecyclerView recyclerView = new RecyclerView(mContext);
            .. set layout manager & your adapter .

            if (scrollListener != null) {
                recyclerView.removeOnScrollListener(scrollListener );
                recyclerView.addOnScrollListener(scrollListener );
            }
            return new RecyclerViewViewHolder(recyclerView);
        }

EDIT 2 : There is an Table View library which scroll all child recyclerviews sync. You can check the source code

Upvotes: 6

Related Questions