在ListView中,使用SwipeRefreshLayout时向上滑动无法正常工作

39

我想在listView中实现下拉刷新功能。同时,如果列表为空,同一布局文件中还显示了其他视图元素。以下是我的布局文件。问题是,当我向下滚动然后尝试向上滚动时,它不会滚动到顶部并刷新,而是仅在那里刷新,向上滚动不起作用。

<android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/swipe_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <RelativeLayout
        android:layout_width="wrap_content"
        android:layout_height="64dp"
        android:paddingLeft="16dp"
        android:paddingRight="16dp" >

        <ImageView
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:layout_alignParentLeft="true"
            android:layout_centerVertical="true"
            android:src="@drawable/inbox_empty" />

        <TextView
            android:id="@+id/noEventsText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_marginLeft="16dp"
            android:layout_toRightOf="@+id/noEventsIcon" />

        <View
            android:id="@+id/divider"
            android:layout_width="match_parent"
            android:layout_height="1px"
            android:layout_alignParentBottom="true"
            android:layout_marginLeft="4dp"
            android:layout_marginRight="4dp"
            android:background="@color/dividerOnBlack" />
    </RelativeLayout>

    <ListView
        android:id="@+id/list_items"
        android:layout_width="match_parent"
        android:layout_height="fill_parent"
        android:cacheColorHint="@android:color/transparent"
        android:choiceMode="multipleChoice"
        android:descendantFocusability="afterDescendants"
        android:divider="@android:color/transparent"
        android:dividerHeight="0px"
        android:scrollbars="none" />
</LinearLayout>

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

这是我的onScroll方法。

@Override
public void onScroll(AbsListView view,int firstVisibleItem,int visibleItemCount,int totalItemCount) {
    // If the total item count is zero and the previous isn't, assume the
    // list is invalidated and should be reset back to initial state
    if (totalItemCount < previousTotalItemCount) {
        this.currentPage = this.startingPageIndex;
        this.previousTotalItemCount = totalItemCount;
        if (totalItemCount == 0) { this.loading = true; } 
    }

    // If it's still loading, we check to see if the dataset count has
    // changed, if so we conclude it has finished loading and update the current page
    // number and total item count.
    if (loading && (totalItemCount > previousTotalItemCount)) {
        loading = false;
        previousTotalItemCount = totalItemCount;
        currentPage++;
    }

    // If reverse then the firstVisibleItem is calculated wrong
    if (reverse) {
        firstVisibleItem = totalItemCount - firstVisibleItem;
    }
    // If it isn't currently loading, we check to see if we have breached
    // the visibleThreshold and need to reload more data.
    // If we do need to reload some more data, we execute onLoadMore to fetch the data.
    if (!loading && (totalItemCount - visibleItemCount)<=(firstVisibleItem + visibleThreshold)) {
        onLoadMore(currentPage + 1, totalItemCount);
        loading = true;
    }
}
12个回答

59
为了让SwipeRefreshLayout正常工作,它需要作为您的ListView的直接父级,并且ListView应该是SwipeRefreshLayout的第一个活动子视图。
SwipeRefreshLayout的文档说ListView应该是唯一的子视图,但只要ListView是第一个子视图就可以有多个子视图。这意味着,例如,如果您使用适配器并具有“空”视图,则SwipeRefreshLayout将正常工作。例如:
<android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/swipe_refresh"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".NearbyJobsActivity$PlaceholderFragment">

    <ListView
        android:id="@android:id/list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:divider="@color/list_divider"
        android:dividerHeight="1dp"
        android:listSelector="@drawable/list_row_selector" />

    <TextView
        android:id="@android:id/empty"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center" />

</android.support.v4.widget.SwipeRefreshLayout>
如果您能够管理这种布局方式,那么SwipeRefreshLayout将可以正常工作,您将不需要其他答案中列出的任何解决方法。
我的问题是我将我的ListView作为Fragment加载,因此实际上有:
<SwipeRefreshLayout>

    <FrameLayout>           )
         <ListView/>        \ fragment
         <TextView/>        /
    </FrameLayout>          )

