Android - SwipeRefreshLayout with empty textview
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.
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).
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(...)" :)