2

背景

假设我有一个垂直的 RecyclerView,其中每一行都是一个水平的 RecyclerView。

我想做的是,无论您滚动哪个水平 RecyclerViews,所有其他 RecyclerViews 都会相应滚动,并且始终与完全相同的滚动 X 坐标同步

问题

我实际上对基本操作做得很好:

在此处输入图像描述

它的工作原理是拥有一个所有水平 RecyclerView 都具有的滚动侦听器,但是当一个开始滚动时,它是唯一拥有它的侦听器,同时它也会影响其他人一起滚动。

但是,我所做的有两个主要问题:

  1. 在一些(水平)滚动操作中(可能是一些手势,比如 fling),多个 RecyclerView 的滚动是不同步的,所以有些在 X 坐标中,与其他的不同。

  2. 垂直滚动时,我无法正确设置 X 坐标。不仅如此,当我期望调用垂直 RecyclerView 的 onBindViewHolder 时,它不会被调用(当我滚动很多时调用,而不仅仅是当我看到重新显示使用过的对象时调用)。

我试过的

这是当前代码:

MainActivity.java

public class MainActivity extends AppCompatActivity {
    int mCurX = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final RecyclerView mainRecyclerView = (RecyclerView) findViewById(R.id.activity_main);
        final LinearLayoutManager verticalLinearLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
        mainRecyclerView.setLayoutManager(verticalLinearLayoutManager);
        final LayoutInflater layoutInflater = LayoutInflater.from(this);
        final OnScrollListener masterOnScrollListener = new OnScrollListener() {
            RecyclerView masterRecyclerView = null;

            @Override
            public void onScrollStateChanged(final RecyclerView recyclerView, final int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                switch (newState) {
                    case RecyclerView.SCROLL_STATE_IDLE:
                        if (masterRecyclerView != null) {
                            masterRecyclerView = null;
                            final int firstVisibleItemPosition = verticalLinearLayoutManager.findFirstVisibleItemPosition();
                            final int lastVisibleItemPosition = verticalLinearLayoutManager.findLastVisibleItemPosition();
                            for (int i = firstVisibleItemPosition; i <= lastVisibleItemPosition; ++i) {
                                RecyclerView horizontalRecyclerView = (RecyclerView) mainRecyclerView.findViewHolderForAdapterPosition(i).itemView;
                                if (horizontalRecyclerView != recyclerView)
                                    horizontalRecyclerView.addOnScrollListener(this);
                            }
                        }
                        break;
                    case RecyclerView.SCROLL_STATE_SETTLING:
                        //TODO fix out-of-sync scrolling issues, probably here
                    case RecyclerView.SCROLL_STATE_DRAGGING:
                        if (masterRecyclerView == null) {
                            masterRecyclerView = recyclerView;
                            final int firstVisibleItemPosition = verticalLinearLayoutManager.findFirstVisibleItemPosition();
                            final int lastVisibleItemPosition = verticalLinearLayoutManager.findLastVisibleItemPosition();
                            for (int i = firstVisibleItemPosition; i <= lastVisibleItemPosition; ++i) {
                                RecyclerView horizontalRecyclerView = (RecyclerView) mainRecyclerView.findViewHolderForAdapterPosition(i).itemView;
                                if (horizontalRecyclerView != recyclerView)
                                    horizontalRecyclerView.removeOnScrollListener(this);
                            }
                        }
                }
            }

            @Override
            public void onScrolled(final RecyclerView recyclerView, final int dx, final int dy) {
                super.onScrolled(recyclerView, dx, dy);
                mCurX += dx;
                final int firstVisibleItemPosition = verticalLinearLayoutManager.findFirstVisibleItemPosition();
                final int lastVisibleItemPosition = verticalLinearLayoutManager.findLastVisibleItemPosition();
                for (int i = firstVisibleItemPosition; i <= lastVisibleItemPosition; ++i) {
                    RecyclerView horizontalRecyclerView = (RecyclerView) mainRecyclerView.findViewHolderForAdapterPosition(i).itemView;
                    if (horizontalRecyclerView != recyclerView)
                        horizontalRecyclerView.scrollBy(dx, dy);
                }
            }
        };
        mainRecyclerView.setAdapter(new Adapter() {
            @Override
            public ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
                RecyclerView horizontalRecyclerView = (RecyclerView) layoutInflater.inflate(R.layout.horizontal_recycler_view, parent, false);
                horizontalRecyclerView.setLayoutManager(new LinearLayoutManager(MainActivity.this, LinearLayoutManager.HORIZONTAL, false));
                horizontalRecyclerView.addOnScrollListener(masterOnScrollListener);
                final ViewHolder horizontalViewHolder = new ViewHolder(horizontalRecyclerView) {
                };
                horizontalRecyclerView.setAdapter(new Adapter() {
                    @Override
                    public ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
                        return new ViewHolder(layoutInflater.inflate(R.layout.single_item, parent, false)) {
                        };
                    }

                    @Override
                    public void onBindViewHolder(final ViewHolder holder, final int position) {
                        ((TextView) holder.itemView).setText("horizontalRecyclerView:" + horizontalViewHolder.getAdapterPosition() + "\nitem:" + position);
                    }

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

            @Override
            public void onBindViewHolder(final ViewHolder holder, final int position) {
                //TODO check why this isn't called for some cases
                RecyclerView recyclerView = (RecyclerView) holder.itemView;
                recyclerView.removeOnScrollListener(masterOnScrollListener);
                //TODO scroll to correct location here. The below code doesn't seem to work at all
                recyclerView.scrollToPosition(0);
                recyclerView.scrollBy(mCurX,0);
                recyclerView.addOnScrollListener(masterOnScrollListener);
                recyclerView.getAdapter().notifyDataSetChanged();
            }

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

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView
    android:id="@+id/activity_main"
    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="lb.com.nestedallscrollingrecyclerviewtest.MainActivity"/>

Horizo​​ntal_recycler_view.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="100dp"/>

single_item.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical"
    android:padding="10dp"/>

问题

  1. 导致它无法滚动同步的代码有什么问题?

  2. 是否有可能我也没有获得我应该拥有的所有 RecyclerViews?

  3. 当我期望它时,垂直 RecyclerView 的 onBindViewHolder 怎么没有被调用?

  4. 如何在垂直的 onBindViewHolder 中将水平 RecyclerView 的 x 坐标滚动设置为其他滚动?

  5. 我不确定这是否是个问题,但如果每个水平 RecyclerView 中的每个项目的宽度可能与其他项目不同,我该怎么办?

4

1 回答 1

1

聚会有点晚了,但只是把它放在这里,以防其他人偶然发现同样的问题。请注意,此解决方案是用Kotlin编写的,如果您选择的是 Java ,您可能需要将其转换为Java

解决方案

有几个问题需要考虑。

  1. 同步滚动horizontal回收器视图
  2. 滚动vertical回收站视图时保留偏移量

Adapter在您的vertical回收站视图中添加此代码

var horizontalRecyclerViews = mutableListOf<RecyclerView>()
var absoluteOffset: Int? = null    //Used to solve issue number 2

// matchOffset is used to synchronise the offset of each horizontal recyclerview.
// It is called when a horizontal recyclerview is scrolled with that recyclerview's
// offset. It is also called when the vertical recycler view is scrolled but without
// an offset value (in which case, it uses the absoluteOffset which is set when
// the horizontal scrolling is stopped) 
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 onBindViewHolder(holder: CustomViewHolder, position: Int) {
    ...
    ...
    ...

    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) {
                // This value is used by the vertical recycler view
                absoluteOffset = p0.computeHorizontalScrollOffset()

                // Disable the fling scroll to make life easier
                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)
        }
    }

    ...
    ...
    ...

    //Clear scroll listeners on each bind to stop them from accumulating
    horizontalRecyclerView.clearOnScrollListeners()

    //Add touch and scroll listeners to horizontalRecyclerView
    horizontalRecyclerView.addOnItemTouchListener(onTouchListener)
    horizontalRecyclerView.addOnScrollListener(onScrollListener)

    //Add each horizontal recyclerView into the mutableList
    horizontalRecyclerViews.add(horizontalRecyclerView)

    ...
    ...
    ...
}

为了解决问题 2,将以下滚动侦听器添加到您的vertical回收站视图

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

        //Cast the Adapter to access the matchOffset method
        (recyclerView.adapter as? Adapter)?.matchOffset()
    }
}

verticalRecyclerView.addOnScrollListener(onScrollListener)
于 2018-11-29T05:02:18.650 回答