How to sync scrolling first-positions of 2 RecyclerViews?

后端 未结 4 1944
情话喂你
情话喂你 2021-01-01 21:21

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,

相关标签:
4条回答
  • 2021-01-01 21:57

    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);
            }
        }
    }
    
    0 讨论(0)
  • 2021-01-01 22:06

    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>
    
    0 讨论(0)
  • 2021-01-01 22:07

    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.

    0 讨论(0)
  • 2021-01-01 22:11

    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

    0 讨论(0)
提交回复
热议问题