Android - SwipeRefreshLayout with empty textview
Asked Answered
A

15

37

I've implemented SwipeRefreshLayout into my app but it can only hold one direct child which should be the listview. I'm trying to figure out how to add an empty textview to the following working XML file:

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

Wrapping it in a Linear/Relative layout makes it buggy because the listview will always update when you want to slide back up the listview. One way I can think of is doing this programmatically but I guess that's not the best option.

You can learn how to implement it using this tutorial: Swipe to refresh GUIDE

So basically it all works fine but I would like to add an empty view that shows a message when the listview is empty.

Ashwin answered 9/4, 2014 at 13:59 Comment(1)
Try using a FrameLayout.Spina
T
59

I didn't liked the limitation to a single child. Furthermore the current implementation of the SwipeRefreshLayout has an hardcoded "magic" handling for ScrollView, ListView and GridView that trigger only if the view it's the direct child of your own view.

That said the good news it's that it is open source, so you can either copy the code and adapt to your needs or you can do what I did:

Use two DIFFERENT SwipeRefreshLayout, one for the Empty view and one for the 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>

Then tell your listview that the empty list view is the swipe refresh layout of the empty view.

Now the empty refresh layout will be automatically hidden by your list view when you have data and will be shown when the list is empty.

The swipe refresh layout of the list shouldn't receive touch events cause the list is hidden.

Good luck.

