Подтвердить что ты не робот

Android - SwipeRefreshLayout с пустым текстовым просмотром

Я внедрил SwipeRefreshLayout в свое приложение, но он может содержать только один прямой дочерний элемент, который должен быть списком. Я пытаюсь понять, как добавить пустой текст в следующий рабочий XML файл:

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

Обтекание его в линейном/относительном макете делает его ошибкой, потому что listview всегда будет обновляться, когда вы хотите сместить резервную копию списка. Один из способов, я могу думать, делает это программно, но я думаю, что это не самый лучший вариант.

Вы можете узнать, как его реализовать, используя этот учебник: Проведите по экрану GUIDE

Итак, в основном все работает отлично, но я хотел бы добавить пустой вид, который показывает сообщение, когда listview пуст.

4b9b3361

Ответ 1

Мне не понравилось ограничение для одного ребенка. Кроме того, текущая реализация SwipeRefreshLayout имеет жесткую "магическую" обработку для ScrollView, ListView и GridView, которые запускаются только в том случае, если представление является прямым дочерним элементом вашего собственного представления.

Это хорошая новость, что он является открытым исходным кодом, поэтому вы можете либо скопировать код и адаптироваться к вашим потребностям, либо сделать то, что я сделал:

Используйте два РАЗЛИЧНЫХ SwipeRefreshLayout, один для Пустого представления и один для 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>

Затем скажите в своем представлении, что пустым списком является маска обновления для пустого представления.

Теперь пустой макет обновления будет автоматически скрыт в вашем представлении списка, когда у вас есть данные, и будет отображаться, когда список пуст.

Маска обновления профайла списка не должна получать события касания, потому что список скрыт.

Удачи.

Ответ 2

Нет необходимости в каком-либо обходном пути.

Вы можете просто использовать эту иерархию представлений:

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

Затем в коде вы просто вызываете:

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

Что это.


РЕДАКТИРОВАТЬ. Если вы хотите иметь возможность прокручивать-обновлять, даже когда отображается пустой вид, вам нужно как-то избежать скрытия ListView, чтобы вы могли использовать индивидуальный ListView, который имеет эту функцию внутри:

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

Вместе с решением, которое я написал, отображение прокрутки для обновления отображается независимо от количества отображаемых вами элементов.

Конечно, если вы действительно хотите скрыть ListView, вы должны изменить код. Возможно добавить "setVisibilityForReal (...)":)

Ответ 3

На самом деле, единственное, что вам не хватает, это то, что пустой TextView должен быть обернут прокручиваемым контейнером - например ScrollView. Для получения дополнительной информации см. Способ SwipeRefreshLayout.canChildScrollUp() и его использование.

В любом случае, вернемся к точке. Вот успешная реализация:

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>

Где ваш empty.xml - это в основном все, что вы хотите, обернутым 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>

Теперь, чтобы избавиться от известной проблемы с обновлением SwipeRefreshLayout, только когда-то-сверху, переключите SwipeRefreshLayout при необходимости (с помощью фрагмента):

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

Что это! Надеюсь, поможет!;)

Btw, почему вы использовали SwipeRefreshLayout с FrameLayout таким образом? Таким образом, вы можете делать плавные анимации перехода, такие как эффекты кроссфейда, и любые из ваших представлений состояния могут быть прокручиваемыми (в случае, если вам нужен единый механизм fetch/refresh/retry).

Ответ 4

Вот что я сделал: Я отключил салфетки, чтобы обновить, если мой списокView не работает.

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

Мой 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>

Работает как шарм.

Ответ 5

Я столкнулся с той же проблемой. Досадно, что у SwipeRefreshLayout может быть только один ребенок AdpaterView.

Если для AdpaterView установлен пустой вид, он будет установлен как скрытый, если данные недоступны. Однако SwipeRefreshLayout нуждается в работе AdpaterView. Таким образом, я расширяю AdpaterView, чтобы он отображался, даже если он пуст.

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

