How to sync scrolling first-positions of 2 RecyclerViews?

让人想犯罪 __ 提交于 2019-12-30 04:01:11

问题


Background

I have 2 RecyclerView instances. One is horizontal, and the second is vertical.

They both show the same data and have the same amount of items, but in different ways, and the cells are not necessary equal in size through each of them .

I wish that scrolling in one will sync with the other, so that the first item that's shown on one, will always be shown on the other (as the first).

The problem

Even though I've succeeded making them sync (I just choose which one is the "master", to control the scrolling of the other), the direction of the scrolling seems to affect the way it works.

Suppose for a moment the cells have equal heeight:

If I scroll up/left, it works as I intended, more or less:

However, if I scroll down/right, it does let the other RecyclerView show the first item of the other, but usually not as the first item:

Note: on the above screenshots, I've scrolled in the bottom RecyclerView, but a similar result will be with the top one.

The situation gets much worse if, as I wrote, the cells have different sizes:

What I've tried

I tried to use other ways of scrolling and going to other positions, but all attempts fail.

Using smoothScrollToPosition made things even worse (though it does seem nicer), because if I fling, at some point the other RecyclerView takes control of the one I've interacted with.

I think I should use the direction of the scrolling, together with the currently shown items on the other RecyclerView.

Here's the current (sample) code. Note that in the real code, the cells might not have equal sizes (some are tall, some are short, etc...). One of the lines in the code makes the cells have different height.

activity_main.xml

<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" tools:context=".MainActivity">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/topReccyclerView" android:layout_width="0dp" android:layout_height="100dp"
        android:layout_marginEnd="8dp" android:layout_marginStart="8dp" android:layout_marginTop="8dp"
        android:orientation="horizontal" app:layoutManager="android.support.v7.widget.LinearLayoutManager"
        app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" tools:listitem="@layout/horizontal_cell"/>

    <android.support.v7.widget.RecyclerView
        android:id="@+id/bottomRecyclerView" android:layout_width="0dp" android:layout_height="0dp"
        android:layout_marginBottom="8dp" android:layout_marginEnd="8dp" android:layout_marginStart="8dp"
        android:layout_marginTop="8dp" app:layoutManager="android.support.v7.widget.LinearLayoutManager"
        app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/topReccyclerView"
        tools:listitem="@layout/horizontal_cell"/>
</android.support.constraint.ConstraintLayout>

horizontal_cell.xml

<TextView
    android:id="@+id/textView" xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="100dp" android:layout_height="100dp"
    android:gravity="center" tools:text="@tools:sample/lorem"/>

vertical_cell.xml

<TextView
    android:id="@+id/textView" xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="50dp"
    android:gravity="center" tools:text="@tools:sample/lorem"/>

MainActivity

