【问题标题】:Android: How to get the current X offset of RecyclerView?Android:如何获取 RecyclerView 的当前 X 偏移量?
【发布时间】:2015-02-14 22:42:34
【问题描述】:

我将Scrollview 用于无限的“时间选择器轮播”,发现这不是最好的方法 (last question)

现在,我找到了Recycler View,但我无法获取recyclerView X 方向的当前滚动偏移量? (假设每个item是100px宽,第二个item只有50%可见,所以scroll-offset是150px)

  • 所有项目的宽度相同,但在第一个项目之前,最后一个项目之后有一些差距
  • recyclerView.getScrollX() 返回 0(文档说:初始滚动值)
  • LayoutManagerfindFirstVisibleItemPosition,但我无法用它计算 X 偏移量

更新

我刚刚找到了一种方法来继续跟踪 X 位置,同时使用 onScrolled 回调更新值,但我更喜欢获取实际值而不是一直跟踪它!

private int overallXScroll = 0;
//...
mRecyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);

            overallXScroll = overallXScroll + dx;

            Log.i("check","overallXScroll->" + overallXScroll);

        }
    });

【问题讨论】:

  • 你的意思不是“x”而是项目位置?
  • 我刚刚澄清了(?)我的问题。我正在寻找所有项目在 X 方向上的当前偏移量。
  • RecyclerView 不支持 getScrollX/Y 因为它不能保证正确的结果。例如,用户可以调用 scrollToPosition(100)。要计算真正的 scrollY,它必须将所有项目布局在 0 到 100 之间。不可行。只要您的适配器内容不变,setOnScrollListener 就可以工作。或者,您可以使用 LLM#findLastVisibleItemPosition 并使用 view.getLeft() 自己计算总偏移量
  • 通过在监听器中添加滚动距离来保存总滚动是迄今为止我发现的最简单和有效的解决方案。但我知道,RecyclerView 在被系统杀死后恢复活动时会保存它的滚动位置。我的意思是,我们的自定义变量在从后台恢复后可能会不同步。

标签: android scroll position android-recyclerview


【解决方案1】:

RecyclerView 已经有了获取水平和垂直滚动偏移量的方法

mRecyclerView.computeHorizontalScrollOffset()
mRecyclerView.computeVerticalScrollOffset()

这适用于包含相同高度(用于垂直滚动偏移)和相同宽度(用于水平滚动偏移)的单元格的 RecyclerViews

【讨论】:

  • 这对我不起作用,有时在滚动列表时会跳近 400 像素,似乎缺少一些东西
  • 这是因为您的单元格没有相同的宽度(或高度,取决于滚动方向)。 computeXScrollOffset 使用平均单元格尺寸,因此如果您的单元格大小不同,则此方法无用。我已经尝试了几个小时来找到一个合适的解决方案来计算这个:)
  • @andreimarinescu 有什么解决方案吗?
  • @urSus 我最终自己计算了偏移量,方法是添加一个滚动侦听器并计算前一个偏移量和滚动距离之间的差异。它适用于我的用例,但是如果回收器视图单元格动态改变它们的高度,这将不起作用,因为向下滚动的距离将不等于您需要向上滚动的距离。
【解决方案2】:

解决方案 1: setOnScrollListener

将您的 X-Position 保存为类变量并更新 onScrollListener 中的每个更改。确保不要重置 overallXScroll(例如 onScreenRotationChange

 private int overallXScroll = 0;
 //...
 mRecyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);

        overallXScroll = overallXScroll + dx;
        Log.i("check","overall X  = " + overallXScroll);

    }
 });

解决方案2:计算当前位置。

