web-dev-qa-db-fra.com

Le défilement vers le haut ne fonctionne pas avec SwipeRefreshLayout dans Listview

Je veux implémenter le défilement pour actualiser la fonctionnalité avec un listView. Il existe également d'autres éléments d'affichage dans le même fichier de mise en page qui s'affichent si la liste est vide. Voici mon fichier de mise en page. Le problème est que lorsque je fais défiler vers le bas, puis j'essaie de faire défiler vers le haut, au lieu de faire défiler jusqu'en haut, puis de le rafraîchir, il est simplement actualisé et le défilement vers le haut ne fonctionne pas.

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

Ceci est ma méthode 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;
    }
}
34
user3773337

Pour que SwipeRefreshLayout fonctionne, il doit être le parent direct de votre ListView, et le ListView doit être la première vue enfant active du SwipeRefreshLayout.

La documentation de SwipeRefreshLayout indique que le ListView doit être le seul enfant, mais il est correct s'il a plus d'un enfant tant que le ListView est le premier. Cela signifie, par exemple, que SwipeRefreshLayout fonctionnera correctement si vous utilisez un adaptateur avec une vue pour "vide". Par exemple:

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

Si vous pouvez gérer ce type de disposition, alors SwipeRefreshLayout fonctionnera correctement et vous n'aurez besoin d'aucune des solutions de contournement répertoriées dans les autres réponses.

Mon propre problème était que je chargeais mon ListView en tant que fragment, donc j'avais en fait:

<SwipeRefreshLayout>

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

</SwipeRefreshLayout>

Donc, le SwipeRefreshLayout choisissait le FrameLayout car il est "cible" et son implémentation par défaut canChildScrollUp() retournait toujours false. Une fois que j'ai déplacé le SwipeRefreshLayout à l'intérieur du fragment, tout a commencé à fonctionner correctement.

<FrameLayout>

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

</FrameLayout>
53
Paul LeBeau

J'ai eu le même problème et l'ai résolu:

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

Créez une mise en page comme celle-ci.

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

Si vous l'utilisez tel quel, chaque fois que vous faites défiler vers le haut dans cette vue, SwipeRefreshLayout se déclenche et se met à jour, rendant votre application incapable de faire défiler vers le haut dans une liste.

L'astuce consiste à câbler manuellement OnScrollListener à partir de ListView. Vous vérifiez simplement si la première ligne affichée correspond à la première position la plus haute, puis activez SwipeRefreshLayout. Sinon, désactivez-le.

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

Avec ce petit extrait, cela fonctionne maintenant parfaitement.

Bon codage!

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

28

Créez votre propre implémentation de SwipeRefreshLayout et remplacez canChildScrollUp de cette manière:

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

return false;
}

il suffit de remplacer par n'importe quelle sous-classe de ScrollView.

10
Rishabh

J'ai eu un problème similaire où l'enfant de mon SwipeRefreshLayout était un FrameLayout qui avait un TextView et ListView comme enfants et quand j'ai fait défiler le ListView il essaiera de faire un rafraîchissement.

Je l'ai corrigé en utilisant un FrameLayout personnalisé qui remplace la méthode canScrollVertically()

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

Encore plus simple, définissez SwipeRefreshLayout activé sifirstVisibleItem ou désactivé sinon:

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);
    }
});
3
Kevin Crain

My Gridview commence à travailler avec 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);
            }

         }


       });

mise en page en 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>
0
Najaf Ali

Fondamentalement, nous voulons faire défiler quelque chose de défilable. Nous pouvons mettre toutes les vues dans 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>

Mais faire défiler ListView peut nécessiter quelques modifications - wrap_content ListView

0
Adrian Grygutis