class MainActivity : AppCompatActivity() {
    var masterView: View? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val inflater = LayoutInflater.from(this)
        topReccyclerView.adapter = object : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
            override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
                (holder.itemView as TextView).text = position.toString()
                holder.itemView.setBackgroundColor(if(position%2==0) 0xffff0000.toInt() else 0xff00ff00.toInt())
            }

            override fun getItemCount(): Int {
                return 100
            }

            override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): RecyclerView.ViewHolder {
                return object : RecyclerView.ViewHolder(inflater.inflate(R.layout.horizontal_cell, parent, false)) {}
            }
        }

        bottomRecyclerView.adapter = object : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
        val baseHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50f, resources.displayMetrics).toInt()

            override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
                (holder.itemView as TextView).text = position.toString()
                holder.itemView.setBackgroundColor(if(position%2==0) 0xffff0000.toInt() else 0xff00ff00.toInt())
                // this makes the heights of the cells different from one another:
                holder.itemView.layoutParams.height = baseHeight + (if (position % 3 == 0) 0 else baseHeight / (position % 3))
            }

            override fun getItemCount(): Int {
                return 100
            }

            override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): RecyclerView.ViewHolder {
                return object : RecyclerView.ViewHolder(inflater.inflate(R.layout.vertical_cell, parent, false)) {}
            }
        }
        LinearSnapHelper().attachToRecyclerView(topReccyclerView)
        LinearSnapHelper().attachToRecyclerView(bottomRecyclerView)
        topReccyclerView.addOnScrollListener(OnScrollListener(topReccyclerView, bottomRecyclerView))
        bottomRecyclerView.addOnScrollListener(OnScrollListener(bottomRecyclerView, topReccyclerView))
    }

    inner class OnScrollListener(private val thisRecyclerView: RecyclerView, private val otherRecyclerView: RecyclerView) : RecyclerView.OnScrollListener() {
        var lastItemPos: Int = Int.MIN_VALUE
        val thisRecyclerViewId = resources.getResourceEntryName(thisRecyclerView.id)

        override fun onScrollStateChanged(recyclerView: RecyclerView?, newState: Int) {
            super.onScrollStateChanged(recyclerView, newState)
            Log.d("AppLog", "onScrollStateChanged:$thisRecyclerViewId $newState")
            when (newState) {
                RecyclerView.SCROLL_STATE_DRAGGING -> if (masterView == null) {
                    Log.d("AppLog", "setting $thisRecyclerViewId to be master")
                    masterView = thisRecyclerView
                }
                RecyclerView.SCROLL_STATE_IDLE -> if (masterView == thisRecyclerView) {
                    Log.d("AppLog", "resetting $thisRecyclerViewId from being master")
                    masterView = null
                    lastItemPos = Int.MIN_VALUE
                }
            }
        }

        override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) {
            super.onScrolled(recyclerView, dx, dy)
            if ((dx == 0 && dy == 0) || (masterView != null && masterView != thisRecyclerView))
                return
            //            Log.d("AppLog", "onScrolled:$thisRecyclerView $dx-$dy")
            val currentItem = (thisRecyclerView.layoutManager as LinearLayoutManager).findFirstCompletelyVisibleItemPosition()
            if (lastItemPos == currentItem)
                return
            lastItemPos = currentItem
            otherRecyclerView.scrollToPosition(currentItem)
//            otherRecyclerView.smoothScrollToPosition(currentItem)
            Log.d("AppLog", "currentItem:" + currentItem)
        }
    }
}

The questions

  1. How do I let the other RecycerView to always have the first item the same as the currently controlled one?

  2. How to modify the code to support smooth scrolling, without causing the issue of suddenly having the other RecyclerView being the one that controls ?


