Android RecyclerView和SwipeRefreshLayout

Android RecyclerView和SwipeRefreshLayout,android,swiperefreshlayout,android-recyclerview,Android,Swiperefreshlayout,Android Recyclerview,我在一个SwipeRefreshLayout中使用新的RecyclerView布局,遇到了一个奇怪的行为。当将列表滚动回顶部时,顶部的视图有时会被打断 如果我现在尝试滚动到顶部,将触发拉刷新 如果我尝试删除回收器视图周围的滑动刷新布局,那么问题就消失了。而且它可以在任何手机上复制(不仅仅是L-Preview设备) 以及包含回收器的片段的代码和SwipeRefreshLayout 如果有其他人经历过这种行为并解决了它,或者至少找到了它的原因?在RecyclerView的addOnScrollL

我在一个
SwipeRefreshLayout
中使用新的
RecyclerView布局
,遇到了一个奇怪的行为。当将列表滚动回顶部时,顶部的视图有时会被打断

如果我现在尝试滚动到顶部,将触发拉刷新

如果我尝试删除回收器视图周围的滑动刷新布局,那么问题就消失了。而且它可以在任何手机上复制(不仅仅是L-Preview设备)

以及包含
回收器的
片段的代码
SwipeRefreshLayout


如果有其他人经历过这种行为并解决了它,或者至少找到了它的原因?

RecyclerView的
addOnScrollListener
中编写以下代码

像这样:

    recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener(){
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            int topRowVerticalPosition =
                    (recyclerView == null || recyclerView.getChildCount() == 0) ? 0 : recyclerView.getChildAt(0).getTop();
            swipeRefreshLayout.setEnabled(topRowVerticalPosition >= 0);

        }

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

不幸的是,这是LinearLayoutManager中的一个已知错误。当第一项可见时,它不会正确计算Crolloffset。 将在释放时修复。

使用此解决方案之前: RecyclerView尚未完成,除非你像我一样,否则不要在生产中使用它

截至2014年11月,RecyclerView中仍存在漏洞,可能导致
canScrollVertical
过早返回false。此解决方案将解决所有滚动问题

滴入式解决方案:

public class FixedRecyclerView extends RecyclerView {
    public FixedRecyclerView(Context context) {
        super(context);
    }

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

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

    @Override
    public boolean canScrollVertically(int direction) {
        // check if scrolling up
        if (direction < 1) {
            boolean original = super.canScrollVertically(direction);
            return !original && getChildAt(0) != null && getChildAt(0).getTop() < 0 || original;
        }
        return super.canScrollVertically(direction);

    }
}
public class SupportSwipeRefreshLayout extends SwipeRefreshLayout {
    private RecyclerView mInternalRecyclerView = null;

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

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

    public void setInternalRecyclerView(RecyclerView internalRecyclerView) {
        mInternalRecyclerView = internalRecyclerView;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (mInternalRecyclerView.canScrollVertically(-1)) {
            return false;
        }
        return super.onInterceptTouchEvent(ev);
    }
}
公共类FixedRecyclView扩展了RecyclView{
公共FixedRecyclerView(上下文){
超级(上下文);
}
public FixedRecyclerView(上下文上下文、属性集属性){
超级(上下文,attrs);
}
public FixedRecyclerView(上下文上下文、属性集属性、int-defStyle){
超级(上下文、属性、定义样式);
}
@凌驾
公共布尔值可垂直滚动(整数方向){
//检查是否向上滚动
如果(方向<1){
布尔原始值=超级。可垂直滚动(方向);
return!original&&getChildAt(0)!=null&&getChildAt(0)。getTop()<0 | | original;
}
返回super.canscroll垂直(方向);
}
}
您甚至不需要将代码中的
RecyclerView
替换为
FixedRecyclerView
,替换XML标记就足够了!(确保当RecyclerView完成时,转换将快速而简单)

说明:

基本上,
canscrollvertical(boolean)
过早返回false,因此我们检查
RecyclerView
是否一直滚动到第一个视图的顶部(其中第一个子视图的顶部为0),然后返回

编辑: 如果出于某种原因不想扩展
RecyclerView
,可以扩展
SwipeRefreshLayout
并重写
canChildScrollUp()
方法,并将检查逻辑放在其中

EDIT2:
RecyclerView已经发布,到目前为止没有必要使用此修复程序。