Возможно, вам также необходимо настроить прозрачность фона в виде адаптера. Но в моем случае это не нужно, поскольку пустой вид представляет собой простой TextView.

Ответ 6

Основываясь на некоторых ответах здесь и на исходном коде SwipeRefreshLayout, я подклассифицировал представление, чтобы конкретно обрабатывать RecyclerView (или ListView), а также "пустой" вид внутри контейнера, который является дочерним.

Он ожидает макет, например

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

Код:

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

Полный текст: https://gist.github.com/grennis/16cb2b0c7f798418284dd2d754499b43

Ответ 7

Я пробовал следующее. Как в пустом виде, так и в случае списка, салфетки будут обновлять данные, а также fastscroll работает нормально, без необходимости изменения кода в активности. Я поставил пустое пространство перед списком и пометил его видимость. и listview помещается после этого в блок SwipeToRefresh. Пример кода -

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

Ответ 8

Поместите SwipeRefreshLayout в FrameLayout и другие представления за ним.

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

Ответ 9

Почему бы не обернуть все внутри 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>

Ответ 10

Возможно, поздно, но если вы все еще сталкиваетесь с этой проблемой, вот чистое решение! Нет необходимости создавать еще один SwipeRefreshLayout.

wrap empty view в ScrollView. Вставьте как AdapterView, так и ScrollView в ViewGroup и поместите его в SwipeRefreshLayout

Пример:

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

ИЗМЕНИТЬ

Здесь гораздо более простой способ.

проверьте, является ли ваш список эмпатичным. если да, то сделайте его невидимым. Пример:

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

это сделает mRecyclerView еще там, и все удары будут работать нормально, даже если нет данных!

Ответ 11

Другой вариант, который хорошо работает в случае, если ваш пустой вид не требует никакого взаимодействия. Например, это простой текстовый комментарий: "Нет данных здесь".

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

Это создает пустой вид SwipeToRefreshLayout, который является прозрачным и содержит также прозрачный RecyclerView.

Затем, в коде, в том месте, где вы добавляете элементы в адаптер просмотра recycler, вы проверяете, пуст ли адаптер, и если это так, вы устанавливаете видимость пустого представления на "видимый". И наоборот.

Фокус в том, что представление находится за прозрачным видом ресайклера, что означает, что просмотр ресайклера и его родительский элемент SwipeToRefreshLayout будут обрабатывать свиток, когда в ресайклере нет элементов. Пустой вид сзади даже не будет затронут, поэтому сенсорное событие будет потребляться SwipeTorefreshLayout.

Пользовательский RecyclerSwipeTorefreshLayout должен обрабатывать метод canChildScrollUp следующим образом

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

Это сделает трюк.

UPDATE: Конечно, просмотр recycler не должен быть прозрачным все время. Вы можете обновить прозрачность, чтобы быть активным, только когда адаптер пуст.

Ура!

Ответ 12

Это была очень неприятная проблема для меня, но через несколько часов с попыткой и неудачей я придумал это решение.

С этим я могу обновить даже с видимым пустым видом (и, конечно, RecyclerView)

В моем файле макета у меня есть эта структура:

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

В коде:

...
adapter.notifyDataSetChanged()

if (adapter.getItemCount() == 0) {
    recyclerView.setVisibility(View.GONE);
    emptyView.setVisibility(View.VISIBLE);
}
else {
    recyclerView.setVisibility(View.VISIBLE);
    emptyView.setVisibility(View.GONE);
}

Ответ 13

Это сработало для меня

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

Ответ 14

Вы можете просто использовать NestedScrollView в SwipeRefreshLayout с одним контейнером. Ниже приведен список использования с ошибками 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>

Ответ 15

Как я упомянул здесь, я сделал это для RecyclerView:

Я использовал clickable="true", как показано ниже, с пустым 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 имеет высоту match_parent и SwipeRefresh имеет wrap_content. Когда в списке есть элемент, не забудьте сделать текст gone.