EDIT: after updating the sample code here with having different sizes of cells (because originally that's closer to the issue I have, as I described before), I noticed that the snapping doesn't work well.

That's why I chose to use this library to snap it correctly:

https://github.com/DevExchanges/SnappingRecyclerview

So instead of LinearSnapHelper, I use 'GravitySnapHelper'. Seems to work better, but still have the syncing issues, and touching while it scrolls.


EDIT: I've finally fixed all syncing issues, and it works fine even if the cells have different sizes.

Still has some issues:

  1. If you fling on one RecyclerView, and then touch the other one, it has very weird behavior of scrolling. Might scroll way more than it should.

  2. The scrolling isn't smooth (when syncing and when flinging), so it doesn't look well.

  3. Sadly, because of the snapping (which I actually might need only for the top RecyclerView), it causes another issue: the bottom RecyclerView might show the last item partially (screenshot with 100 items), and I can't scroll more to show it fully :

I don't even think that the bottom RecyclerView should be snapping, unless the top one was touched. Sadly this is all I got so far, that has no syncing issues.

Here's the new code, after all the fixes I've found:

class MainActivity : AppCompatActivity() {
    var masterView: View? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val inflater = LayoutInflater.from(this)
        topReccyclerView.adapter = object : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
            override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
                (holder.itemView as TextView).text = position.toString()
                holder.itemView.setBackgroundColor(if (position % 2 == 0) 0xffff0000.toInt() else 0xff00ff00.toInt())
            }

            override fun getItemCount(): Int = 1000

            override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): RecyclerView.ViewHolder {
                return object : RecyclerView.ViewHolder(inflater.inflate(R.layout.horizontal_cell, parent, false)) {}
            }
        }

        bottomRecyclerView.adapter = object : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
            val baseHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50f, resources.displayMetrics).toInt()
            override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
                (holder.itemView as TextView).text = position.toString()
                holder.itemView.setBackgroundColor(if (position % 2 == 0) 0xffff0000.toInt() else 0xff00ff00.toInt())
                holder.itemView.layoutParams.height = baseHeight + (if (position % 3 == 0) 0 else baseHeight / (position % 3))
            }

            override fun getItemCount(): Int = 1000

            override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): RecyclerView.ViewHolder {
                return object : RecyclerView.ViewHolder(inflater.inflate(R.layout.vertical_cell, parent, false)) {}
            }
        }
        // GravitySnapHelper is available from : https://github.com/DevExchanges/SnappingRecyclerview
        GravitySnapHelper(Gravity.START).attachToRecyclerView(topReccyclerView)
        GravitySnapHelper(Gravity.TOP).attachToRecyclerView(bottomRecyclerView)
        topReccyclerView.addOnScrollListener(OnScrollListener(topReccyclerView, bottomRecyclerView))
        bottomRecyclerView.addOnScrollListener(OnScrollListener(bottomRecyclerView, topReccyclerView))
    }

    inner class OnScrollListener(private val thisRecyclerView: RecyclerView, private val otherRecyclerView: RecyclerView) : RecyclerView.OnScrollListener() {
        var lastItemPos: Int = Int.MIN_VALUE
        val thisRecyclerViewId = resources.getResourceEntryName(thisRecyclerView.id)

        override fun onScrollStateChanged(recyclerView: RecyclerView?, newState: Int) {
            super.onScrollStateChanged(recyclerView, newState)
            when (newState) {
                RecyclerView.SCROLL_STATE_DRAGGING -> if (masterView == null) {
                    masterView = thisRecyclerView
                }
                RecyclerView.SCROLL_STATE_IDLE -> if (masterView == thisRecyclerView) {
                    masterView = null
                    lastItemPos = Int.MIN_VALUE
                }
            }
        }

        override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) {
            super.onScrolled(recyclerView, dx, dy)
            if (dx == 0 && dy == 0 || masterView !== null && masterView !== thisRecyclerView) {
                return
            }
            val otherLayoutManager = otherRecyclerView.layoutManager as LinearLayoutManager
            val thisLayoutManager = thisRecyclerView.layoutManager as LinearLayoutManager
            val currentItem = thisLayoutManager.findFirstCompletelyVisibleItemPosition()
            if (lastItemPos == currentItem) {
                return
            }
            lastItemPos = currentItem
            otherLayoutManager.scrollToPositionWithOffset(currentItem, 0)
        }
    }
}

回答1:


Combining the two RecyclerViews, there are four cases of movement:

a. Scrolling the horizontal recycler to the left

b. Scrolling it to the right

c. Scrolling the vertical recycler to the top

d. Scrolling it to the bottom

Cases a and c don't need to be taken care of since they work out of the box. For cases b and d you need to do two things:

  1. Know which recycler you are in (vertical or horizontal) and which direction the scroll went (up or down resp. left or right) and
  2. calculate an offset (of list items) from the number of visible items in otherRecyclerView (if the screen is bigger the offset needs to be bigger, too).