Tenner answered 9/4, 2014 at 22:11 Comment(11)
Here is some sample code: github.com/AndroidExamples/SwipeRefreshLayout-ListViewExampleSebi
Your solution worked great (contrary to all the other suggested ones(, but I had to exactly follow your layout (wrap the swipe layouts in the relative layout). Thanks!Mcneil
@Mcneil you're welcome. That reference implementation is not mine, I just found it and being lazy I linked that instead of providing my own. There's no need to use a relative layout, you can use a frame layout or other layout. The important thing is that the SwipeRefreshLayout act upon its direct child only, meaning that you need 2: one for the listview, one for the empty view. FrameLayout are more efficient in performances if you can use them.Tenner
This worked well, thank you! Also worth noting is that the content of the scrollview can be anything, not just a textview. Good if you want a nicer empty view with an icon or something else for instance.Amok
May I ask a dumb question here: Why the scrollview will make the emptyview's SwipeRefreshLayout work while without it isn't?Deena
@Deena what do you mean by "work"? Your question does not make sense. You probably meant *listview in place of scrollview. If that's the case I can reply: When you set an empty view to the listview the listview automatically hide itself / show (setVisibility VISIBLE / GONE) the emptyview (and vice-versa) when it has zero items. If the empty view is a swipe it become visible in the layout and thus react to touchTenner
@Deena I'm sorry, stumbling here again, I didn't get your question before. The reason is that SwipeRefreshLayout allow you to pull to refresh only if it contains something meant to scroll (like a ScrollView, a ListView or a RecyclerView).Tenner
There is no need of creating another <android.support.v4.widget.SwipeRefreshLayout ...>. see my clean solution.Blockish
I would not call overriding the listview to change the behavior of setVisibility clean. Sorry but the actual clean solution is use a NestedScrollview. It just didn't existed when i wrote this answer. And my answer is still cleaner then yours. :-)Tenner
oh.. i though you were that other answer. Saw yours. It's ok.. ListView is outdated anyway.Tenner
Exactly the sameTenner
H
31

There is no need for any workaround.

You can simply use this view hierarchy :

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

Then, in code, you just call:

_listView.setEmptyView(findViewById(android.R.id.empty));

That's it.


EDIT: If you wish to be able to swipe-to-refresh even when the empty view is shown, you will have to somehow avoid hiding the ListView, so you could use a customized ListView that has this function inside:

@Override
  public void setVisibility(final int visibility)
    {
    if(visibility!=View.GONE||getCount()!=0)
      super.setVisibility(visibility);
    }

Together with the solution I wrote, the swipe-to-refresh is shown no matter how many items you are showing.

Of course, if you really want to hide the ListView, you should change the code. Maybe add "setVisibilityForReal(...)" :)

Hydrozoan answered 2/5, 2014 at 9:15 Comment(16)
It's non-obvious you need to switch to a FrameLayout or RelativeLayout if you were using a LinearLayout prior to adding the SwipeRefreshLayout. A combination of this and the above comment about using a second SwipeRefreshLayout work (if you need the empty list to refresh as well).Tory
@senkir I don't understand. didn't what I wrote work for you?Hydrozoan
But I cannot swipe down this empty view to refresh. Can I get the solution for this.. Thanks in advance. @androiddeveloperIngrained
@CHAKRAVARTHI I think some of what you wrote got deleted. Do you mean that with an empty view, it cannot do swipe-to-refresh?Hydrozoan
I scrolled down (not reached the top) and scrolled up the page, it starts to refresh. Don't understand the behaviour.. @androiddeveloperIngrained
No ability to refresh the list again after implementation of this solutionGluten
@BhavikMehta Please explain. I use this solution in my own app(here: play.google.com/store/apps/details?id=com.lb.app_manager ), and I have no issues with the listView at allHydrozoan
@androiddeveloper : Sorry to say but after using your solution, when data is not there in the list, i am unable to swipe and refresh it again. my implementation is just like yoursGluten
@BhavikMehta If you wish, you can create a new question here, with what is relevant (XML and code, but not too much), and let me know about it. Maybe I could help.Hydrozoan
@androiddeveloper: Please help me here #30818597Gluten
If you want to be able to pull refresh the empty view you'll have to put the FrameLayout (that includes the ListView and the EmptyView) inside the SwipeRefreshLayout. Also, set android:clickable=”true” in the FrameLayoutAlimentation
Astonished to see soo many upvotes. Above solution is not working when listview is empty i.e Empty view is showing.Holmquist
@MuhammadBabar This is by design, as it doesn't make sense to scroll on a view that's not scrollable (textview in this case), so swipe-to-refresh won't work. Try using the solution "antoinem" suggested. Also, if you wish, I think you can use a header in the ListView, which will replace the empty view I've shown, and you will be responsible of showing it or not (based on whether the list is empty or not), by setting its visibility.Hydrozoan
@androiddeveloper With due respect it does make sense when data can't be loaded due to no internet or may be server was down. So empty view can be refreshed. Thanks a lot for mentioning @Alimentation solution. Previously i did tried the same but android:clickable=true was the key and did worked. Still this doesn't work with standard SwipeRefreshLayout! Have to use github.com/googlesamples/android-SwipeRefreshMultipleViewsHolmquist
@MuhammadBabar Well, this doesn't take a lot of effort. You can just avoid making the ListView invisible, by extending it. I've added an update, and I've also checked it out. It works fine, even if there are no items in the list.Hydrozoan
@androiddeveloper extending ListView and avoiding invisibility sounds logical. Will give it a try next time. Thanks.Holmquist
S
11

Actually, the only think you are missing is having that empty TextView be wrapped with a scrollable container - for example ScrollView. For details, have a look at SwipeRefreshLayout.canChildScrollUp() method and its usage.

Anyway, back to the point. Here is a successful implementation:

activity_some.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/swipe_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:measureAllChildren="true">

        <WebView
            android:id="@+id/webview"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

        <include layout="@layout/empty" />

    </FrameLayout>

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

Where your empty.xml is basically anything you wish wrapped with a ScrollView.

empty.xml

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/empty"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fillViewport="true">

    <TextView
        android:text="Nothing here..."
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</ScrollView>

Now in order to get rid of the famous SwipeRefreshLayout refresh-only-when-at-the-top issue, toggle the SwipeRefreshLayout when necessary (Fragment-specific):

private ViewTreeObserver.OnScrollChangedListener mOnScrollChangedListener;

@Override
public void onStart() {
    super.onStart();

    mOnScrollChangedListener = new ViewTreeObserver.OnScrollChangedListener() {
        @Override
        public void onScrollChanged() {
            int scrollY = mWebView.getScrollY();
            if (scrollY == 0)
                swipeLayout.setEnabled(true);
            else
                swipeLayout.setEnabled(false);

        }
    };
    swipeLayout.getViewTreeObserver().addOnScrollChangedListener(mOnScrollChangedListener);
}

@Override
public void onStop() {
    swipeLayout.getViewTreeObserver().removeOnScrollChangedListener(mOnScrollChangedListener);
    super.onStop();
}

That's it! Hope it helps! ;)

Btw, why would you use SwipeRefreshLayout with FrameLayout this way? Because this way you can do smooth transition animations, like crossfade effects, and any of your state views can be swipeable (in case you want a unified fetch/refresh/retry mechanism).

Sapphira answered 12/1, 2015 at 14:1 Comment(2)
Thanks, I've ended up using your solution. I'll add a little observation, though: the key is that the ScrollView has to be a child of SwipeRefreshLayout. In my case, I've initially tried only to wrap the empty view (wich was kind of brother for the SwipeRefreshLayout) in a ScrollView, which wasn't enough.Ethnography
You deserve more upvotes. Worth to mention, everyone, don't forget to use android:fillViewport="true" in the ScrollViewDeal
T
8

Here's what I did : I disabled the swipe to refresh, unless my listView is up.

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

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

Works like a charm.

Trautman answered 8/10, 2014 at 8:43 Comment(0)
I
6

I encountered the same problem. It is annoying that SwipeRefreshLayout can only have one AdpaterView child.

If an empty view is set for an AdpaterView, it will be set as hidden if no data is available. However, SwipeRefreshLayout needs an AdpaterView to work. So, I extend AdpaterView to make it still shown, even if it is empty.

@Override
public void setVisibility(int visibility) {
    if (visibility == View.GONE && getCount() == 0) {
        return;
    }
    super.setVisibility(visibility);
}

Maybe you need to set the background of adapter view as transparent as well. But in my case, it is not needed, as the empty view is a simple TextView.

Indestructible answered 12/4, 2014 at 2:17 Comment(1)
Just to clarify, I believe that SwipeRefreshLayout can contain ScrollView children as well as AdapterViewsDeragon
R
5

Based on some answers here and the source code of SwipeRefreshLayout, I have subclassed the view to specifically handle having a RecyclerView (or ListView) and also an "empty" view inside a container which is the child.

It expects a layout such as

<SwipeRefreshLayoutWithEmpty ...>
  <FrameLayout ...>
    <TextView android:text="List is Empty" ...>
    <RecyclerView ...>
  </FrameLayout>
</SwipeRefreshLayoutWithEmpty>

The code is:

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

Rudolph answered 11/5, 2016 at 23:19 Comment(0)
C
2

I have tried following thing. In both empty view, and case of a list, swipe will refresh the data, also fastscroll is working fine without any code change required in activity. I have put emptyview before the listview and marked its visiblity to gone. and listview is put after that inside SwipeToRefresh block. 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>
Courtnay answered 7/11, 2016 at 13:20 Comment(1)
Thanks. This works. You must use RelativeLayout or FrameLayout. If you use LinearLayout it won't work.Gab
B
2

Probably late, But if you're still facing this issue, Here is the clean solution! There is no need of creating another SwipeRefreshLayout.

wrap empty view into a ScrollView. Stick both AdapterView and the ScrollView into a ViewGroup and put it into the SwipeRefreshLayout

Sample:

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

EDIT

Here is much more simpler way.

check if your list is empthy. if yes then make it invisible. Sample:

if(lst.size() > 0) {
 mRecyclerView.setVisibility(View.VISIBLE);
//set adapter
}else
{
  mRecyclerView.setVisibility(View.INVISIBLE);
 findViewById(R.id.txtNodata).setVisibility(View.VISIBLE);
}

this will make mRecyclerView still there and all swipes will be working fine now even there is no data!

Blockish answered 22/6, 2017 at 13:8 Comment(0)
C
2

You may be just use NestedScrollView in SwipeRefreshLayout with single container. Below is the list of buggy use mRecyclerView.setNestedScrollingEnabled(false);

<?xml version="1.0" encoding="utf-8"?>
<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">

    <!-- some toolbar -->

    <android.support.v4.widget.SwipeRefreshLayout
            android:id="@+id/conversationFragment_swipeRefresh"
            android:layout_alignParentBottom="true"
            android:layout_below="@+id/some_toolbar"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

        <android.support.v4.widget.NestedScrollView
                android:layout_width="match_parent"
                android:layout_height="match_parent">

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

                <TextView
                        android:id="@+id/conversationFragment_noResultText"
                        android:layout_width="match_parent"
                        android:layout_height="match_parent"
                        android:text="@string/FragmentConversations_empty"
                        android:layout_centerHorizontal="true" />

                <android.support.v7.widget.RecyclerView
                        android:id="@+id/conversationFragment_recyclerView"
                        android:layout_width="match_parent"
                        android:layout_height="match_parent" />

            </FrameLayout>

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

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

</RelativeLayout>
Caught answered 22/4, 2019 at 19:49 Comment(0)
C
1

Put SwipeRefreshLayout into a FrameLayout and other views behind it.

<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>
Candycandyce answered 27/6, 2014 at 2:15 Comment(1)
This suggestion leads to issue with no ability to refresh empty list.Ravishment
I
1

Why not wrap everything inside 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>
Indra answered 14/2, 2015 at 17:54 Comment(2)
Yeah I thought that would work as well, but if you do that something goes wrong. When swiping down and then swipe back up it starts to refresh. So I'd suggest to put ListView as the direct child of SwipeRefreshLayout.Policlinic
This configuration failed for me as well. Scrolling and refreshing didn't work as expected and sometimes I was unable to scroll the ListView back to the top because the SwipeRefreshLayout wanted to take control. And that happened even after adding code to disable the SwipeRefreshLayout when the ListView was not scrolled to the top.Chaparro
A
0

Another option that works nicely in case that your empty view doesn't need any interaction. For example, it is a simple textview saying "No data in here."

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

This puts the empty view behind the SwipeToRefreshLayout which is transparent and which contains also a transparent RecyclerView.

Then, in the code, in the place where you add the items to the recycler view adapter, you check if the adapter is empty, and if so you set the visibility of the empty view to "visible". And vice versa.

The trick is that the view is behind the transparent recycler view, which means that the recycler view and his parent, the SwipeToRefreshLayout, will handle the scroll when there are no items in the recycler. The empty view behind won't even be touched so the touch event will be consumed by the SwipeTorefreshLayout.

The custom RecyclerSwipeTorefreshLayout should handle the canChildScrollUp method in the following way

@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);
//...