我最近遇到了同样的问题。我尝试了@Krunal_Patel建议的方法,但它在我的Nexus 4中大部分时间都有效,在三星galaxy s2中根本不起作用。调试时,RecycleView.getChildAt(0).getTop()对于RecycleView总是不正确的。因此,在研究了各种方法之后,我认为我们可以利用LayoutManager的方法findFirstCompletelyVisibleItemPosition()来预测RecyclerView的第一项是否可见,以启用SwiperFreshLayout。找到下面的代码。希望它能帮助试图解决相同问题的人。干杯

    recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {

        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        }

        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            swipeRefresh.setEnabled(linearLayoutManager.findFirstCompletelyVisibleItemPosition() == 0);
        }
    });

我也经历过同样的问题。我通过添加滚动监听器解决了这个问题,该监听器将等待在
RecyclerView
上绘制预期的第一个可见项。您也可以绑定其他滚动侦听器,沿此滚动侦听器。在使用标题视图保持架的情况下,当应启用
SwipeRefreshLayout
时,添加预期的第一个可见值以将其用作阈值位置

public class SwipeRefreshLayoutToggleScrollListener extends RecyclerView.OnScrollListener {
        private List<RecyclerView.OnScrollListener> mScrollListeners = new ArrayList<RecyclerView.OnScrollListener>();
        private int mExpectedVisiblePosition = 0;

        public SwipeRefreshLayoutToggleScrollListener(SwipeRefreshLayout mSwipeLayout) {
            this.mSwipeLayout = mSwipeLayout;
        }

        private SwipeRefreshLayout mSwipeLayout;
        public void addScrollListener(RecyclerView.OnScrollListener listener){
            mScrollListeners.add(listener);
        }
        public boolean removeScrollListener(RecyclerView.OnScrollListener listener){
            return mScrollListeners.remove(listener);
        }
        public void setExpectedFirstVisiblePosition(int position){
            mExpectedVisiblePosition = position;
        }
        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
            notifyScrollStateChanged(recyclerView,newState);
            LinearLayoutManager llm = (LinearLayoutManager) recyclerView.getLayoutManager();
            int firstVisible = llm.findFirstCompletelyVisibleItemPosition();
            if(firstVisible != RecyclerView.NO_POSITION)
                mSwipeLayout.setEnabled(firstVisible == mExpectedVisiblePosition);

        }

        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            notifyOnScrolled(recyclerView, dx, dy);
        }
        private void notifyOnScrolled(RecyclerView recyclerView, int dx, int dy){
            for(RecyclerView.OnScrollListener listener : mScrollListeners){
                listener.onScrolled(recyclerView, dx, dy);
            }
        }
        private void notifyScrollStateChanged(RecyclerView recyclerView, int newState){
            for(RecyclerView.OnScrollListener listener : mScrollListeners){
                listener.onScrollStateChanged(recyclerView, newState);
            }
        }
    }

这就是我在我的案例中解决这个问题的方式。它可能会对其他人有用,他们最终在这里搜索类似的解决方案

recyclerView.addOnScrollListener(new OnScrollListener()
    {
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy)
        {
            // TODO Auto-generated method stub
            super.onScrolled(recyclerView, dx, dy);
        }

        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState)
        {
            // TODO Auto-generated method stub
            //super.onScrollStateChanged(recyclerView, newState);
            int firstPos=linearLayoutManager.findFirstCompletelyVisibleItemPosition();
            if (firstPos>0)
            {
                swipeLayout.setEnabled(false);
            }
            else {
                swipeLayout.setEnabled(true);
            }
        }
    });

我希望这肯定能帮助那些正在寻找类似解决方案的人。

我遇到了同样的问题。我的解决方案是覆盖
onScrolled
方法的
onscrollistener

解决方法如下:

    recyclerView.setOnScrollListener(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);
        int offset = dy - ydy;//to adjust scrolling sensitivity of calling OnRefreshListener
        ydy = dy;//updated old value
        boolean shouldRefresh = (linearLayoutManager.findFirstCompletelyVisibleItemPosition() == 0)
                    && (recyclerView.getScrollState() == RecyclerView.SCROLL_STATE_DRAGGING) && offset > 30;
        if (shouldRefresh) {
            swipeRefreshLayout.setRefreshing(true);
        } else {
            swipeRefreshLayout.setRefreshing(false);
        }
    }
});