</SwipeRefreshLayout>

因此,SwipeRefreshLayout选择FrameLayout作为其“目标”,并且其默认的canChildScrollUp()实现始终返回false。一旦我将SwipeRefreshLayout移动到Fragment内部,一切就开始正常工作了。

<FrameLayout>

    <SwipeRefreshLayout>    )
         <ListView/>        \ fragment
         <TextView/>        /
    </SwipeRefreshLayout>   )

</FrameLayout>

41

我曾遇到同样的问题并解决了它:

listView = (ListView) findViewById(R.id.listView);
listView.setOnScrollListener(new AbsListView.OnScrollListener() {
    @Override
     public void onScrollStateChanged(AbsListView view, int scrollState) {

    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        if (listView.getChildAt(0) != null) {
        swipeRefreshLayout.setEnabled(listView.getFirstVisiblePosition() == 0 && listView.getChildAt(0).getTop() == 0);
        }
    }
});

好的解决方案! - gc986
2
这是有史以来最好的解决方案。非常简单的代码片段。 - Thecarisma
感谢!这是唯一一个可行的方法,因为使用默认列表需要查找 android.R.id.list - skryshtafovych
应该是被接受的答案!感谢您提供的解决方案。 - hetsgandhi
太棒了,它解决了我的GridView问题,非常感谢你。 - Najaf Ali

28
创建一个像这样的布局。
<android.support.v4.widget.SwipeRefreshLayout  
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/swipe_container"
android:layout_width="match_parent"
android:layout_height="match_parent">

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="@string/guides_no_results"
        android:id="@+id/empty_view"
        android:gravity="center"
        android:padding="16dp"
        android:fontFamily="sans-serif-light"
        android:textSize="20sp"
        android:visibility="gone"/>


    <ListView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:drawSelectorOnTop="true"
        android:divider="@android:color/transparent"
        android:dividerHeight="0dp"
        android:id="@+id/guides_list" />

</LinearLayout>

如果你直接使用它,那么每次在视图中向上滚动时,SwipeRefreshLayout都会触发更新,导致你的应用程序无法在列表中向上滚动。

这里的诀窍是手动将ListView的OnScrollListener连接起来。你只需要检查正在显示的第一行是否与最顶部的位置相匹配,然后启用SwipeRefreshLayout。否则,禁用它。

guidesList.setOnScrollListener(new AbsListView.OnScrollListener()
{  
    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState)
    {

    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
    {
        int topRowVerticalPosition = (guidesList == null || guidesList.getChildCount() == 0) ? 0 : guidesList.getChildAt(0).getTop();
        swipeContainer.setEnabled(firstVisibleItem == 0 && topRowVerticalPosition >= 0);
    }
});

有了这个小片段,现在它可以完美地运行。

愉快的编码!

来源 - http://nlopez.io/swiperefreshlayout-with-listview-done-right/


2
虽然这个链接可能回答了问题,但最好在此处包含答案的基本部分并提供参考链接。如果链接页面更改,仅有链接的答案可能会失效。 - Zielu
@MarkusWMahlberg 这个标记是针对原始带有链接的回答,但这已经在此期间得到修复。 - Zielu
@Zielu 好的,那么我们可以删除不必要的评论了 ;) - Markus W Mahlberg
它能够工作,但我在这方面有不同的问题。当列表中的第一行大小大于其父级(例如具有非常长文本的文本视图)时,它不会显示任何其他行。 - Malav Shah
这对我来说可行,但如果您在片段中实现此操作,则需要引用您的“Activity”的“SwipeRefreshLayout”,这并不理想。 - ban-geoengineering

11

制作自己的SwipeRefreshLayout实现,并以以下方式重写canChildScrollUp:

