Android - SwipeRefreshLayout with empty textview

2020-01-30 06:42发布

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.

15条回答
The star\"
2楼-- · 2020-01-30 07:24

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>
查看更多
我欲成王,谁敢阻挡
3楼-- · 2020-01-30 07:25

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);
}
查看更多
▲ chillily
4楼-- · 2020-01-30 07:27

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>
查看更多
爷的心禁止访问
5楼-- · 2020-01-30 07:29

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.

查看更多
倾城 Initia
6楼-- · 2020-01-30 07:29

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!

查看更多
放我归山
7楼-- · 2020-01-30 07: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(...)" :)

查看更多
登录 后发表回答