具有不同高度的项目的RecyclerView:滚动条 [英] RecyclerView with items of different height: Scrollbar

查看:598
本文介绍了具有不同高度的项目的RecyclerView:滚动条的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个RecyclerView,其中带有带有滚动条的高度不同的项目. 由于项目的高度不同,滚动条会更改其垂直大小,具体取决于当前显示的项目(请参见屏幕截图). 我创建了一个示例项目,在此处中显示了问题.

I have a RecyclerView with items of varying heights with a scrollbar. Because of the different heights of the items, the scrollbar changes it's vertical size, dependent on which items are currently displayed (see screenshots). I have created an example project that displays the problem here.

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

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

The scrollbar's position and height can be controlled by overriding RecyclerViews computeVerticalScrollOffset, computeVerticalScrollRange and computeVerticalScrollExtent. I have no idea though on how to implement these to make the scrollbar work properly with dynamic item heights.

我认为问题是RecyclerView根据当前可见的项目估计所有项目的总高度,并相应地设置滚动条的位置和高度.解决此问题的一种方法可能是对所有物品的总高度进行更好的估算.

The problem, I reckon, is that RecyclerView estimates the total height of all items based on the items currently visible and sets position and height of the scrollbar accordingly. One way to solve this might be to give a better estimation of the total height of all items.

推荐答案

处理这种情况的最佳方法可能是基于每个项目的大小以某种方式计算滚动条范围.那可能是不切实际或不希望的.取而代之的是,这是自定义RecyclerView的简单实现,您可以将其与尝试获取所需的内容一起玩.它将向您展示如何使用各种滚动方法来控制滚动条.它将根据显示的项目数将拇指的大小保持在初始大小.要记住的关键是滚动范围是任意的,但所有其他度量(范围,偏移)必须使用相同的单位.

The best way to handle this situation may be to somehow calculate the scroll bar range based on the size of each item. That may not be practical or desirable. In lieu of that, here is a simple implementation of a custom RecyclerView that you can play with to try to get what you want. It will show you how you can use the various scroll methods to control the scroll bar. It will stick the size of the thumb to an initial size based upon the number of items displayed. The key thing to remember is that the scroll range is arbitrary but all other measurements (extent, offset) must use the same units.

请参阅 computeVerticalScrollRange() .

这是结果的视频.

更新:该代码已更新,可以解决一些问题:拇指的移动变得不那么急促,并且当RecyclerView滚动到手指时,拇指将停在底部.底部.在代码之后也有一些警告.

Update: The code has been updated to correct a few issues: The movement of the thumb is less jerky and the thumb will now come to rest at the bottom as the RecyclerView scrolls to the bottom. There are also a few caveats that are given after the code.

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.";
}

注意事项 根据实现方式,可能需要解决以下问题.

Caveats The following issues may need to be addressed depending on the implementation.

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

The sample code works only for vertical scrolling. The sample code also assumes that the contents of the RecyclerView are static. Any updates to the data backing the RecyclerView may cause scrolling issues. If any changes are made that effect the height of any view displayed on the first full screen of the RecyclerView, the scrolling will be off. Changes below that will probably work OK. This is due to how the code calculates the scrolling offset.

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

To determine the base value for the scrolling offset, (variable mTopCutOff), the RecyclerView must be scrolled to the top the first time computeVerticalScrollRange() is invoked so views can be measured; otherwise, the code will stop with an "IllegalStateException". This is especially troublesome on an orientation change if the RecyclerView is scrolled at all. A simple way around this would be to inhibit restoration of the scrolling position so it defaults to the top on an orientation change.

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

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

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

I hope this helps. (btw, your MCVE made this a lot easier.)

这篇关于具有不同高度的项目的RecyclerView:滚动条的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