Android RecyclerView 和 SwipeRefreshLayout

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/25178329/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-20 09:11:29  来源:igfitidea点击:

RecyclerView and SwipeRefreshLayout

androidswiperefreshlayoutandroid-recyclerview

提问by Lukas Olsen

I'm using the new RecyclerView-Layoutin a SwipeRefreshLayoutand experienced a strange behaviour. When scrolling the list back to the top sometimes the view on the top gets cut in.

RecyclerView-Layout在 a 中使用 newSwipeRefreshLayout并经历了一种奇怪的行为。当将列表滚动回顶部时,顶部的视图有时会被切入。

List scrolled to the top

列表滚动到顶部

If i try to scroll to the top now - the Pull-To-Refresh triggers.

如果我现在尝试滚动到顶部 - 下拉刷新触发。

cutted row

切排

If i try and remove the Swipe-Refresh-Layout around the Recycler-View the Problem is gone. And its reproducable on any Phone (not only L-Preview devices).

如果我尝试删除 Recycler-View 周围的 Swipe-Refresh-Layout,问题就消失了。并且它可以在任何手机上重现(不仅是 L-Preview 设备)。

 <android.support.v4.widget.SwipeRefreshLayout
    android:id="@+id/contentView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:visibility="gone">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/hot_fragment_recycler"
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</android.support.v4.widget.SwipeRefreshLayout>

That's my layout - the rows are built dynamically by the RecyclerViewAdapter (2 Viewtypes in this List).

这就是我的布局 - 行是由 RecyclerViewAdapter(此列表中的 2 个视图类型)动态构建的。

public class HotRecyclerAdapter extends TikDaggerRecyclerAdapter<GameRow> {

private static final int VIEWTYPE_GAME_TITLE = 0;
private static final int VIEWTYPE_GAME_TEAM = 1;

@Inject
Picasso picasso;

public HotRecyclerAdapter(Injector injector) {
    super(injector);
}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position, int viewType) {
    switch (viewType) {
        case VIEWTYPE_GAME_TITLE: {
            TitleGameRowViewHolder holder = (TitleGameRowViewHolder) viewHolder;
            holder.bindGameRow(picasso, getItem(position));
            break;
        }
        case VIEWTYPE_GAME_TEAM: {
            TeamGameRowViewHolder holder = (TeamGameRowViewHolder) viewHolder;
            holder.bindGameRow(picasso, getItem(position));
            break;
        }
    }
}

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
    switch (viewType) {
        case VIEWTYPE_GAME_TITLE: {
            View view = inflater.inflate(R.layout.game_row_title, viewGroup, false);
            return new TitleGameRowViewHolder(view);
        }
        case VIEWTYPE_GAME_TEAM: {
            View view = inflater.inflate(R.layout.game_row_team, viewGroup, false);
            return new TeamGameRowViewHolder(view);
        }
    }
    return null;
}

@Override
public int getItemViewType(int position) {
    GameRow row = getItem(position);
    if (row.isTeamGameRow()) {
        return VIEWTYPE_GAME_TEAM;
    }
    return VIEWTYPE_GAME_TITLE;
}

Here's the Adapter.

这是适配器。

   hotAdapter = new HotRecyclerAdapter(this);

    recyclerView.setHasFixedSize(false);
    recyclerView.setAdapter(hotAdapter);
    recyclerView.setItemAnimator(new DefaultItemAnimator());
    recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));

    contentView.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
        @Override
        public void onRefresh() {
            loadData();
        }
    });

    TypedArray colorSheme = getResources().obtainTypedArray(R.array.main_refresh_sheme);
    contentView.setColorSchemeResources(colorSheme.getResourceId(0, -1), colorSheme.getResourceId(1, -1), colorSheme.getResourceId(2, -1), colorSheme.getResourceId(3, -1));

And the code of the Fragmentcontaining the Recyclerand the SwipeRefreshLayout.

和的代码Fragment包含RecyclerSwipeRefreshLayout

If anyone else has experienced this behaviour and solved it or at least found the reason for it?

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

采纳答案by tom91136

Before you use this solution: RecyclerView is not complete yet, TRY NOT TO USE IT IN PRODUCTION UNLESS YOU'RE LIKE ME!

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

As for November 2014, there are still bugs in RecyclerView that would cause canScrollVerticallyto return false prematurely. This solution will resolve all scrolling problems.

至于 2014 年 11 月,RecyclerView 中仍然存在会导致canScrollVertically过早返回 false 的错误。此解决方案将解决所有滚动问题。

The drop in solution:

解决方案的下降:

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

    }
}

You don't even need to replace RecyclerViewin your code with FixedRecyclerView, replacing the XML tag would be sufficient! (The ensures that when RecyclerView is complete, the transition would be quick and simple)

