J'ai implémenté SwipeRefreshLayout
dans mon application, mais elle ne peut contenir qu'un seul enfant direct, qui devrait être la liste. J'essaie de comprendre comment ajouter une vue texte vide au fichier XML de travail suivant:
<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" >
<ListView
Android:id="@+id/listViewConversation"
Android:layout_width="fill_parent"
Android:layout_height="fill_parent"
Android:dividerHeight="1dp" />
</Android.support.v4.widget.SwipeRefreshLayout>
Le placer dans une disposition linéaire/relative le rend bogué, car la vue liste sera toujours mise à jour lorsque vous souhaitez faire défiler la liste. Je peux penser à cela par programmation, mais je suppose que ce n’est pas la meilleure option.
Vous pouvez apprendre à l'implémenter en utilisant ce tutoriel: Balayez pour actualiser GUIDE
Donc, fondamentalement, tout fonctionne bien, mais j'aimerais ajouter une vue vide qui affiche un message lorsque la liste est vide.
La limitation à un seul enfant ne me plaisait pas ... De plus, l'implémentation actuelle de SwipeRefreshLayout a une gestion "magique" codée en dur pour ScrollView, ListView et GridView qui ne se déclenche que si la vue est l'enfant direct de votre propre vue.
Cela dit, la bonne nouvelle, c’est qu’il est open source, vous pouvez donc copier le code et vous adapter à vos besoins ou bien faire ce que j’ai fait:
Utilisez deux DIFFERENT SwipeRefreshLayout, un pour la vue vide et un pour le ListView.
<FrameLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
xmlns:tools="http://schemas.Android.com/tools"
Android:layout_width="match_parent"
Android:layout_height="match_parent"
tools:context="com.tobrun.example.swipetorefresh.MainActivity">
<Android.support.v4.widget.SwipeRefreshLayout
Android:id="@+id/swipeRefreshLayout_listView"
Android:layout_width="match_parent"
Android:layout_height="match_parent">
<ListView
Android:id="@+id/listView"
Android:layout_width="match_parent"
Android:layout_height="wrap_content" />
</Android.support.v4.widget.SwipeRefreshLayout>
<Android.support.v4.widget.SwipeRefreshLayout
Android:id="@+id/swipeRefreshLayout_emptyView"
Android:layout_width="match_parent"
Android:layout_height="match_parent">
<ScrollView
Android:layout_width="match_parent"
Android:layout_height="match_parent"
Android:fillViewport="true">
<TextView
Android:id="@+id/emptyView"
Android:text="@string/empty"
Android:layout_width="match_parent"
Android:layout_height="wrap_content"
Android:layout_gravity="center"
Android:gravity="center" />
</ScrollView>
</Android.support.v4.widget.SwipeRefreshLayout>
</FrameLayout>
Indiquez ensuite à votre liste que la vue liste vide est la disposition d'actualisation par balayage de la vue vide.
Désormais, la présentation d'actualisation vide sera automatiquement masquée par votre vue Liste lorsque vous aurez des données et sera affichée lorsque la liste sera vide.
La présentation d'actualisation par balayage de la liste ne doit pas recevoir d'événements tactiles car la liste est masquée.
Bonne chance.
Il n'y a pas besoin de solution de contournement.
Vous pouvez simplement utiliser cette hiérarchie de vues:
<FrameLayout ...>
<Android.support.v4.widget.SwipeRefreshLayout ...>
<ListView
Android:id="@Android:id/list" ... />
</Android.support.v4.widget.SwipeRefreshLayout>
<TextView
Android:id="@Android:id/empty" ...
Android:text="@string/empty_list"/>
</FrameLayout>
Ensuite, dans le code, vous appelez simplement:
_listView.setEmptyView(findViewById(Android.R.id.empty));
C'est tout.
ÉDITER: Si vous souhaitez pouvoir effectuer un balayage pour actualiser même lorsque la vue vide est affichée, vous devrez en quelque sorte éviter de masquer le ListView. Vous pourrez donc utiliser un ListView personnalisé contenant cette fonction:
@Override
public void setVisibility(final int visibility)
{
if(visibility!=View.GONE||getCount()!=0)
super.setVisibility(visibility);
}
Avec la solution que j'ai écrite, le glissement-d'actualisation est affiché quel que soit le nombre d'éléments que vous affichez.
Bien sûr, si vous voulez vraiment cacher le ListView, vous devez changer le code. Peut-être ajouter "setVisibilityForReal (...)" :)
Voici ce que j'ai fait:.
mBookedProductsListView.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView absListView, int i) {
}
@Override
public void onScroll(AbsListView absListView, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (firstVisibleItem == 0)
mSwipeRefreshLayout.setEnabled(true);
else
mSwipeRefreshLayout.setEnabled(false);
}
});
Mon xml:
<?xml version="1.0" encoding="utf-8"?>
<Android.support.v4.widget.SwipeRefreshLayout
xmlns:Android="http://schemas.Android.com/apk/res/Android"
Android:id="@+id/swipeRefreshLayout"
Android:layout_width="match_parent"
Android:layout_height="match_parent"
>
<RelativeLayout
Android:id="@+id/productListLL"
Android:orientation="vertical"
Android:layout_width="match_parent"
Android:layout_height="match_parent"
>
<EditText
Android:id="@+id/search"
Android:layout_width="match_parent"
Android:layout_height="40dp"
Android:layout_alignParentTop="true"
Android:background="#a4aeb8"
Android:drawableRight="@drawable/search_icon"
Android:paddingRight="15dp"
Android:textColor="@Android:color/white"
Android:paddingLeft="15dp"
Android:hint="Rechercher"
Android:textColorHint="@Android:color/white"
Android:inputType="textCapWords|textNoSuggestions"
/>
<include Android:layout_width="match_parent" Android:layout_height="wrap_content" layout="@layout/filter_by_categories_buttons" Android:id="@+id/categoriesTree" Android:layout_below="@id/search" />
<ListView
Android:id="@+id/productListLV"
Android:layout_width="match_parent"
Android:layout_height="match_parent"
Android:divider="@drawable/product_listview_divider"
Android:dividerHeight="1dp"
Android:scrollbars="none"
Android:overScrollMode="never"
Android:choiceMode="multipleChoiceModal"
Android:background="#e7eaef"
Android:visibility="gone"
Android:layout_below="@id/categoriesTree"
/>
</RelativeLayout>
</Android.support.v4.widget.SwipeRefreshLayout>
Fonctionne comme un charme.
J'ai rencontré le même problème. Il est gênant que SwipeRefreshLayout ne puisse avoir qu'un AdpaterView child.
Si une vue vide est définie pour un AdpaterView , elle sera masquée si aucune donnée n'est disponible. Cependant, SwipeRefreshLayout a besoin d'un AdpaterView pour fonctionner. Donc, j'étends AdpaterView pour qu'il reste affiché, même s'il est vide.
@Override
public void setVisibility(int visibility) {
if (visibility == View.GONE && getCount() == 0) {
return;
}
super.setVisibility(visibility);
}
Peut-être devez-vous également définir l’arrière-plan de la vue adaptateur comme transparent. Mais dans mon cas, ce n'est pas nécessaire, car la vue vide est un simple TextView .
Sur la base de certaines réponses ici et du code source de SwipeRefreshLayout, j'ai sous-classé la vue pour gérer spécifiquement le fait d'avoir un RecyclerView (ou ListView) ainsi qu'une vue "vide" à l'intérieur d'un conteneur qui est l'enfant.
Il attend une mise en page telle que
<SwipeRefreshLayoutWithEmpty ...>
<FrameLayout ...>
<TextView Android:text="List is Empty" ...>
<RecyclerView ...>
</FrameLayout>
</SwipeRefreshLayoutWithEmpty>
Le code est:
public class SwipeRefreshLayoutWithEmpty extends SwipeRefreshLayout {
private ViewGroup container;
public SwipeRefreshLayoutWithEmpty(Context context) {
super(context);
}
public SwipeRefreshLayoutWithEmpty(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean canChildScrollUp() {
// The swipe refresh layout has 2 children; the circle refresh indicator
// and the view container. The container is needed here
ViewGroup container = getContainer();
if (container == null) {
return false;
}
// The container has 2 children; the empty view and the scrollable view.
// Use whichever one is visible and test that it can scroll
View view = container.getChildAt(0);
if (view.getVisibility() != View.VISIBLE) {
view = container.getChildAt(1);
}
return ViewCompat.canScrollVertically(view, -1);
}
private ViewGroup getContainer() {
// Cache this view
if (container != null) {
return container;
}
// The container may not be the first view. Need to iterate to find it
for (int i=0; i<getChildCount(); i++) {
if (getChildAt(i) instanceof ViewGroup) {
container = (ViewGroup) getChildAt(i);
if (container.getChildCount() != 2) {
throw new RuntimeException("Container must have an empty view and content view");
}
break;
}
}
if (container == null) {
throw new RuntimeException("Container view not found");
}
return container;
}
}
Full Gist: https://Gist.github.com/grennis/16cb2b0c7f798418284dd2d754499b43
J'ai essayé de suivre quelque chose. Dans les deux vues vides, et dans le cas d’une liste, balayer actualisera les données, fastscroll fonctionnera également sans qu'aucun changement de code ne soit requis dans l’activité . et listview est placé après celui-ci dans le bloc SwipeToRefresh . Sample Code -
<FrameLayout
Android:layout_width="match_parent"
Android:layout_height="match_parent"
>
<TextView
Android:id="@+id/tv_empty_text"
Android:layout_width="match_parent"
Android:layout_height="wrap_content"
Android:layout_margin="32dp"
Android:gravity="center_horizontal"
Android:text="No item found"
Android:visibility="gone"/>
<Android.support.v4.widget.SwipeRefreshLayout
Android:id="@+id/swipe"
Android:layout_width="match_parent"
Android:layout_height="match_parent">
<ListView
Android:id="@+id/lv_product"
Android:layout_width="match_parent"
Android:layout_height="match_parent" />
</Android.support.v4.widget.SwipeRefreshLayout>
</FrameLayout>
Pourquoi ne pas tout envelopper à l'intérieur de SwipeRefreshLayout
<Android.support.v4.widget.SwipeRefreshLayout
Android:id="@+id/swipe"
Android:layout_width="match_parent"
Android:layout_height="match_parent" >
<LinearLayout
Android:layout_width="match_parent"
Android:layout_height="match_parent"
Android:orientation="vertical" >
<ListView
Android:id="@Android:id/list"
Android:layout_width="match_parent"
Android:layout_height="match_parent"
Android:layout_gravity="center_horizontal"/>
<RelativeLayout
Android:id="@Android:id/empty"
Android:layout_width="match_parent"
Android:layout_height="match_parent" >
...
</RelativeLayout>
</LinearLayout>
</Android.support.v4.widget.SwipeRefreshLayout>
Placez SwipeRefreshLayout dans un FrameLayout et d’autres vues derrière ce dernier.
<FrameLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
Android:layout_width="match_parent"
Android:layout_height="match_parent" >
<LinearLayout
Android:id="@+id/your_message_layout"
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="No result"/>
</LinearLayout>
<Android.support.v4.widget.SwipeRefreshLayout
Android:id="@+id/swipe_container"
Android:layout_width="match_parent"
Android:layout_height="match_parent" >
<ListView
Android:id="@+id/your_list_view"
Android:layout_width="match_parent"
Android:layout_height="wrap_content">
</ListView>
</Android.support.v4.widget.SwipeRefreshLayout>
</FrameLayout>
Probablement tard, mais si vous faites toujours face à ce problème, voici la solution propre! Il n'est pas nécessaire de créer une autre SwipeRefreshLayout
.
envelopper empty view
dans une ScrollView
. Coller AdapterView
et ScrollView
dans ViewGroup
et le mettre dans SwipeRefreshLayout
Échantillon:
<Android.support.v4.widget.SwipeRefreshLayout
Android:layout_width="match_parent"
Android:layout_height="match_parent">
<FrameLayout
Android:layout_width="match_parent"
Android:layout_height="match_parent">
<ListView
Android:layout_width="match_parent"
Android:layout_height="match_parent" />
<ScrollView
Android:layout_width="match_parent"
Android:layout_height="match_parent">
<TextView
Android:layout_width="250dp"
Android:layout_height="wrap_content"
Android:layout_gravity="center"
Android:gravity="center" />
</ScrollView>
</FrameLayout>
</Android.support.v4.widget.SwipeRefreshLayout>
MODIFIER
Voici un moyen beaucoup plus simple.
vérifiez si votre liste est vide. Si oui, alors le rendre invisible. Échantillon:
if(lst.size() > 0) {
mRecyclerView.setVisibility(View.VISIBLE);
//set adapter
}else
{
mRecyclerView.setVisibility(View.INVISIBLE);
findViewById(R.id.txtNodata).setVisibility(View.VISIBLE);
}
cela fera mRecyclerView
toujours là et tous les balayages fonctionneront bien maintenant même s'il n'y a pas de données!
Cela a fonctionné pour moi
<FrameLayout
xmlns:Android="http://schemas.Android.com/apk/res/Android"
Android:layout_width="match_parent"
Android:layout_height="match_parent"
Android:background="@color/lighter_white">
<Android.support.v4.widget.SwipeRefreshLayout
Android:id="@+id/swipeContainer"
Android:layout_width="match_parent"
Android:layout_height="match_parent">
<ListView
Android:id="@+id/list"
Android:layout_width="match_parent"
Android:layout_height="wrap_content"
Android:layout_marginLeft="10dp"
Android:layout_marginRight="10dp"
Android:background="@color/silver"
Android:layout_marginTop="10dp"
Android:divider="@Android:color/transparent"
Android:dividerHeight="8dp"
Android:padding="4dp"/>
</Android.support.v4.widget.SwipeRefreshLayout>
<TextView
Android:id="@+id/empty"
Android:text="@string/strNoRecordsFound"
Android:layout_width="match_parent"
Android:layout_height="match_parent"
Android:textColor="@Android:color/black"
Android:alpha="0.5"
Android:gravity="center">
</TextView>
</FrameLayout>
Une autre option qui fonctionne bien dans le cas où votre vue vide ne nécessite aucune interaction. Par exemple, il s’agit d’un texte simple qui dit "Aucune donnée ici".
<RelativeLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
xmlns:app="http://schemas.Android.com/apk/res-auto"
Android:layout_width="match_parent"
Android:layout_height="match_parent"
>
<TextView
Android:id="@+id/emptyView"
Android:layout_width="match_parent"
Android:layout_height="match_parent"
Android:gravity="center"
Android:visibility="visible"
Android:layout_centerInParent="true"
Android:text="@string/no_earnable_available"
Android:textSize="18dp"
Android:textColor="@color/black"
Android:background="@color/white"
/>
<some.app.RecyclerViewSwipeToRefreshLayout
Android:id="@+id/swipe_refresh_layout"
Android:layout_width="match_parent"
Android:layout_height="match_parent"
Android:paddingTop="4dp"
Android:background="@Android:color/transparent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
>
<Android.support.v7.widget.RecyclerView
Android:id="@+id/recyclerView"
Android:layout_width="match_parent"
Android:layout_height="match_parent"
Android:background="@Android:color/transparent"
/>
</some.app.RecyclerViewSwipeToRefreshLayout>
</RelativeLayout>
Cela place la vue vide derrière le SwipeToRefreshLayout qui est transparent et qui contient également un RecyclerView transparent.
Ensuite, dans le code, à l'endroit où vous ajoutez les éléments à l'adaptateur de vue Recycler, vous vérifiez si l'adaptateur est vide et, le cas échéant, définissez la visibilité de la vue vide sur "visible". Et vice versa.
L'astuce est que la vue est derrière la vue transparente du recycleur, ce qui signifie que la vue du recycleur et son parent, SwipeToRefreshLayout, gèrent le défilement lorsqu'il n'y a aucun élément dans le recycleur. La vue vide derrière ne sera même pas touchée et l'événement tactile sera donc consommé par SwipeTorefreshLayout.
La RecyclerSwipeTorefreshLayout personnalisée doit gérer la méthode canChildScrollUp de la manière suivante
@Override
public boolean canChildScrollUp() {
if (recycler == null) throw new IllegalArgumentException("recycler not set!");
else if (recycler.getAdapter().getItemCount() == 0){ // this check could be done in a more optimised way by setting a flag from the same place where you change the visibility of the empty view
return super.canChildScrollUp();
} else {
RecyclerView.LayoutManager layoutManager = recycler.getLayoutManager();
if (layoutManager instanceof LinearLayoutManager) {
return ((LinearLayoutManager) recycler.getLayoutManager()).findFirstVisibleItemPosition() != 0 ||
(((LinearLayoutManager) recycler.getLayoutManager()).findFirstVisibleItemPosition() == 0
&& recycler.getChildAt(0) != null && recycler.getChildAt(0).getY() < 0);
//...
Ça fera l'affaire.
UPDATE: Bien entendu, la vue recycleur n'a pas besoin d'être transparente en permanence. Vous pouvez mettre à jour la transparence pour n'être active que lorsque l'adaptateur est vide.
À votre santé!
C'était une question très frustrante pour moi, mais après quelques heures d'essais et d'échecs, j'ai proposé cette solution.
Avec cela, je peux actualiser même avec une vue vide visible (et RecyclerView aussi bien sûr)
Dans mon fichier de mise en page, j'ai cette structure:
SwipeRefreshLayout
FrameLayout
RecyclerView
my_empty_layout // Doesnt have to be ScrollView it can be whatever ViewGroup you want, I used LinearLayout with a single TextView child
Dans du code:
...
adapter.notifyDataSetChanged()
if (adapter.getItemCount() == 0) {
recyclerView.setVisibility(View.GONE);
emptyView.setVisibility(View.VISIBLE);
}
else {
recyclerView.setVisibility(View.VISIBLE);
emptyView.setVisibility(View.GONE);
}