Figuring this out was a bit fiddly, but the result is pretty straight forward.

    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        super.onScrollStateChanged(recyclerView, newState);
        if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
            if (masterView == otherRecyclerView) {
                thisRecyclerView.stopScroll();
                otherRecyclerView.stopScroll();
                syncScroll(1, 1);
            }
            masterView = thisRecyclerView;
        } else if (newState == RecyclerView.SCROLL_STATE_IDLE && masterView == thisRecyclerView) {
            masterView = null;
        }
    }

    @Override
    public void onScrolled(RecyclerView recyclerview, int dx, int dy) {
        super.onScrolled(recyclerview, dx, dy);
        if ((dx == 0 && dy == 0) || (masterView != null && masterView != thisRecyclerView)) {
            return;
        }
        syncScroll(dx, dy);
    }

    void syncScroll(int dx, int dy) {
        LinearLayoutManager otherLayoutManager = (LinearLayoutManager) otherRecyclerView.getLayoutManager();
        LinearLayoutManager thisLayoutManager = (LinearLayoutManager) thisRecyclerView.getLayoutManager();
        int offset = 0;
        if ((thisLayoutManager.getOrientation() == HORIZONTAL && dx > 0) || (thisLayoutManager.getOrientation() == VERTICAL && dy > 0)) {
            // scrolling horizontal recycler to left or vertical recycler to bottom
            offset = otherLayoutManager.findLastCompletelyVisibleItemPosition() - otherLayoutManager.findFirstCompletelyVisibleItemPosition();
        }
        int currentItem = thisLayoutManager.findFirstCompletelyVisibleItemPosition();
        otherLayoutManager.scrollToPositionWithOffset(currentItem, offset);
    }

Of course you could combine the two if clauses since the bodies are the same. For the sake of readability, I thought it is good to keep them apart.

The second problem was syncing when the respective "other" recycler was touched while the "first" recycler was still scrolling. Here the following code (included above) is relevant:

if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
    if (masterView == otherRecyclerView) {
        thisRecyclerView.stopScroll();
        otherRecyclerView.stopScroll();
        syncScroll(1, 1);
    }
    masterView = thisRecyclerView;
}

newState equals SCROLL_STATE_DRAGGING when the recycler is touched and dragged a little bit. So if this is a touch (& drag) after a touch on the respective "other" recycler, the second condition (masterView == otherRecyclerview) is true. Both recyclers are stopped then and the "other" recycler is synced with "this" one.




回答2:


1-) Layout manager

The current smoothScrollToPosition does not take the element to the top. So let's write a new layout manager. And let's override this layout manager's smoothScrollToPosition.

public class TopLinearLayoutManager extends LinearLayoutManager
{
    public TopLinearLayoutManager(Context context, int orientation)
    {
        //orientation : vertical or horizontal
        super(context, orientation, false);
    }

    @Override
    public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position)
    {
        RecyclerView.SmoothScroller smoothScroller = new TopSmoothScroller(recyclerView.getContext());
        smoothScroller.setTargetPosition(position);
        startSmoothScroll(smoothScroller);
    }

    private class TopSmoothScroller extends LinearSmoothScroller
    {
        TopSmoothScroller(Context context)
        {
            super(context);
        }

        @Override
        public int calculateDtToFit(int viewStart, int viewEnd, int boxStart, int boxEnd, int snapPreference)
        {
            return (boxStart - viewStart);
        }
    }
}

2-) Setup

    //horizontal one
    RecyclerView rvMario = (RecyclerView) findViewById(R.id.rvMario);

    //vertical one
    RecyclerView rvLuigi = (RecyclerView) findViewById(R.id.rvLuigi);

    final LinearLayoutManager managerMario = new LinearLayoutManager(MainActivity.this, LinearLayoutManager.HORIZONTAL, false);
    rvMario.setLayoutManager(managerMario);
    ItemMarioAdapter adapterMario = new ItemMarioAdapter(itemList);
    rvMario.setAdapter(adapterMario);

     //Snap to start by using Ruben Sousa's RecyclerViewSnap
    SnapHelper snapHelper = new GravitySnapHelper(Gravity.START);
    snapHelper.attachToRecyclerView(rvMario);

    final TopLinearLayoutManager managerLuigi = new TopLinearLayoutManager(MainActivity.this, LinearLayoutManager.VERTICAL);
    rvLuigi.setLayoutManager(managerLuigi);
    ItemLuigiAdapter adapterLuigi = new ItemLuigiAdapter(itemList);
    rvLuigi.setAdapter(adapterLuigi);