这里有一种处理方法,它也处理ListView/GridView

public class SwipeRefreshLayout extends android.support.v4.widget.SwipeRefreshLayout
  {
  public SwipeRefreshLayout(Context context)
    {
    super(context);
    }

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

  @Override
  public boolean canChildScrollUp()
    {
    View target=getChildAt(0);
    if(target instanceof AbsListView)
      {
      final AbsListView absListView=(AbsListView)target;
      return absListView.getChildCount()>0
              &&(absListView.getFirstVisiblePosition()>0||absListView.getChildAt(0)
              .getTop()<absListView.getPaddingTop());
      }
    else
      return ViewCompat.canScrollVertically(target,-1);
    }
  }
公共类SwipeRefreshLayout扩展了android.support.v4.widget.SwipeRefreshLayout
{
公共布局(上下文)
{
超级(上下文);
}
公共布局(上下文、属性集属性)
{
超级(上下文,attrs);
}
@凌驾
公共布尔值canChildScrollUp()
{
查看目标=getChildAt(0);
if(AbsListView的目标实例)
{
最终AbsListView AbsListView=(AbsListView)目标;
返回absListView.getChildCount()>0
&&(absListView.getFirstVisiblePosition()>0 | | absListView.getChildAt(0)

.getTop()krunal的解决方案很好,但它的工作原理类似于修补程序,并且不涵盖某些特定情况,例如:

假设RecyclerView在屏幕中间包含一个EditText。我们启动应用程序(topRowVerticalPosition=0),点击EditText。结果,软件键盘显示,RecyclerView的大小减小,系统自动滚动它以保持EditText可见,topRowVerticalPosition不应为0,但不会调用onScrolled,也不会重新计算topRowVerticalPosition

因此,我建议采用以下解决方案:

public class FixedRecyclerView extends RecyclerView {
    public FixedRecyclerView(Context context) {
        super(context);
    }

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

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

    @Override
    public boolean canScrollVertically(int direction) {
        // check if scrolling up
        if (direction < 1) {
            boolean original = super.canScrollVertically(direction);
            return !original && getChildAt(0) != null && getChildAt(0).getTop() < 0 || original;
        }
        return super.canScrollVertically(direction);

    }
}
public class SupportSwipeRefreshLayout extends SwipeRefreshLayout {
    private RecyclerView mInternalRecyclerView = null;

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

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

    public void setInternalRecyclerView(RecyclerView internalRecyclerView) {
        mInternalRecyclerView = internalRecyclerView;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (mInternalRecyclerView.canScrollVertically(-1)) {
            return false;
        }
        return super.onInterceptTouchEvent(ev);
    }
}

将内部RecyclerView指定给SupportSwiperFreshLayout后,如果无法向上滚动RecyclerView,它将自动向SupportSwiperFreshLayout发送触摸事件,否则将发送到RecyclerView。

源代码

单线解决方案

不推荐使用setOnScrollListener
public class SwipeRefreshLayout extends android.support.v4.widget.SwipeRefreshLayout
  {
  public SwipeRefreshLayout(Context context)
    {
    super(context);
    }

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

  @Override
  public boolean canChildScrollUp()
    {
    View target=getChildAt(0);
    if(target instanceof AbsListView)
      {
      final AbsListView absListView=(AbsListView)target;
      return absListView.getChildCount()>0
              &&(absListView.getFirstVisiblePosition()>0||absListView.getChildAt(0)
              .getTop()<absListView.getPaddingTop());
      }
    else
      return ViewCompat.canScrollVertically(target,-1);
    }
  }
public class SupportSwipeRefreshLayout extends SwipeRefreshLayout {
    private RecyclerView mInternalRecyclerView = null;

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

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

    public void setInternalRecyclerView(RecyclerView internalRecyclerView) {
        mInternalRecyclerView = internalRecyclerView;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (mInternalRecyclerView.canScrollVertically(-1)) {
            return false;
        }
        return super.onInterceptTouchEvent(ev);
    }
}
recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {

    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
    }

    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        swipeRefresh.setEnabled(linearLayoutManager.findFirstCompletelyVisibleItemPosition() == 0);
    }
});
recylerView.setOnScrollChangeListener((view, i, i1, i2, i3) -> swipeToRefreshLayout.setEnabled(linearLayoutManager.findFirstCompletelyVisibleItemPosition() == 0));