您甚至不需要RecyclerView在代码中FixedRecyclerView替换为 ,替换 XML 标记就足够了!(确保当 RecyclerView 完成时,转换会快速而简单)

Explanation:

解释:

Basically, canScrollVertically(boolean)returns false too early,so we check if the RecyclerViewis scrolled all the way to the top of the first view (where the first child's top would be 0) and then return.

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

EDIT: And if you don't want to extend RecyclerViewfor some reason, you can extend SwipeRefreshLayoutand override the canChildScrollUp()method and put the checking logic in there.

编辑:如果您RecyclerView出于某种原因不想扩展,您可以扩展SwipeRefreshLayout和覆盖该canChildScrollUp()方法并将检查逻辑放在那里。

EDIT2:RecyclerView has been released and so far there's no need to use this fix.

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

回答by krunal patel

write the following code in addOnScrollListenerof the RecyclerView

写下面的代码中addOnScrollListenerRecyclerView

Like this:

像这样:

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

回答by balachandarkm

I came across the same problem recently. I tried the approach suggested by @Krunal_Patel, But It worked most of the times in my Nexus 4 and didn't work at all in samsung galaxy s2. While debugging, recyclerView.getChildAt(0).getTop() is always not correct for RecyclerView. So, After going through various methods, I figured that we can make use of the method findFirstCompletelyVisibleItemPosition() of the LayoutManager to predict whether the first item of the RecyclerView is visible or not, to enable SwipeRefreshLayout.Find the code below. Hope it helps someone trying to fix the same issue. Cheers.

我最近遇到了同样的问题。我尝试了@Krunal_Patel 建议的方法,但它在我的 Nexus 4 中大部分时间都有效,而在三星 Galaxy s2 中根本无效。在调试时,recyclerView.getChildAt(0).getTop() 对于 RecyclerView 总是不正确的。所以,经过各种方法,我想我们可以利用LayoutManager的findFirstCompletelyVisibleItemPosition()方法来预测RecyclerView的第一项是否可见,启用SwipeRefreshLayout。找到下面的代码。希望它可以帮助尝试解决相同问题的人。干杯。

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

回答by Amrut Bidri

This is how I have resolved this issue in my case. It might be useful for someone else who end up here for searching solutions similar to this.

这就是我在我的情况下解决这个问题的方式。对于最终在这里搜索与此类似的解决方案的其他人来说,这可能很有用。

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

I hope this might definitely help someone who are looking for similar solution.

我希望这绝对可以帮助正在寻找类似解决方案的人。

回答by Keshav Gera

Source Codehttps://drive.google.com/open?id=0BzBKpZ4nzNzURkRGNVFtZXV1RWM

源代码https://drive.google.com/open?id=0BzBKpZ4nzNzURkRGNVFtZXV1RWM

recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {

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

回答by SANAT

Single line solution.

单线解决方案。

setOnScrollListeneris deprecated.

setOnScrollListener已弃用。

You can use setOnScrollChangeListenerfor same purspose like this :

您可以将setOnScrollChangeListener用于相同的目的,如下所示:

recylerView.setOnScrollChangeListener((view, i, i1, i2, i3) -> swipeToRefreshLayout.setEnabled(linearLayoutManager.findFirstCompletelyVisibleItemPosition() == 0));

回答by Elynad

In case of someone find this question and is not satisfied by the answer :

如果有人发现这个问题并且对答案不满意:

It seems that SwipeRefreshLayout is not compatible with adapters that have more than 1 item type.

似乎 SwipeRefreshLayout 与具有 1 个以上项目类型的适配器不兼容。

回答by SilentKnight

I run into the same problem. My solution is overriding onScrolledmethod of OnScrollListener.

我遇到了同样的问题。我的解决办法是重写onScrolled的方法OnScrollListener

Workaround is here:

解决方法在这里:

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

回答by android developer

Here's one way to handle this, which also handles ListView/GridView.

这是处理此问题的一种方法,它也处理 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);
    }
  }

回答by alcsan

The krunal's solution is good, but it works like hotfix and does not cover some specific cases, for example this one:

krunal 的解决方案很好,但它像修补程序一样工作并且不涵盖某些特定情况,例如这个:

Let's say that the RecyclerView contains an EditText at the middle of screen. We start application (topRowVerticalPosition = 0), taps on the EditText. As result, software keyboard shows up, size of the RecyclerView is decreased, it is automatically scrolled by system to keep the EditText visible and topRowVerticalPosition should not be 0, but onScrolled is not called and topRowVerticalPosition is not recalculated.

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

Therefore, I suggest this solution:

因此,我建议此解决方案:

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

After you specify internal RecyclerView to SupportSwipeRefreshLayout, it will automatically send touch event to SupportSwipeRefreshLayout if RecyclerView cannot be scrolled up and to RecyclerView otherwise.

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