LinearLayoutManager平滑滚动到错误位置

5
我正在尝试制作一个类似于Instagram滤镜布局的布局。基本上,当您选择一个滤镜时,它将滚动到您选择的项目+1,向您显示还有更多滤镜可供选择。
我目前正在尝试为我的水平RecyclerView构建自定义LinearLayoutManager:
public class LinearLayoutSnapManager extends LinearLayoutManager {
    private int mCurrentPos = 0;

    public LinearLayoutSnapManager(Context context) {
        super(context);
    }

    public LinearLayoutSnapManager(Context context, int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
    }

    public LinearLayoutSnapManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);

    }


    public void snap(RecyclerView rv, int position) {
        if (mCurrentPos == position) {
            // No move
            return;
        }

        boolean goingRight = true;
        if (position < mCurrentPos) {
            goingRight = false;
        }
        mCurrentPos = position;
        smoothScrollToPosition(rv, new RecyclerView.State(), goingRight ? getScrollRightPos(): getScrollLeftPos());
    }

    private int getScrollLeftPos() {
        int newPos = mCurrentPos - 1;
        return (newPos > 0) ? newPos : 0;
    }

    private int getScrollRightPos() {
        return mCurrentPos + 1;
    }
}

滚动向左正常工作,但是当我向右滚动时,它似乎只跳到列表的末尾而不是newItem + 1,我不知道为什么会发生这种情况。

enter image description here


你能发布更多的代码吗?我的意思是,你是如何使用那些方法的?你的逻辑似乎很好,但你可能在错误的时刻传递了位置参数。 - FabioR
可能是在调用 getScrollRightPos 时,将旧的(未更新的)位置传递给了 mCurrentPos,因此它会滚动到旧位置 + 1(可能在 RecyclerView 的末尾)。此外,我认为您在点击不相邻的当前项时存在问题,这些情况下滚动是否正常工作? - FabioR
谁在调用snap()int position是从哪里来的? - Kirill
4个回答

2
我在水平列表的接口回调中从 onclicklistener 调用了我的 snap 方法。 在调用接口之前,我设置了选定的项目,然后通知视图已更改以更新当前状态。 这导致布局管理器返回不良索引的所有问题。
当我制作了一个空项目并测试我的逻辑时,我发现了这个问题。 令我惊讶的是,它能正常工作,这使我能够找到真正的问题。
一旦我将snaplogic移动到所有视图逻辑之前,所有东西都按预期工作,而且我发布的代码没有任何更改。
希望这能帮助未来的某位开发者。

1
我建议使用另一种解决方案。LinearLayoutManager附带了方便的函数:

int findFirstCompletelyVisibleItemPosition()

返回第一个完全可见视图的适配器位置。


int findLastCompletelyVisibleItemPosition()

返回最后一个完全可见视图的适配器位置。


int findFirstVisibleItemPosition()

返回第一个可见视图的适配器位置。


int findFirstVisibleItemPosition()

返回最后一个可见视图的适配器位置。

前两个函数返回完全可见视图的位置,而后两个函数返回部分可见视图的位置。选择取决于您想要实现的行为。

在使用它们时,函数getScrollLeftPosgetScrollRightPos应如下所示:

private int getScrollLeftPos() {
    int newPos = findFirstCompletelyVisibleItemPosition() - 1;
    return (newPos > 0) ? newPos : 0;
}

private int getScrollRightPos() {
    return findLastCompletelyVisibleItemPosition()+ 1;
}

不要调用方法:

smoothScrollToPosition(rv, new RecyclerView.State(), goingRight ? getScrollRightPos(): getScrollLeftPos());

来自LayoutManager。如果您有对RecyclerView的引用,请调用:

rv.smoothScrollToPosition(newPosition);

如果您查看RecyclerView的源代码,您会发现这样的实现:
public void smoothScrollToPosition(int position) {
    if (mLayoutFrozen) {
        return;
    }
    if (mLayout == null) {
        Log.e(TAG, "Cannot smooth scroll without a LayoutManager set. " +
                "Call setLayoutManager with a non-null argument.");
        return;
    }
    mLayout.smoothScrollToPosition(this, mState, position);
}

这基本上与您所做的相同,但请注意,LayoutManager 的功能是使用适当的 RecyclerView.State 调用的。它只是与 RecyclerView 一致。


1
所以我认为问题的核心在于findLastVisibleItemPosition()似乎不正确。当我点击列表中的第二项时,它返回0。findLastCompletelyVisibleItemPosition()也是如此。当它查看与回收视图附加的4个视图时,Getchildcount()为1。 - Nick

0

如果您想让最后选择的筛选器在第二个位置显示(一个筛选器之前,多个筛选器之后),那么您的方法是不正确的。

在这种情况下,无论向右还是向左,处理过程都是相同的。RecyclerView 的位置始终比所选筛选器的位置少 1,除了第一个元素。

(此示例未针对最新位置进行优化)

public class LinearLayoutSnapManager extends LinearLayoutManager {
    private int mCurrentSelectedPos = 0;

    public LinearLayoutSnapManager(Context context) {
        super(context);
    }

    public LinearLayoutSnapManager(Context context, int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
    }

    public LinearLayoutSnapManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);

    }


    public void snap(RecyclerView rv, int selectedPosition) {
        if (mCurrentSelectedPos == selectedPosition) {
            // No move
            return;
        }

        mCurrentSelectedPos = selectedPosition;

        int newDisplayPos;
        if (mCurrentSelectedPos > 0) {
            newDisplayPos = mCurrentSelectedPos - 1;
        } else {
            newDisplayPos = 0;
        }

        smoothScrollToPosition(rv, new RecyclerView.State(), newDisplayPos);
    }

}

这段代码和上面一样存在同样的问题,只不过现在是当向右滚动时没有任何反应。当向右滚动时,我需要它像向左滚动时那样运行。 - Nick

0

这是因为输入的“position”不是布局位置,首先找到如下的布局位置,然后应用smoothscrolltoposition。

  Common.foodlistnuber = viewHolder.getLayoutPosition(); //position
  new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {
  recyclerView.smoothScrollToPosition(Common.foodlistnuber);
        }
    }, 200);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接