2017-09-04 261 views
9

我有一个RecyclerView与滚动条不同高度的项目。 由于项目的高度不同,滚动条会根据当前显示的项目更改垂直尺寸(请参见屏幕截图)。 我创建了一个示例项目,显示问题hereRecyclerView与不同高度的项目:滚动条

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

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

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

large scrollbar small scrollbar

+0

在我看来,问题是你有一个滚动视图里面的回收视图,你呢? – azizbekian

+0

@azizbekian不,情况并非如此。 – FWeigl

+0

你有恒定数量的行吗?如果它是可变的,我认为你不能管理它。鉴于房车的性质,你有刚刚被提取时的高度 – crgarridos

回答

3

处理这种情况的最佳方法可能是以某种方式计算基于每个项目大小的滚动条范围。这可能不实际或不理想。取而代之的是,下面是一个自定义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使这更容易。)

+0

工作得很好,非常感谢!另外:“要记住的关键是滚动范围是任意的”,伟大的发现,我试图以像素计算大小。 – FWeigl

+0

@Ascorbin感谢您的赏金。我对我的例子的工作方式有点不满 - 拇指没有在适当的时间着陆,还有其他一些让我厌恶的事情。我有一个更新,如果你有兴趣,我会发布一个编辑给我的答案。我通常不喜欢回去更新已接受的答案,但我会发布的内容并没有太大差异,可能会有所帮助。 – Cheticamp

+0

很酷,期待。 – FWeigl

1

如果我没有记错的属性android:scollBarSize="Xdp"应该为你工作。将它添加到您的RecyclerView xml。

这样你决定的大小,它会保持不变。

+0

不错的想法,不幸的是scrollBarSize定义了滚动条的水平大小/宽度,而不是它的高度。 – FWeigl

1

使用项目位置作为滚动进度的度量。这会使您的滚动指示器变得有点跳跃,但至少它会保持固定大小。

RecyclerView有多种自定义滚动指示器的实现。大多数双倍作为快速滚动。

这里是my own implementation,根据RecyclerViewFastScroller library。基本上,一个必须创建一个自定义视图的子类,那将是动画,类似于滚动型和DrawerLayout:

  • 商店电流偏移
  • 在动画偏移的拇指查看位置通过View#offset*电话
  • 在布局设置基于当前偏移量的位置。

你可能不想现在开始学习所有的魔法,只需要使用一些现有的快速滚动库(RecyclerViewFastScroller或它的一个克隆)。