就我而言,我有一个水平列表,其中填充了时间值(0:00、0:30、1:00、1:30 ... 23:00、23:30)。我正在从屏幕中间的时间项(计算点)计算时间。这就是为什么我需要 RecycleView 的确切 X-Scroll 位置

  • 每个时间项的宽度相同,均为 60dp(仅供参考:60dp = 30min,2dp = 1min)
  • 第一项(标题项)有一个额外的填充,设置 0min 到中心

    private int mScreenWidth = 0;
    private int mHeaderItemWidth = 0;
    private int mCellWidth = 0;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
    
      //init recycle views
      //...
      LinearLayoutManager mLLM = (LinearLayoutManager) getLayoutManager();
      DisplayMetrics displaymetrics = new DisplayMetrics();
      this.getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
      this.mScreenWidth = displaymetrics.widthPixels;
    
      //calculate value on current device
      mCellWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 60, getResources()
            .getDisplayMetrics());
    
      //get offset of list to the right (gap to the left of the screen from the left side of first item)
      final int mOffset = (this.mScreenWidth / 2) - (mCellWidth / 2);
    
      //HeaderItem width (blue rectangle in graphic)
      mHeaderItemWidth = mOffset + mCellWidth;
    
      mRecyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
    
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
    
            //get first visible item
            View firstVisibleItem = mLLM.findViewByPosition(mLLM.findFirstVisibleItemPosition());
    
            int leftScrollXCalculated = 0;
            if (firstItemPosition == 0){
                   //if first item, get width of headerview (getLeft() < 0, that's why I Use Math.abs())
                leftScrollXCalculated = Math.abs(firstVisibleItem.getLeft());
            }
            else{
    
                   //X-Position = Gap to the right + Number of cells * width - cell offset of current first visible item
                   //(mHeaderItemWidth includes already width of one cell, that's why I have to subtract it again)
                leftScrollXCalculated = (mHeaderItemWidth - mCellWidth) + firstItemPosition  * mCellWidth + firstVisibleItem.getLeft();
            }
    
            Log.i("asdf","calculated X to left = " + leftScrollXCalculated);
    
        }
    });
    

    }

【讨论】:

  • 什么是firstItemPosition
  • 解决方案 1 在滚动非常快时可能会产生一定的错误。
  • 解决方案 2 完全救了我的后腿。我正在使用解决方案 1,并且在进行快速上下滚动时,我的 scrollY 一直偏离几个像素,但现在我只检查 mLLM.findFirstCompletelyVisibleItemPosition() == 0 是否我知道我是否在顶部,我可以将我的 scrollY 重置为再次同步。仅供参考:如果有人想知道mLLMLinearLayoutManager,您可以通过以下方式获得:LinearLayoutManager mLLM = (LinearLayoutManager) getLayoutManager(); 假设您正在使用扩展形式LinearLayoutManagerLayoutManager,当然谢谢@longilong!跨度>
  • @ZeroOne, firstImtePosition = mLLM.findFirstVisibleItemPosition();从线性布局管理器中,获取第一个可见项目位置。
【解决方案3】:

感谢@umar-qureshi 提供正确的线索!看来您可以使用OffsetExtentRange 来确定滚动百分比,这样

percentage = 100 * offset / (range - extent)

例如(放入OnScrollListener):

int offset = recyclerView.computeVerticalScrollOffset();
int extent = recyclerView.computeVerticalScrollExtent();
int range = recyclerView.computeVerticalScrollRange();

int percentage = (int)(100.0 * offset / (float)(range - extent));

Log.i("RecyclerView, "scroll percentage: "+ percentage + "%");

【讨论】:

  • recyclerView.computeVerticalScrollRange() 返回 0。
  • 如果要支持滚动条,请覆盖此方法。

【解决方案4】:
public class CustomRecyclerView extends RecyclerView {

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

    public CustomRecyclerView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public CustomRecyclerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public int getHorizontalOffset() {
        return super.computeHorizontalScrollOffset();
    }
}

比你需要获取偏移量的地方:

recyclerView.getHorizontalOffset()

【讨论】:

  • @MichaelDePhillips 您正在查看内部类 LayoutManager 的源代码,它确实返回零,但我们不关心该方法;)我们关心的实际方法返回以下内容: return mLayout.canScrollHorizo​​ntally( ) ? mLayout.computeHorizo​​ntalScrollOffset(mState) : 0;
  • @MichaelDePhillips 我们不关心该方法的原因是因为我们在初始化 recyclerview 时要做的第一件事就是设置一个布局管理器。在大多数情况下,这将是一个 LinearLayoutManager,它扩展了 LayoutManager 的方法,为垂直和水平问题提供准确的偏移量。
  • 进一步检查,您是正确的。感谢您清理它。我将编辑我的评论。
  • 这会返回一个任意单位的 int,并且不能可靠地为您提供准确的滚动量;那不是它的用途。例如,一旦我的第一个可见位置从 0 移动到 1,它就会从 1850px 下降到 300px。
  • computeHorizontalScrollOffset() 用于计算滚动条大小。我遇到了一个问题,当单元格的中心平滑地滚动出屏幕时,这个值会突然下降。另一方面,@longilong 的第一种解决方案可以获得可靠的偏移量。