This will do the trick.

UPDATE: Of course, the recycler view doesn't have to be transparent all the time. You can update the transparency to be active only when the adapter is empty.

Cheers!

Aggregation answered 19/10, 2015 at 15:12 Comment(0)
S
0

This was a very frustrating issue for me but after a few hours with try and fail I came up with this solution.

With this I can refresh even with empty view visible (and RecyclerView too of course)

In my layout file I have this 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

In code:

...
adapter.notifyDataSetChanged()

if (adapter.getItemCount() == 0) {
    recyclerView.setVisibility(View.GONE);
    emptyView.setVisibility(View.VISIBLE);
}
else {
    recyclerView.setVisibility(View.VISIBLE);
    emptyView.setVisibility(View.GONE);
}
Solicitude answered 19/7, 2017 at 12:59 Comment(0)
S
0

This worked for me

<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>
Sayres answered 19/1, 2018 at 18:47 Comment(0)
W
-1

As i mentioned here, i have done this for RecyclerView:

I have used clickable="true" like below with empty RecyclerView:

mRecyclerView.setVisibility(View.VISIBLE);
mRecyclerView.setClickable(true);
myText.setVisibility(View.VISIBLE);

xml layout:

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

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

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


    <TextView
        android:id="@+id/myText"
        android:visibility="gone"
        android:text="@string/noListItem"
        android:textSize="18sp"
        android:layout_centerInParent="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</RelativeLayout>

RecyclerView has height match_parent and SwipeRefresh has wrap_content. When there is item in list, don't forget to make text gone.

Wellturned answered 12/8, 2015 at 6:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.