3-) Scroll listener

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

     //get firstCompleteleyVisibleItemPosition
     int firstCompleteleyVisibleItemPosition = managerMario.findFirstCompletelyVisibleItemPosition();

     if (firstCompleteleyVisibleItemPosition >= 0)
     {  
      //vertical one, smooth scroll to position
      rvLuigi.smoothScrollToPosition(firstCompleteleyVisibleItemPosition);
     }
    }

    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState)
    {
     super.onScrollStateChanged(recyclerView, newState);
    }
});

4-) Output




回答3:


Building on Burak's TopLinearLayoutManager, but correcting the logic of the OnScrollListener we finally get working smoothscrolling and correct snapping (of the horizontal RecyclerView).

public class MainActivity extends AppCompatActivity {
    View masterView = null;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity);
        final LayoutInflater inflater = LayoutInflater.from(this);
        final RecyclerView topRecyclerView = findViewById(R.id.topReccyclerView);
        RecyclerView.Adapter adapterTop = new RecyclerView.Adapter<RecyclerView.ViewHolder>() {
            @Override
            public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
                return new ViewHolder(inflater.inflate(R.layout.horizontal_cell, parent, false));
            }

            @Override
            public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
                ((TextView) holder.itemView).setText(String.valueOf(position));
                holder.itemView.setBackgroundColor(position % 2 == 0 ? Integer.valueOf(0xffff0000) : Integer.valueOf(0xff00ff00));
            }

            @Override
            public int getItemCount() {
                return 100;
            }

            class ViewHolder extends RecyclerView.ViewHolder {
                final TextView textView;

                ViewHolder(View itemView) {
                    super(itemView);
                    textView = itemView.findViewById(R.id.textView);
                }
            }
        };
        topRecyclerView.setAdapter(adapterTop);

        final RecyclerView bottomRecyclerView = findViewById(R.id.bottomRecyclerView);
        RecyclerView.Adapter adapterBottom = new RecyclerView.Adapter() {
            int baseHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50f, getResources().getDisplayMetrics());

            @Override
            public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
                return new ViewHolder(inflater.inflate(R.layout.vertical_cell, parent, false));
            }

            @Override
            public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
                ((TextView) holder.itemView).setText(String.valueOf(position));
                holder.itemView.setBackgroundColor((position % 2 == 0) ? Integer.valueOf(0xffff0000) : Integer.valueOf(0xff00ff00));
                holder.itemView.getLayoutParams().height = baseHeight + (position % 3 == 0 ? 0 : baseHeight / (position % 3));
            }

            @Override
            public int getItemCount() {
                return 100;
            }

            class ViewHolder extends RecyclerView.ViewHolder {
                final TextView textView;

                ViewHolder(View itemView) {
                    super(itemView);
                    textView = itemView.findViewById(R.id.textView);
                }
            }
        };
        bottomRecyclerView.setAdapter(adapterBottom);

        TopLinearLayoutManager topLayoutManager = new TopLinearLayoutManager(this, LinearLayoutManager.HORIZONTAL);
        topRecyclerView.setLayoutManager(topLayoutManager);
        TopLinearLayoutManager bottomLayoutManager = new TopLinearLayoutManager(this, LinearLayoutManager.VERTICAL);
        bottomRecyclerView.setLayoutManager(bottomLayoutManager);

        final OnScrollListener topOnScrollListener = new OnScrollListener(topRecyclerView, bottomRecyclerView);
        final OnScrollListener bottomOnScrollListener = new OnScrollListener(bottomRecyclerView, topRecyclerView);
        topRecyclerView.addOnScrollListener(topOnScrollListener);
        bottomRecyclerView.addOnScrollListener(bottomOnScrollListener);

        GravitySnapHelper snapHelperTop = new GravitySnapHelper(Gravity.START);
        snapHelperTop.attachToRecyclerView(topRecyclerView);
    }

    class OnScrollListener extends RecyclerView.OnScrollListener {
        private RecyclerView thisRecyclerView;
        private RecyclerView otherRecyclerView;
        int lastItemPos = Integer.MIN_VALUE;

        OnScrollListener(RecyclerView thisRecyclerView, RecyclerView otherRecyclerView) {
            this.thisRecyclerView = thisRecyclerView;
            this.otherRecyclerView = otherRecyclerView;
        }

        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
            if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
                masterView = thisRecyclerView;
            } else if (newState == RecyclerView.SCROLL_STATE_IDLE && masterView == thisRecyclerView) {
                masterView = null;
                lastItemPos = Integer.MIN_VALUE;
            }
        }

        @Override
        public void onScrolled(RecyclerView recyclerview, int dx, int dy) {
            super.onScrolled(recyclerview, dx, dy);
            if ((dx == 0 && dy == 0) || (masterView != thisRecyclerView)) {
                return;
            }
            int currentItem = ((TopLinearLayoutManager) thisRecyclerView.getLayoutManager()).findFirstCompletelyVisibleItemPosition();
            if (lastItemPos == currentItem) {
                return;
            }
            lastItemPos = currentItem;
            otherRecyclerView.getLayoutManager().smoothScrollToPosition(otherRecyclerView, null, currentItem);
        }
    }
}



