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>
Обтекание его в линейной / относительной компоновке делает его глючным, потому что просмотр списка всегда обновляется, когда вы хотите сдвинуть назад просмотр списка. Один из способов, которым я могу придумать, - это делать это программно, но, думаю, это не лучший вариант.
Вы можете узнать, как реализовать это, используя этот учебник: Проведите пальцем, чтобы обновить РУКОВОДСТВО
Так что в принципе все работает нормально, но я хотел бы добавить пустое представление, которое показывает сообщение, когда представление списка пусто.
14 ответов
Мне не понравилось ограничение на одного ребенка. Кроме того, текущая реализация 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>
Затем скажите вашему представлению списка, что пустое представление списка - это макет обновления смахивания пустого представления.
Теперь пустой макет обновления будет автоматически скрываться вашим списком, когда у вас есть данные, и будет отображаться, когда список пуст.
Компоновка списка при обновлении смахивания не должна получать сенсорные события, поскольку список скрыт.
Удачи.
Нет необходимости в обходном пути.
Вы можете просто использовать эту иерархию представлений:
<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(...)":)
На самом деле, единственное, что вам не хватает - это пустое 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();
}
Это оно! Надеюсь, поможет!;)
Кстати, почему вы используете SwipeRefreshLayout
с FrameLayout
сюда? Потому что таким образом вы можете делать плавные переходные анимации, такие как эффекты перекрестного затухания, и любое из ваших представлений состояния может быть смахиваемым (в случае, если вам нужен унифицированный механизм извлечения / обновления / повторения).
Вот что я сделал: я отключил свайп, чтобы обновить, если мой listView не работает.
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>
Работает как шарм.
Я столкнулся с той же проблемой. Раздражает, что SwipeRefreshLayout может иметь только одного дочернего элемента AdpaterView.
Если для AdpaterView установлено пустое представление, оно будет скрыто, если данные недоступны. Однако для работы SwipeRefreshLayout требуется AdpaterView. Итак, я расширяю AdpaterView, чтобы он все еще отображался, даже если он пуст.
@Override
public void setVisibility(int visibility) {
if (visibility == View.GONE && getCount() == 0) {
return;
}
super.setVisibility(visibility);
}
Может быть, вам нужно также установить прозрачность фона вида адаптера. Но в моем случае это не нужно, поскольку пустое представление - это простое TextView.
Основываясь на некоторых ответах здесь и исходном коде 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
Вы можете просто использовать 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>
Возможно, поздно, но если вы все еще сталкиваетесь с этой проблемой, вот чистое решение! Нет необходимости создавать другой SwipeRefreshLayout
,
заворачивать 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
все еще там и все свайпы будут работать нормально, теперь даже нет данных!
Я попробовал следующую вещь. Как в пустом представлении, так и в случае списка, свайп обновит данные, а также fastscroll работает нормально без каких-либо изменений кода, требуемых в действии. Я поставил emptyview перед списком и отметил его видимость исчезнувшим. и 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>
Почему бы не обернуть все внутри 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>
Поместите 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>
Это была очень неприятная проблема для меня, но после нескольких часов попыток и неудач я нашел это решение.
Благодаря этому я могу обновиться даже с видимым пустым представлением (и, конечно, с 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);
}
Это сработало для меня
<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>
Еще один вариант, который хорошо работает, если ваше пустое представление не нуждается во взаимодействии. Например, это простое текстовое представление "Нет данных здесь".
<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.
Затем в коде, в месте, где вы добавляете элементы в адаптер представления переработчика, вы проверяете, является ли адаптер пустым, и если да, то вы устанавливаете видимость пустого представления как "видимая". И наоборот.
Хитрость заключается в том, что представление находится за прозрачным представлением рециркулятора, что означает, что представление рециркулятора и его родительский объект, 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);
//...
Это сделает свое дело.
ОБНОВЛЕНИЕ: Конечно, представление переработчика не должно быть прозрачным все время. Вы можете обновить прозрачность, чтобы она была активной только тогда, когда адаптер пуст.
Ура!
Как я уже упоминал, я сделал это для RecyclerView
:
я использовал clickable="true"
как ниже с пустым RecyclerView
:
mRecyclerView.setVisibility(View.VISIBLE);
mRecyclerView.setClickable(true);
myText.setVisibility(View.VISIBLE);
XML-макет:
<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
,