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

12 回答 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 作为片段加载,所以我实际上有:

<SwipeRefreshLayout>

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

</SwipeRefreshLayout>

所以 SwipeRefreshLayout 选择 FrameLayout 作为它的“目标”,它的默认canChildScrollUp()实现总是返回 false。一旦我将 SwipeRefreshLayout 移动到 Fragment 内,一切都开始正常工作。

<FrameLayout>

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

</FrameLayout>
于 2014-12-29T13:44:35.553 回答
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);
        }
    }
});
于 2016-03-03T17:58:49.463 回答
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/

于 2014-11-04T05:55:47.830 回答
11

制作自己的 SwipeRefreshLayout 实现并以这种方式覆盖 canChildScrollUp:

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

return false;
}

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

于 2014-09-29T12:31:53.227 回答
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;
    }
}
于 2015-09-23T01:39:50.877 回答
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);
    }
});
于 2015-09-23T09:58:05.907 回答
1

如果RecyclerViewListView不是SwipeRefreshLayout那么这个问题的直接孩子。

最简单的解决方案是提供OnChildScrollUpCallback实现并适当地返回结果。在下面的 Kotlin 代码中,在布局代码中也可以看到refreshLayoutisSwipeRefreshLayoutrecyclerViewis 。RecyclerViewxml

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>
于 2020-09-22T19:30:56.807 回答
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);
        }
    });
于 2021-03-04T11:44:28.067 回答
0

我的 Gridview 开始使用 Swipefreshlayout

gridView.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 (gridView.getChildAt(0) != null) {
                mrefreshlayout.setEnabled(gridView.getFirstVisiblePosition() == 0 && gridView.getChildAt(0).getTop() == 0);
            }

         }


       });

xml中的布局

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

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

        <GridView
            android:background="#ffffff"
            android:gravity="center"
            android:verticalSpacing="2dp"
            android:horizontalSpacing="2dp"
            android:numColumns="2"
            android:id="@+id/grid_view"
            android:animateLayoutChanges="true"
            android:overScrollMode="always"
            android:scrollbars="vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>

        <TextView
            android:id="@+id/nofieldtv"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_above="@+id/adlayout"
            android:textColor="#000000"
            android:gravity="center"
            android:visibility="gone"
            android:textSize="16sp"
            android:layout_below="@+id/headerLayout"
            android:layout_marginTop="2dp">

        </TextView>

    </LinearLayout>


</android.support.v4.widget.SwipeRefreshLayout>
于 2019-04-04T05:42:07.553 回答
0

@the_dani已经给出了正确的答案,但这是因为listview我在这里为您提供recylerview与滑动刷新布局发生滚动冲突的解决方案。因为Recyclerview你必须使用LayoutManagerfor get first visible item

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

});
于 2021-05-07T13:33:32.623 回答
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

于 2019-04-30T12:53:53.677 回答
-1

对于 Xamarin,我遇到了同样的问题(向上滚动不适用于SwipeRefreshLayoutin ListView)并以这种方式实现。

public void OnScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {

}

public void OnScrollStateChanged(AbsListView view, [GeneratedEnum]ScrollState scrollState) {
    if (listView.GetChildAt(0) != null) {
        swipeRefreshLayout.Enabled = listView.FirstVisiblePosition == 0 && listView.GetChildAt(0).Top == 0;
    }
}
于 2019-09-19T19:17:56.083 回答