Scroll up does not work with SwipeRefreshLayout in Listview

一个人想着一个人 提交于 2019-11-27 04:24:40

问题


I want to implement scroll to refresh functionality with a listView. Also there are other view elements in the same layout file which are displayed if the list is empty. Here is my layout file. The problem is that when I scroll down and then try to scroll up, instead of scrolling all the way to the top and then refreshing it just refreshes there and scroll up is not working.

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

This is my onScroll method.

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

回答1:


In order for SwipeRefreshLayout to work, it needs to be the direct parent of your ListView, and the ListView should be the first active child view of the SwipeRefreshLayout.

The documentation for SwipeRefreshLayout says that the ListView should be the only child, but it is okay if it has more than one child as long as the ListView is first. This means, for instance, that SwipeRefreshLayout will work fine if you are using an adapter with a view for "empty". For example:

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

If you can manage this sort of layout, then SwipeRefreshLayout will work fine and you won't need any of the workarounds listed in other answers.

My own problem was that that I was loading my ListView as a Fragment, so I actually had:

<SwipeRefreshLayout>

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

</SwipeRefreshLayout>

So the SwipeRefreshLayout was choosing the FrameLayout as it's "target" and its default canChildScrollUp() implementation was always returning false. Once I moved the SwipeRefreshLayout inside the Fragment, everything started working correctly.

<FrameLayout>

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

</FrameLayout>



回答2:


I had the same problem and solved it:

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



回答3:


Create a layout like this one.

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

If you use this as-is, everytime you scroll up in that view, the SwipeRefreshLayout fires and updates, making your app unable to scroll up in a list.

The trick here is to wire the OnScrollListener from the ListView manually. You just check if the first row being shown matches the first top-most position, and then enable the SwipeRefreshLayout. Otherwise, disable it.

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

With this little snippet now it works perfectly.

Happy coding!

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




回答4:


Make your own implementation of SwipeRefreshLayout and override the canChildScrollUp in this way:

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

return false;
}

just replace with any subclass of ScrollView.




回答5:


I had a similar problem where the child of my SwipeRefreshLayout was a FrameLayout which had a TextView and ListView as children and when I scrolled up on the ListView it would try to do a refresh.

I fixed it by using a custom FrameLayoutwhich overrides the canScrollVertically() method

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



回答6:


Even simpler just set SwipeRefreshLayout enabled if firstVisibleItem or disabled if not:

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



回答7:


My Gridview Start Working with 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);
            }

         }


       });

layout in 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>



回答8:


Basically, we want to scroll something scrollable. We can put all views in 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>

But scrolling ListView may require some modifications - wrap_content ListView




回答9:


For Xamarin I had the same issue (Scroll up does not work with SwipeRefreshLayout in Listview) and implemented it this way.

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


来源:https://stackoverflow.com/questions/25270171/scroll-up-does-not-work-with-swiperefreshlayout-in-listview

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!