回答4:


Another simple solution working fine in my device

Variable

RecyclerView horizontalRecyclerView, verticalRecyclerView;
LinearLayoutManager horizontalLayoutManager, verticalLayoutManager;

ArrayList<String> arrayList = new ArrayList<>();
ArrayList<String> arrayList2 = new ArrayList<>();

RecyclerView code

horizontalRecyclerView = findViewById(R.id.horizontalRc);
verticalRecyclerView = findViewById(R.id.verticalRc);

horizontalRecyclerView.setHasFixedSize(true);
verticalRecyclerView.setHasFixedSize(true);

horizontalLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
verticalLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);

horizontalRecyclerView.setLayoutManager(horizontalLayoutManager);
verticalRecyclerView.setLayoutManager(verticalLayoutManager);

for (int i = 0; i < 50; i++) {
     arrayList.add("" + i);
     arrayList2.add("" + i);
 }

 MyDataAdapter horizontalAdapter = new MyDataAdapter(this, arrayList);
 MyDataAdapter verticalAdapter = new MyDataAdapter(this, arrayList2);

 horizontalRecyclerView.setAdapter(horizontalAdapter);
 verticalRecyclerView.setAdapter(verticalAdapter);

logic inside RecyclerView.addOnScrollListener

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

                int pos = horizontalLayoutManager.findFirstCompletelyVisibleItemPosition();
                verticalLayoutManager.scrollToPositionWithOffset(pos, 20);

            }

        });


        verticalRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {

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

                int pos = verticalLayoutManager.findFirstCompletelyVisibleItemPosition();
                horizontalLayoutManager.scrollToPositionWithOffset(pos, 20);


            }
        });

Hope it help some one

WHOLE Code

public class Main4Activity extends AppCompatActivity {

    RecyclerView horizontalRecyclerView, verticalRecyclerView;
    LinearLayoutManager horizontalLayoutManager, verticalLayoutManager;

    ArrayList<String> arrayList = new ArrayList<>();
    ArrayList<String> arrayList2 = new ArrayList<>();

