简体   繁体   中英

Android support v4 SwipeRefreshLayout empty view issue

SwipeRefresh is not working after setting an empty view for listview which is the only child of a SwipeRefresh layout. How to solve this issue?

Here is the solution: 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.

I had this issue too, and solved it without any additional code in the activity by using the layout below.

If you are using a ListActivity or ListFragment it handles showing/hiding the empty view for you, and refreshing works with an empty list as well with this layout structure.

No hacks needed, everything is in the SwipeRefreshLayout , as it should be.

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/Refresher"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <ListView
            android:id="@android:id/list"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:divider="@null" />
        <ScrollView
            android:id="@android:id/empty"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <TextView
                android:text="Geen formulieren gevonden"
                style="@style/text.EmptyView" />
        </ScrollView>
    </LinearLayout>
</android.support.v4.widget.SwipeRefreshLayout>

Hope this helps you. If so, don't forget to accept the answer.

Note: this is a duplicate of my answer to this question , but that question is pretty much a duplicate of this question... :)

UPDATE
If the SwipeRefreshLayout is activated too early, you can implement ListView.OnScroll with:

_refresher.Enabled = e.FirstVisibleItem == 0;

This disables the refresher until you scrolled to the top. This is only needed when using a ListView, the new RecyclerView works as expected.

The problem for me was that when the empty view was visible then the refreshing circle wasn't shown although the refreshing method worked. For me this code worked, I hope it helps.

  • the xml layout:

     <FrameLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:clickable="true"> <ListView android:id="@+id/listView" android:layout_width="match_parent" android:layout_height="wrap_content" android:fadingEdge="none" android:footerDividersEnabled="false" android:headerDividersEnabled="false"/> <ScrollView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center" android:gravity="center"> <TextView android:id="@+id/empty_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center" android:gravity="center" android:visibility="gone"/> </ScrollView> </FrameLayout>

  • the custom SwipeRefreshLayout:

     package misc; import android.content.Context; import android.support.v4.widget.SwipeRefreshLayout; import android.widget.ListView; public class CustomSwipeRefreshLayout extends SwipeRefreshLayout { private ListView mListView; public CustomSwipeRefreshLayout(Context context) { super(context); } public void setListView(ListView listView) { mListView = listView; } @Override public boolean canChildScrollUp() { if (mListView == null) { return true; } return mListView.canScrollVertically(-1); } }
  • and in my Fragment where I use the layout I only set the swipe to refresh layout in this way:

     mSwipeRefreshLayout.setListView(mListView);
  • the ListView's empty view is set in this way:

     TextView emptyView = (TextView) view.findViewById(R.id.empty_view); emptyView.setText("No data"); mListView.setEmptyView(emptyView);

I hope it helps.

here's how i did this (probably not very elegant solution)

private void createEmptyView() {
    ViewGroup parent = (ViewGroup) listView.getParent();
    emptyView = (TextView) getLayoutInflater(null)
            .inflate(R.layout.view_empty, parent, false);
    parent.addView(emptyView);
    listView.setEmptyView(emptyView);
}

public class FrameSwipeRefreshLayout extends SwipeRefreshLayout {

    private ViewGroup listView;
    public void setListView(ViewGroup list) {
        listView = list;
    }

    @Override
    public boolean canChildScrollUp() {
        if (listView != null) {
            View child = listView.getChildAt(0);
            return child != null && child.getTop() != 0;
        }
        return super.canChildScrollUp();
    }
}

empty layout

<TextView xmlns:android="http://schemas.android.com/apk/res/android"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:gravity="center_horizontal"
      android:clickable="true" />

list layout

    <FrameSwipeRefreshLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            >
        <ListView
            android:id="@android:id/list"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            />
        </FrameLayout>
    </FrameSwipeRefreshLayout>

You can check out this issue. I posted a work around solution.

Android - SwipeRefreshLayout with empty textview

I fixed this issue using two SwipeToRefreshLayouts.

  • One for wrapping the ListView
  • The other as an emptyView

I posted my code on GitHub .

As @Dinesh written above, 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 .

I have another idea which works well. Subclass ListView and override setAdapter() and setEmptyView() . setEmptyView should just store the view to use for the empty view, and setAdapter should register a DataSetObserver that will hide/show the empty view without altering the list view's visibility. You would probably want to use a FrameLayout that holds your SwipeToRefreshLayout and an empty view (as siblings). You can then position the empty view at the top/middle/bottom etc pretty easily using gravity and layout gravity. You could also use a RelativeLayout but I haven't tried. You will want to somehow unregister the dataSetObserver, I believe you may want to do that in onDetachFromWindow as I did below.

public class SwipeToRefreshCompatibleList extends ListView {

    private boolean mIsEmpty = false;
    private View mEmptyView;
    private Adapter mAdapter;

    private DataSetObserver mLocalObserver = new  DataSetObserver() {
        @Override
        public void onChanged() {
            boolean isEmpty = mAdapter == null || mAdapter.getCount() == 0;
            if (mEmptyView != null) {
                if (isEmpty != mIsEmpty) {
                    mEmptyView.setVisibility(isEmpty ? View.VISIBLE : View.GONE);
                }
            }
            mIsEmpty = isEmpty;
        }
    };

    public SwipeToRefreshCompatibleList(Context context) {
        super(context);
    }

    public SwipeToRefreshCompatibleList(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public SwipeToRefreshCompatibleList(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public SwipeToRefreshCompatibleList(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    public void setAdapter(ListAdapter adapter) {
        mAdapter = adapter;
        adapter.registerDataSetObserver(mLocalObserver);
        super.setAdapter(adapter);
    }


    @Override
    public void setEmptyView(View view) {
        mEmptyView = view;
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (mAdapter != null) {
            mAdapter.unregisterDataSetObserver(mLocalObserver);
        }
    }

}

Example layout file:

    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >
        <android.support.v4.widget.SwipeRefreshLayout
            android:id="@+id/swipe_container"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            >

            <com.company.widget.SwipeToRefreshCompatibleList
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:id="@+id/BasicList"
                android:divider="@null"
                />


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

        <TextView
            android:padding="20dp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center_horizontal"
            android:layout_gravity="center"
            android:textSize="18sp"
            android:id="@android:id/empty"
            />

    </FrameLayout>

While this takes a bit more code than simply subclassing ListView and altering setVisibility such that it won't set the list to GONE/HIDDEN, I feel like this is less of a hack and still allows the user to set the list to GONE/Hidden if they needed.

I have tried UI hack but it didn't work, the only solution worked is set adapter. pass empty value make sure the value will not be null.

NotificationListAdapter notificationListAdapter;
notificationListAdapter = new NotificationListAdapter(mContext,notificationResponse);
reCycleViewNotificationList.setAdapter(notificationListAdapter); // weather ListView or RecyclerView

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM