android - RecyclerView 具有不同高度的项目 : Scrollbar

标签 android android-recyclerview scrollbar

我有一个 RecyclerView,其中包含带有滚动条的不同高度的项目。 由于项目的不同高度,滚动条会改变它的垂直大小,这取决于当前显示的项目(见屏幕截图)。 我创建了一个示例项目来显示问题 here .

  1. 有没有人遇到同样的问题并解决了它?
  2. 如何覆盖滚动条高度和位置的计算以提出自己的实现?

编辑: 滚动条的位置和高度可以通过覆盖 RecyclerViews computeVerticalScrollOffsetcomputeVerticalScrollRangecomputeVerticalScrollExtent。 我不知道如何实现这些以使滚动条在动态项目高度下正常工作。

我认为,问题在于 RecyclerView 根据当前可见的项目估计所有项目的总高度,并相应地设置滚动条的位置和高度。解决这个问题的一种方法可能是更好地估计所有项目的总高度。

large scrollbar small scrollbar

最佳答案

处理这种情况的最好方法可能是根据每个项目的大小以某种方式计算滚动条范围。这可能不切实际或不可取。取而代之的是,这里有一个自定义 RecyclerView 的简单实现,您可以使用它来尝试获得您想要的东西。它将向您展示如何使用各种滚动方法来控制滚动条。它会根据显示的项目数量将拇指的大小固定为初始大小。要记住的关键是滚动范围是任意的,但所有其他测量(范围、偏移)必须使用相同的单位。

请参阅 computeVerticalScrollRange() 的文档.

这是结果的视频。

enter image description here

更新:代码已更新以更正一些问题:拇指的移动不再那么急促,拇指现在将作为 RecyclerView 停在底部。滚动到底部。代码后还有一些注意事项。

MyRecyclerView.java(已更新)

public class MyRecyclerView extends RecyclerView {
    // The size of the scroll bar thumb in our units.
    private int mThumbHeight = UNDEFINED;

    // Where the RecyclerView cuts off the views when the RecyclerView is scrolled to top.
    // For example, if 1/4 of the view at position 9 is displayed at the bottom of the RecyclerView,
    // mTopCutOff will equal 9.25. This value is used to compute the scroll offset.
    private float mTopCutoff = UNDEFINED;

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

    public MyRecyclerView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public MyRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    /**
     * Retrieves the size of the scroll bar thumb in our arbitrary units.
     *
     * @return Scroll bar thumb height
     */
    @Override
    public int computeVerticalScrollExtent() {
        return (mThumbHeight == UNDEFINED) ? 0 : mThumbHeight;
    }

    /**
     * Compute the offset of the scroll bar thumb in our scroll bar range.
     *
     * @return Offset in scroll bar range.
     */
    @Override
    public int computeVerticalScrollOffset() {
        return (mTopCutoff == UNDEFINED) ? 0 : (int) ((getCutoff() - mTopCutoff) * ITEM_HEIGHT);
    }

    /**
     * Computes the scroll bar range. It will simply be the number of items in the adapter
     * multiplied by the given item height. The scroll extent size is also computed since it
     * will not vary. Note: The RecyclerView must be positioned at the top or this method
     * will throw an IllegalStateException.
     *
     * @return The scroll bar range
     */
    @Override
    public int computeVerticalScrollRange() {
        if (mThumbHeight == UNDEFINED) {
            LinearLayoutManager lm = (LinearLayoutManager) getLayoutManager();
            int firstCompletePositionw = lm.findFirstCompletelyVisibleItemPosition();

            if (firstCompletePositionw != RecyclerView.NO_POSITION) {
                if (firstCompletePositionw != 0) {
                    throw (new IllegalStateException(ERROR_NOT_AT_TOP_OF_RANGE));
                } else {
                    mTopCutoff = getCutoff();
                    mThumbHeight = (int) (mTopCutoff * ITEM_HEIGHT);
                }
            }
        }
        return getAdapter().getItemCount() * ITEM_HEIGHT;
    }

    /**
     * Determine where the RecyclerVIew display cuts off the list of views. The range is
     * zero through (getAdapter().getItemCount() - 1) inclusive.
     *
     * @return The position in the RecyclerView where the displayed views are cut off. If the
     * bottom view is partially displayed, this will be a fractional number.
     */
    private float getCutoff() {
        LinearLayoutManager lm = (LinearLayoutManager) getLayoutManager();
        int lastVisibleItemPosition = lm.findLastVisibleItemPosition();

        if (lastVisibleItemPosition == RecyclerView.NO_POSITION) {
            return 0f;
        }

        View view = lm.findViewByPosition(lastVisibleItemPosition);
        float fractionOfView;

        if (view.getBottom() < getHeight()) { // last visible position is fully visible
            fractionOfView = 0f;
        } else { // last view is cut off and partially displayed
            fractionOfView = (float) (getHeight() - view.getTop()) / (float) view.getHeight();
        }
        return lastVisibleItemPosition + fractionOfView;
    }

    private static final int ITEM_HEIGHT = 1000; // Arbitrary, make largish for smoother scrolling
    private static final int UNDEFINED = -1;
    private static final String ERROR_NOT_AT_TOP_OF_RANGE
            = "RecyclerView must be positioned at the top of its range.";
}

注意事项 根据实现情况,可能需要解决以下问题。

示例代码仅适用于垂直滚动。示例代码还假定 RecyclerView 的内容是静态的。支持 RecyclerView 的数据的任何更新可能会导致滚动问题。如果进行了任何影响 RecyclerView 的第一个全屏上显示的任何 View 的高度的更改,滚动将关闭。下面的更改可能会正常工作。这是由于代码计算滚动偏移量的方式所致。

要确定滚动偏移的基值,(变量 mTopCutOff ),RecyclerView 必须第一次滚动到顶部 computeVerticalScrollRange()被调用以便可以测量 View ;否则,代码将以“IllegalStateException”停止。如果 RecyclerView 方向改变,这尤其麻烦。被滚动了。解决此问题的一个简单方法是禁止恢复滚动位置,以便在方向更改时默认为顶部。

(以下可能不是最好的解决方案...)

var lm: LinearLayoutManager = object : LinearLayoutManager(this) {
    override fun onRestoreInstanceState(state: Parcelable?) {
        // Don't restore
    }
}

希望对您有所帮助。 (顺便说一句,您的 MCVE 使这变得容易多了。)

关于android - RecyclerView 具有不同高度的项目 : Scrollbar,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46033473/

相关文章:

android - 如何检查应用程序是否可移动到 sd

android - RecyclerView 出现空指针异常

Javascript检测文本区域中的滚动条

android - 服务 - android 剪贴板监听器

android - 手机上的DialogFragment填充屏幕

java - Android 日历月日

android - 如何在 android 中的回收器 View (GridLayoutManager)中减少列之间的空间

android - recyclerview 的最小 sdk 以及用它替换 listview 的优缺点

html - 移除垂直滚动条

scroll - Bootstrap : How to enable scroll bars?