【解决方案5】:

如果您需要知道当前页面及其与 RecyclerView 宽度相比的偏移量,您可能想尝试以下操作:

recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        super.onScrollStateChanged(recyclerView, newState);
    }

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);

        final int scrollOffset = recyclerView.computeHorizontalScrollOffset();
        final int width = recyclerView.getWidth();
        final int currentPage = scrollOffset / width;
        final float pageOffset = (float) (scrollOffset % width) / width;

        // the following lines just the example of how you can use it
        if (currentPage == 0) {
            leftIndicator.setAlpha(pageOffset);
        }

        if (adapter.getItemCount() <= 1) {
            rightIndicator.setAlpha(pageOffset);
        } else if (currentPage == adapter.getItemCount() - 2) {
            rightIndicator.setAlpha(1f - pageOffset);
        }
    }
});

如果 currentPage 的索引为 0,则 leftIndicator(箭头)将是透明的,如果只有一个 rightIndicator,则 rightIndicator 也是如此(在示例中,适配器始终至少有一项显示所以没有检查空值)。

这样,您基本上将拥有与使用来自ViewPager.OnPageChangeListener 的回调pageScrolled 几乎相同的功能。

【讨论】:

    【解决方案6】:

    通过覆盖 RecyclerView onScrolled(int dx,int dy) ,您可以获得滚动偏移量。但是当您添加或删除项目时,该值可能是错误的。

    public class TempRecyclerView extends RecyclerView {
       private int offsetX = 0;
       private int offsetY = 0;
       public TempRecyclerView(Context context) {
           super(context);
       }
    
       public TempRecyclerView(Context context, @Nullable AttributeSet attrs) {
          super(context, attrs);
       }
    
       public TempRecyclerView(Context context, @Nullable AttributeSet attrs,int defStyle){
          super(context, attrs, defStyle);
       }
    /**
     * Called when the scroll position of this RecyclerView changes. Subclasses 
       should usethis method to respond to scrolling within the adapter's data 
       set instead of an explicit listener.
     * <p>This method will always be invoked before listeners. If a subclass 
       needs to perform any additional upkeep or bookkeeping after scrolling but 
       before listeners run, this is a good place to do so.</p>
     */
       @Override
       public void onScrolled(int dx, int dy) {
      //  super.onScrolled(dx, dy);
          offsetX+=dx;
          offsetY+=dy;
         }
     }
    

    【讨论】:

      【解决方案7】:
      recyclerViewScrollListener = new RecyclerView.OnScrollListener() {
      
          @Override
          public void onScrollStateChanged(@NonNull @NotNull RecyclerView recyclerView, int newState) {
              super.onScrollStateChanged(recyclerView, newState);
              
          }
      
          @Override
          public void onScrolled(@NonNull @NotNull RecyclerView recyclerView, int dx, int dy) {
              super.onScrolled(recyclerView, dx, dy);
      
             
      
              int scrollBetween += dy;
      
              int scrollBetweenDP = pxToDp(scrollBetween, getContext());
      
              if (scrollBetweenDP > 50 ) {
                  // scroll down distance 50dp
      
              } else if (scrollBetweenDP < -50) {
                 //scroll up distance 50dp
              }
      
             
          }
      };
      

      【讨论】:

      • 您的答案可以通过额外的支持信息得到改进。请edit 添加更多详细信息,例如引用或文档,以便其他人可以确认您的答案是正确的。你可以找到更多关于如何写好答案的信息in the help center
      猜你喜欢
      • 1970-01-01
      • 2018-05-26
      • 1970-01-01
      • 2012-06-12
      • 2020-07-03
      • 2014-07-27
      • 2019-03-14
      • 2017-07-17
      • 2014-02-18
      相关资源
      最近更新 更多