@Override
public boolean canChildScrollUp() {
if (scrollView != null)
    return scrollView.canScrollVertically(-1);

return false;
}

只需替换为 ScrollView 的任何子类即可。


5

我遇到了一个类似的问题,我的SwipeRefreshLayout的子项是一个FrameLayout,它有一个TextViewListView作为子项,当我在ListView上向上滑动时,它会尝试进行刷新。

我通过使用自定义的FrameLayout解决了这个问题,它覆盖了canScrollVertically()方法。

包名:com.wi.director.ui.common;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.FrameLayout;

/**
 * Created by devansh on 9/22/15.
 */
public class FrameLayoutForSwipeRefresh extends FrameLayout {

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

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

    public FrameLayoutForSwipeRefresh(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public boolean canScrollVertically(int direction) {
        if (super.canScrollVertically(direction)) {
            return true;
        }

        int cc = getChildCount();
        for (int i = 0; i < cc; i++) {
            if (getChildAt(i).canScrollVertically(direction)) {
                return true;
            }
        }

        return false;
    }
}

我不得不稍微改变我的片段布局以与你的匹配(好吧,没有TextView),这样才能正常工作,但它确实非常有效。一个漂亮、干净的解决方案 - 谢谢! :-) - ban-geoengineering

3

更简单的方法是只需设置SwipeRefreshLayout,如果firstVisibleItem存在,则启用,否则禁用:

yourList.setOnScrollListener(new AbsListView.OnScrollListener() {
    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {

    }

    @Override 
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        yourSwipeRefreshLayout.setEnabled(firstVisibleItem == 0);
    }
});

1
如果RecyclerViewListView不是SwipeRefreshLayout的直接子项,则会出现此问题。最简单的解决方案是提供OnChildScrollUpCallback实现并适当返回结果。在下面的Kotlin代码中,refreshLayoutSwipeRefreshLayout,而recyclerViewRecyclerView,如xml布局代码所示。
refreshLayout.setOnChildScrollUpCallback(object : SwipeRefreshLayout.OnChildScrollUpCallback {
  override fun canChildScrollUp(parent: SwipeRefreshLayout, child: View?): Boolean {
    if (recyclerView != null) {
      return recyclerView.canScrollVertically(-1)
    }
    return false
  }
})

当涉及到编程时,xml布局大致如下:

 <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
    android:id="@+id/refreshLayout"
    ...
    lots of other views i.e TextView, ImageView, MotionLayout
    ...
    ...
    ...
    <androidx.recyclerview.widget.RecyclerView
       android:id="@+id/recyclerView".../>
       
    ...
    ...
    ...
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

0

@the_dani 给出了正确的答案,但是它是针对 listview 的,所以在这里我给你提供了解决方案,用于解决 recylerview 在滚动时与下拉刷新布局的冲突。对于 Recyclerview,您必须使用 LayoutManager 来获取 第一个可见项

 rvStaggered.addOnScrollListener(new RecyclerView.OnScrollListener() {

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                  if (recyclerView.getChildAt(0) != null) {
                    swipeMain.setEnabled(
                            linearLayoutManager.findFirstVisibleItemPosition() == 0
                                    && recyclerView.getChildAt(0).getTop() == 0);
                }
            }

});

0

尝试以下代码:

 recyclerview.addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            int topRowVerticalPosition = (binding.rvMessageList == null || binding.rvMessageList.getChildCount() == 0) ? 0 : binding.rvMessageList.getChildAt(0).getTop();
            swipeRefresh.setEnabled(topRowVerticalPosition >= 0);
        }
    });

0

基本上,我们想要滚动可滚动的内容。我们可以将所有视图放在ScrollView中。

<androidx.swiperefreshlayout.widget.SwipeRefreshLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

        </LinearLayout>
    </ScrollView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

但是滚动ListView可能需要一些修改 - wrap_content ListView


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接