android – RecyclerView与不同高度的项目:滚动条

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

>有没有人有同样的问题并修复它?
>如何覆盖滚动条高度和位置的计算以提出自己的实现?

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

我想,问题是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第一个完整屏幕上显示的任何视图的高度,则滚动将关闭.下面的更改可能会正常工作.这是由于代码如何计算滚动偏移量.

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

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

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

我希望这有帮助. (顺便说一句,你的MCVE使这更容易.)

相关文章

Android性能优化——之控件的优化 前面讲了图像的优化,接下...
前言 上一篇已经讲了如何实现textView中粗字体效果,里面主要...
最近项目重构,涉及到了数据库和文件下载,发现GreenDao这个...
WebView加载页面的两种方式 一、加载网络页面 加载网络页面,...
给APP全局设置字体主要分为两个方面来介绍 一、给原生界面设...
前言 最近UI大牛出了一版新的效果图,按照IOS的效果做的,页...