    boolean isVertical = true, isHorizontal = true;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main4);


        horizontalRecyclerView = findViewById(R.id.horizontalRc);
        verticalRecyclerView = findViewById(R.id.verticalRc);

        horizontalRecyclerView.setHasFixedSize(true);
        verticalRecyclerView.setHasFixedSize(true);

        horizontalLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
        verticalLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);

        horizontalRecyclerView.setLayoutManager(horizontalLayoutManager);
        verticalRecyclerView.setLayoutManager(verticalLayoutManager);

        for (int i = 0; i < 50; i++) {
            arrayList.add("" + i);
            arrayList2.add("" + i);
        }

        MyDataAdapter horizontalAdapter = new MyDataAdapter(this, arrayList);
        MyDataAdapter verticalAdapter = new MyDataAdapter(this, arrayList2);

        horizontalRecyclerView.setAdapter(horizontalAdapter);
        verticalRecyclerView.setAdapter(verticalAdapter);

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

                int pos = horizontalLayoutManager.findFirstCompletelyVisibleItemPosition();
                verticalLayoutManager.scrollToPositionWithOffset(pos, 20);


            }

        });


        verticalRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {

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

                int pos = verticalLayoutManager.findFirstCompletelyVisibleItemPosition();
                horizontalLayoutManager.scrollToPositionWithOffset(pos, 20);


            }
        });



       /* horizontalRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);

                int pos = horizontalLayoutManager.findFirstCompletelyVisibleItemPosition();
                verticalLayoutManager.scrollToPositionWithOffset(pos, 20);

                *//*if (isHorizontal) {
                    int pos = horizontalLayoutManager.findFirstCompletelyVisibleItemPosition();
                    verticalLayoutManager.scrollToPositionWithOffset(pos, 20);
                    Log.e("isHorizontal", "TRUE");
                    isVertical = false;
                } else {
                    isHorizontal = true;
                }*//*

            }

           *//* @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                isVertical = true;
            }*//*
        });


        verticalRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {

            *//* @Override
             public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                 super.onScrollStateChanged(recyclerView, newState);
                 isHorizontal = true;
             }
 *//*
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);

                int pos = verticalLayoutManager.findFirstCompletelyVisibleItemPosition();
                horizontalLayoutManager.scrollToPositionWithOffset(pos, 20);

               *//* if (isVertical) {
                    int pos = verticalLayoutManager.findFirstCompletelyVisibleItemPosition();
                    horizontalLayoutManager.scrollToPositionWithOffset(pos, 20);
                    Log.e("isVertical", "TRUE");
                    isHorizontal = false;
                } else {
                    isVertical = true;
                }*//*


            }
        });*/


    }
}

Adapter code

public class MyDataAdapter extends RecyclerView.Adapter<MyDataAdapter.ViewHolder> {

    Context context;
    ArrayList<String> arrayList;


    public MyDataAdapter(Context context, ArrayList<String> arrayList) {
        this.context = context;
        this.arrayList = arrayList;
    }

    @Override
    public MyDataAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(context).inflate(R.layout.temp, parent, false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(MyDataAdapter.ViewHolder holder, int position) {

        if (position % 2 == 0) {
            holder.tvNumber.setBackgroundResource(R.color.colorGreen);
        } else {
            holder.tvNumber.setBackgroundResource(R.color.colorRed);
        }
        holder.tvNumber.setText(arrayList.get(position));
    }

    @Override
    public int getItemCount() {
        return arrayList.size();
    }

    public class ViewHolder extends RecyclerView.ViewHolder {

        TextView tvNumber;

        public ViewHolder(View itemView) {
            super(itemView);
            tvNumber = itemView.findViewById(R.id.tvNumber);
        }
    }
}

Activity layout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">


    <android.support.v7.widget.RecyclerView
        android:id="@+id/horizontalRc"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:visibility="gone" />

    <android.support.v7.widget.RecyclerView
        android:id="@+id/verticalRc"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginTop="20dp"
        android:visibility="gone" />



</LinearLayout>    

temp Layout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:padding="40dp">

    <TextView
        android:id="@+id/tvNumber"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="50dp" />


</LinearLayout>

COLOR

<color name="colorGreen">#307832</color>
<color name="colorRed">#ff4c4c</color>


来源:https://stackoverflow.com/questions/47282276/how-to-sync-scrolling-first-positions-of-2-recyclerviews

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!