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));