Android - 使用空textview的SwipeRefreshLayout

时间:2014-04-09 13:59:28

标签: android android-listview

我已将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>

将其包装在线性/相对布局中会使其失灵,因为当您想要向后滑动列表视图时,列表视图将始终更新。我能想到的一种方法是以编程方式进行此操作,但我猜这不是最佳选择。

您可以使用本教程了解如何实现它:Swipe to refresh GUIDE

所以基本上它一切正常,但我想添加一个空视图,当listview为空时显示一条消息。

15 个答案:

答案 0 :(得分:49)

我不喜欢对一个孩子的限制。 此外,SwipeRefreshLayout的当前实现具有ScrollView,ListView和GridView的硬编码“魔术”处理,仅当视图是您自己视图的直接子视图时才触发。

这说好消息是它是开源的,所以你可以复制代码并适应你的需求,或者你可以做我做的事情:

使用两个不同的SwipeRefreshLayout,一个用于Empty视图,另一个用于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>

然后告诉列表视图空列表视图是空视图的滑动刷新布局。

现在,当您有数据时,列表视图将自动隐藏空刷新布局,并在列表为空时显示。

列表的滑动刷新布局不应该接收触摸事件,因为列表是隐藏的。

祝你好运。

答案 1 :(得分:31)

无需任何解决方法。

您可以简单地使用此视图层次结构:

    <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(...)”:)

答案 2 :(得分:11)

实际上,您唯一想到的就是将空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?因为这样您可以执行平滑过渡动画,例如 crossfade 效果,并且任何视图都可以可滑动(如果您需要统一获取/刷新/重试机制)。

答案 3 :(得分:8)

这是我做的: 我禁用滑动刷新,除非我的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>

像魅力一样。

答案 4 :(得分:6)

我遇到了同样的问题。令人讨厌的是, SwipeRefreshLayout 只能有一个 AdpaterView 子项。

如果为 AdpaterView 设置了空视图,则在没有可用数据的情况下,它将被设置为隐藏。但是, SwipeRefreshLayout 需要 AdpaterView 才能运行。因此,我扩展了 AdpaterView 以使其仍然显示,即使它是空的。

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

也许您需要将适配器视图的背景设置为透明。但在我的情况下,它不是必需的,因为空视图是一个简单的 TextView

答案 5 :(得分:4)

基于这里的一些答案和SwipeRefreshLayout的源代码,我已经将视图子类化为专门处理具有RecyclerView(或ListView)以及&#34;空&#34;查看作为孩子的容器内部。

它需要一个布局,如

<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

答案 6 :(得分:2)

我试过以下的事情。在空视图和列表的情况下,滑动将刷新数据,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>

答案 7 :(得分:1)

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

答案 8 :(得分:1)

为什么不将所有内容都包装在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>

答案 9 :(得分:1)

可能迟到了,但如果您仍然面临这个问题,那么这是一个干净的解决方案!无需创建另一个SwipeRefreshLayout

empty view打包成ScrollView。将AdapterViewScrollView同时添加到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仍然存在,即使没有数据,所有滑动也会正常工作!

答案 10 :(得分:0)

在您的空视图不需要任何交互的情况下,另一个选项可以很好地工作。例如,这是一个简单的文本视图,说“这里没有数据。”

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

这样就可以了。

<强>更新 当然,回收者视图不一定要透明。只有在适配器为空时,才能将透明度更新为活动状态。

干杯!

答案 11 :(得分:0)

这对我来说是一个非常令人沮丧的问题,但经过几个小时的尝试和失败,我想出了这个解决方案。

有了这个,即使空视图可见,我也可以刷新(当然还有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);
}

答案 12 :(得分:0)

这对我有用

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

答案 13 :(得分:0)

您可能只在单个容器的SwipeRefreshLayout中使用NestedScrollView。 以下是越野车使用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>

答案 14 :(得分:-1)

正如我提到的here,我已经为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_parentSwipeRefresh的身高为wrap_content。当列表中有项目时,请不要忘记创建文本gone