RecyclerView添加空视图

6

我正在尝试在我的RecyclerView Adapter上实现一个EmptyView,但是我没有得到任何结果。

我已经按照这个tutorial和这个tip的步骤进行了操作,但是都没有起作用。

我已经实现了:

if (viewType == EMPTY_VIEW) {
    v = LayoutInflater.from(parent.getContext()).inflate(R.layout.empty_view, parent, false);
    EmptyViewHolder evh = new EmptyViewHolder(v);
    return evh;
}

v = LayoutInflater.from(parent.getContext()).inflate(R.layout.data_row, parent, false);
ViewHolder vh = new ViewHolder(v);
return vh;

但是它不让我编译,因为它们是不同的ViewHolder,因为我创建了两个ViewHolder类,但它们都extends Recycler.ViewHolder,所以我不明白...

我试图这样做是因为我有一个SearchView,当列表为空时,我希望它显示一个EmptyView,我已经通过编程实现了它,但我更喜欢添加一个像layout这样的东西,因为我不太知道如何在程序中放置TextViewsButtons

另外,如果我加上

return dataList.size() > 0 ? dataList.size() : 1;

由于索引为0,它给我报错。

我已经调试了viewType并且总是为1,因此它不会进入if条件...

Android中深入研究后,我发现了这个:

/**
 * Return the view type of the item at <code>position</code> for the purposes
 * of view recycling.
 *
 * <p>The default implementation of this method returns 0, making the assumption of
 * a single view type for the adapter. Unlike ListView adapters, types need not
 * be contiguous. Consider using id resources to uniquely identify item view types.
 *
 * @param position position to query
 * @return integer value identifying the type of the view needed to represent the item at
 *                 <code>position</code>. Type codes need not be contiguous.
 */
 public int getItemViewType(int position) {
    return 0;
 }

但问题是没有改变值。

编辑

我几乎完成了,我做了这个:

 @Override
public int getItemViewType(int position) {

    return list.size() > 0 ? list.size() : 1;
}

但有时当 size() 为 0 时它会返回 0……我不明白,我正在使用 SearchView,有时当我输入一个与列表中任何项都不匹配的字母时,它不显示,有时它会显示……

另外还有一件事发生了,就是当 layout 弹出时,它显示在屏幕的左边,但我把它设置为居中,我想这可能是 RecyclerView 的问题,因为 layout 放在其中。

RecyclerView 的布局:

<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:id="@+id/rtpew"
    android:layout_centerInParent="true"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    >
         <LinearLayout android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:id="@+id/linearpew">
            <android.support.v7.widget.RecyclerView
             android:id="@+id/rv"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
            />
         </LinearLayout>
 </RelativeLayout>

这是我的emptylayout

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true">
<ImageView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/ImageViewSearchFail"
    android:src="@drawable/sadface"
    android:layout_alignParentTop="true"
    android:layout_centerHorizontal="true" />
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:gravity="center_vertical"
    android:layout_gravity="center"
    android:textSize="@dimen/15dp"
    android:layout_marginTop="4dp"
    android:text="foo"
    android:layout_below="@+id/ImageViewSearchFail"
    android:layout_centerHorizontal="true" />
<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/ButtonAddEntity"
    android:text="foo"
    android:background="?android:selectableItemBackground"
    android:layout_centerVertical="true"
    android:layout_centerHorizontal="true" />
</RelativeLayout>

我想到的另一种方法是按照以下方式进行编程实现:
   @Override
public boolean onQueryTextChange(String query) {
    final ArrayList<List> filteredModelList = filter(mModel, query);
        mAdapter.animateTo(filteredModelList);
        rv.scrollToPosition(0);
    if(query.isEmpty()){
            //Here
    }
  return true;
}

并且:

private ArrayList<List> filter(ArrayList<List> models, String query) {
    query = query.toLowerCase();
    final ArrayList<List> filteredModelList = new ArrayList<List>();
    for (List model : models) {
        final String text = model.getRedName().toLowerCase();
        if (text.contains(query)) {
            filteredModelList.add(model);
        }
    }
    if (filteredModelList.size()<0) {
           //HERE
    }
    else{
           //Delete the views added
    }
    return filteredModelList;
}

问题

-我只是使用@Jimeux answer添加了视图,但我想在Adapter中实现此功能,我做到了,但即使list为空,有时仍然不会显示view

-在放置emptyview.xml时,它会放在RecyclerView内部,因此自从我把所有这些xml放在中心位置后,它就会显示在右侧。我尝试通过编程方式添加xml,但就像混乱一样....

5个回答

3

请按以下步骤逐一操作:

1). 由于您的RecyclerView项有两种视图类型,因此您的适配器声明应该像以下这样是通用的:

public class YourAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> 

你的ListView项和空视图的ViewHolders应该像这样扩展RecyclerView.ViewHolder

 static class ListItemViewHolder extends RecyclerView.ViewHolder {

        public ListItemViewHolder(View itemView) {
            super(itemView);
            // initialize your views here for list items
        }
    }

static class EmptyViewViewHolder extends RecyclerView.ViewHolder {

        public EmptyViewViewHolder(View itemView) {
            super(itemView);
            // initialize your views here for empty list
        }
    }

2). 你需要 Override getItemCount()getItemViewType() 方法。

@Override
    public int getItemCount() {
        return yourList.size() > 0 ? yourList.size() : 1;// if size of your list is greater than 0, you will return your size of list otherwise 1 for the empty view.
    }

    @Override
    public int getItemViewType(int position) {
        if (yourList.size() == 0) {
            return VIEW_TYPE_EMPTY;
        }
        return position;
    }

3). 现在你的 onCreateViewHolder() 将如下所示

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    if (viewType == VIEW_TYPE_EMPTY) {
        return new EmptyViewViewHolder(mLayoutInflater
                .inflate(R.layout.empty_view_layout, parent, false));
    } else {
        return new ListItemViewHolder(mLayoutInflater
                .inflate(R.layout.row_list_item, parent, false));
    }
}

4). 在你的onBindViewHolder()中也必须应用相同的检查。

@Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (getItemViewType(position) == VIEW_TYPE_EMPTY) {
            EmptyViewViewHolder emptyViewViewHolder = (EmptyViewViewHolder) holder;
            // set values for your empty views
        } else {
            ListItemViewHolder listItemViewHolder = (ListItemViewHolder) holder;
            // set values for your list items
        }
    }

5). 最后,覆盖(Override)你的 SearcView.setOnQueryTextListener()

searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
            @Override
            public boolean onQueryTextSubmit(String query) {
                return false;
            }

            @Override
            public boolean onQueryTextChange(String newText) {
                currentSearchKeyword = newText.trim();
                if(currentSearchKeyword.iseEmpty()){
                    yourList.clear();
                    yourAdapter.notifyDataSetChanged();
                }else{
                   // there will two cases again 1). If your currentSearchKeyword matchces with list results, add that items to your list and notify your adapter. 2) If the currentSearchKeyword doesn't matched with list results, clear your list and notify your adapter;
                }
                return false;
            }
        });

希望这可以帮到你,如果有任何问题,请告诉我。

我没有测试过,但是你知道为什么我的旧代码会在Recycler View内部填充View吗? - Skizo-ozᴉʞS ツ
我不明白你在RecyclerView中膨胀视图这一行的意思是什么?你是指空视图吗?为什么它没有显示出来? - Mukesh Rana
不不不,我的旧代码中有所有你提出的问题中的某些相似之处,但问题在于当我在RecyclerView中键入kajd(没有匹配项)时(我知道它在里面,因为它执行了滚动动画并且新布局在其中),我将使用你的答案进行测试,看看它是否仍然发生 :) - Skizo-ozᴉʞS ツ
请您仔细阅读我提到的每一个步骤,您的问题并不大,您需要注意的是如何处理搜索结果的情况。 - Mukesh Rana
我知道是什么,问题在于布局总是显示...:S我的意思是emptylayout - Skizo-ozᴉʞS ツ
显示剩余7条评论

3

由于需要处理两种不同类型的视图,使用中间业务对象列表将更容易地将它们与视图绑定。想法是在列表中为表示空状态的项目定义一种占位符。在这种情况下,定义一个中间层非常有用,可以让您考虑将来应用于列表的任何更改(例如添加元素类型)。此外,以这种方式,您可以更清楚地将业务模型与UI表示分开(例如,您可以实现基于模型对象内部状态返回UI设置的方法)。

您可以按照以下步骤进行:

  1. Define a dedicated abstract type for List items (e.g. ListItem) to wrap your business objects. Its implementation could be something like this:

    public abstract class ListItem {
    
        public static final int TYPE_EMPTY = 0;
        public static final int TYPE_MY_OBJ = 1;
    
        abstract public int getType();
    
    }  
    
  2. Define a class for each of your List element type:

    public class EmptyItem extends ListItem {
    
        @Override
        public int getType() {
            return TYPE_EMPTY;
        }
    
    }
    
    public class MyObjItem extends ListItem {
    
        private MyObj obj;
    
        public ContactItem(MyObj obj) {
            this.obj = obj;
        }
    
        public MyObj getMyObj() {
            return obj;
        }
    
        // here you can also add methods for simplify
        // objects rendering (e.g. get background color
        // based on your object internal status)
    
        @Override
        public int getType() {
            return TYPE_MY_OBJ;
        }
    
    }
    
  3. Create your list.

    List<ListItem> mItems = new ArrayList<>();
    if (dataList != null && dataList.size() > 0) {
        for (MyObj obj : dataList) {
            mItems.add(new MyObjItem(obj));
        }
    } else {
        mItems.add(new EmptyItem());
    }
    

    This is the most important part of code. You have many options for creating this list. You can do it inside your RecyclerView Adapter or outside, but it's extremely important to properly handle eventual modifications to it. This is essential for exploiting Adapter notify methods. For example, if you create list within the Adapter, it should probably provide also methods for adding or removing your model items. For example:

    public void addObj(MyObj obj) {
        if (mItems.size() == 1 && mItems.get(0).getType() == ListItem.EMPTY_TYPE) {
            mItems.clear();
        }
        mItems.add(new MyObjItem(obj));
        notifyDataSetChanged();
    } 
    
  4. Define an adapter for your RecyclerView, working on List defined at point 3. Here what is important is to override getItemViewType method as follows:

    @Override
    public int getItemViewType(int position) {
        return mItems.get(position).getType();
    }
    
此外,ViewHolder 的类型应为 RecyclerView.ViewHolder(除非您决定即使在这种情况下也创建一个中间类)。
  1. Then you need to have two layouts and ViewHolder for empty and business obj items. Adapter methods should take care of this accordingly:

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == ListItem.TYPE_EMPTY) {
            View itemView = mLayoutInflater.inflate(R.layout.empty_layout, parent, false);
            return new EmptyViewHolder(itemView);
        } else {
            View itemView = mLayoutInflater.inflate(R.layout.myobj_layout, parent, false);
            return new MyObjViewHolder(itemView);
        }
    }
    
    
    @Override
    public void onBindViewHolder(final RecyclerView.ViewHolder viewHolder, final int position) {
        int type = getItemViewType(position);
        if (type == ListItem.TYPE_EMPTY) {
            EmptyItem header = (EmptyItem) mItems.get(position);
            EmptyViewHolder holder = (EmptyViewHolder) viewHolder;
            // your logic here... probably nothing to do since it's empty
        } else {            
            MyObjItem event = (MyObjItem) mItems.get(position);
            MyObjViewHolder holder = (MyObjViewHolder) viewHolder;
            // your logic here
        }
    }
    
当然,正如我在开头所写的那样,你不需要严格定义ui表示中间类型(EmptyItem和MyObjItem)。你甚至可以只使用MyObj类型,并为其创建特定配置以表示空占位符。这种方法可能不是最好的选择,如果将来需要通过包括新的列表项类型来使逻辑更加复杂。

顺便说一下,谢谢您的回复。为什么我需要将EmptyObject作为List放入呢? - Skizo-ozᴉʞS ツ
@Skizo 嗯,想法是在您的RecyclerView中创建ListItemView之间的绑定。由于您希望在业务对象列表为空的情况下在您的RecyclerView中包含一个视图,因此您可以使用专用的ListItem来表示此情况。采用这种方法,将来您还可以为项目组添加标题或在列表底部添加加载器项。希望我的解释清楚明白。当然,如果有问题,请随时问我。 - andrea.petreri
为什么我尝试使用的方法不起作用...我遵循了一些教程,它们做的是相同的:S - Skizo-ozᴉʞS ツ
不,我现在的做法(我的问题中有一些代码)为什么不能正常工作…… - Skizo-ozᴉʞS ツ
没问题...我喜欢处理recyclerview / listview的东西。 - andrea.petreri
显示剩余12条评论

2
编译错误可能是因为您将主ViewHolder作为泛型参数扩展了RecyclerView.Adapter。您应该这样做:
YourAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>

然后适当地转换你的ViewHolders(你可以在这里重复使用getViewType(position))。确保在你的方法中切换ViewHolder类型。


问题是我有这个:public class Adaptergg extends RecyclerView.Adapter<Adaptergg.ViewHolder> { - Skizo-ozᴉʞS ツ
@Skizo 没错。除非 Adapter.ViewHolderEmptyViewHolder 的超类,否则你应该更改它。 - David Medenjak
但我不明白,这两个Viewholder类都是扩展RecyclerView.viewHolder的,如果我放一个就可以工作,但当我尝试放第二个时它会崩溃。 - Skizo-ozᴉʞS ツ
@Skizo extends RecyclerView.Adapter<SUPER_CLASS> <- 基类需要是你的视图持有者的超类,如果你将其中一个视图持有者作为泛型类型提供,另一个视图持有者将无法工作(除非它扩展了前一个)。 - David Medenjak
那我不能使用 <adaptergg.Viewholder> 吗?我改成了 Recycler.ViewHolder,在 onBindViewHolder 中它没有修改 holder,这意味着我不能执行 holder.tvHello.setText("SD");。 - Skizo-ozᴉʞS ツ
显示剩余2条评论

2
如果我是你,我不会在适配器中放置空视图。将其放置在包含RecyclerView的线性布局linearpew下,并根据数据更改来隐藏/显示它。您也可以使用此设置轻松添加加载视图、错误视图等。
这是我其中一个应用程序的简化代码,供您参考。如果您不熟悉它,@Bind来自Butter Knife。您可能还想查看Jake Wharton的u2020项目以获取更多RecyclerView的想法。
//fragment_layout.xml
<RelativeLayout 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"
        android:orientation="vertical"
        android:id="@+id/content">
    </FrameLayout>

    <include layout="@layout/status_views" />

</RelativeLayout>

//status_views.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical">

    <LinearLayout style="@style/ListStatusView"
        android:id="@+id/empty_view"/>

    <LinearLayout style="@style/ListStatusView"
        android:id="@+id/error_view"/>

    <LinearLayout style="@style/ListStatusView"
        android:id="@+id/loading_view"
        android:padding="30dp"/>

</LinearLayout>

//MyFragment.java
@Bind(R.id.content)      protected ViewGroup contentView;
@Bind(R.id.loading_view) protected ViewGroup loadingView;
@Bind(R.id.empty_view)   protected ViewGroup emptyView;
@Bind(R.id.error_view)   protected ViewGroup errorView;

@Bind({R.id.loading_view, R.id.error_view, R.id.empty_view, R.id.content})
protected List<ViewGroup> stateViews;

protected void activateView(View view) {
    for (ViewGroup vg : stateViews)
        vg.setVisibility(View.GONE);

    view.setVisibility(VISIBLE);
}

@Override
public void onActivityCreated(@Nullable Bundle state) {
    super.onActivityCreated(state);
    if (state == null) {
        activateView(loadingView);
        loadData();
    } else if (data.isEmpty())
        activateView(emptyView);
    else
        activateView(contentView);
}

编辑:这是一个简化版本,没有使用Butter Knife 库。
private ViewGroup contentView;
private ViewGroup emptyView;

@Override
protected void onCreate(@Nullable Bundle state) {
    super.onCreate(state);
    setContentView(R.layout.activity_main);
    contentView = (ViewGroup) findViewById(R.id.content_view);
    emptyView = (ViewGroup) findViewById(R.id.empty_view);
}

@Override
public boolean onQueryTextChange(String query) {
    final ArrayList<List> filteredModelList = filter(mModel, query);
    mAdapter.animateTo(filteredModelList);
    rv.scrollToPosition(0);

    if(query.isEmpty()){
        contentView.setVisibility(View.GONE);
        emptyView.setVisibility(View.VISIBLE);
    } else {
        contentView.setVisibility(View.VISIBLE);
        emptyView.setVisibility(View.GONE);
    }
    return true;
}

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/rtpew"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_centerInParent="true">

    <LinearLayout
        android:id="@+id/content_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <android.support.v7.widget.RecyclerView
            android:id="@+id/rv"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
    </LinearLayout>

    <RelativeLayout android:id="@+id/empty_view">
        <ImageView android:src="@drawable/sadface"/>
        <TextView android:text="foo"/>
        <Button android:id="@+id/ButtonAddEntity"/>
    </RelativeLayout>
</RelativeLayout>

你能否提供一个Activity的示例,因为我正在一个Activity中。那么我应该从我的代码中删除什么?我需要重新做适配器吗? - Skizo-ozᴉʞS ツ
你可以展示一下做这个功能的应用程序给我看吗? - Skizo-ozᴉʞS ツ
你只需要在Activity布局的XML文件中添加一个空视图布局,并在Activity代码中引用它。然后,您可以随时隐藏/显示它。您可以从适配器中删除空视图代码,只需将其保留即可。 - Jimeux
我有一个空的 XML 布局,但是读取我的问题时,它由于 RecyclerView 而向左移动... - Skizo-ozᴉʞS ツ
是的,这可能是一个解决方案,但由于我真的不想做那么多比较,适配器的想法会更完美... 我会等待有人回答这篇帖子并使用适配器来完成它,否则你的答案将是正确的。 - Skizo-ozᴉʞS ツ

1
这是您可以尝试的内容:

1. 替换

EmptyViewHolder evh = new EmptyViewHolder(v);

使用

RecyclerView.ViewHolder evh = new EmptyViewHolder(v);

这可能是编译失败的原因。
2. 替换。
@Override
public int getItemViewType(int position) {
    return list.size() > 0 ? list.size() : 1;
}

使用

@Override
public int getItemViewType(int position) {
    return list.get(position) != null ? 1 : 0;
}

为了使其工作,您必须在想要显示 "EmptyView" 时插入一个 "null" 对象:
int progressPosition = list.size();
list.add(null);
adapter.notifyItemInserted(progressPosition);

当您想隐藏EmptyView时,可以删除null对象:

int progressPosition = existingList.size() - 1;
existingList.remove(progressPosition);
adapter.notifyItemRemoved(progressPosition);

此外,您必须修改您的onCreateViewHolder()方法如下:
 @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == 1) {
            // inflate your default ViewHolder here ...
        } else {
            // inflate the EmptyViewHolder here
        }
    }

我相信我们之前已经讨论过了...详细讨论请参见this问题。

3. 不要使用SearchView,考虑使用带有FilterAutoCompleteTextView。这可能更容易与您的RecyclerViewAdapter集成。请参见this答案以获取示例。

我会根据我对您的问题的理解更新这个答案...请尝试并更新我。


大师,谢谢您的回复...我一回到笔记本电脑就会测试这个答案...顺便问一下,您不理解什么?我把空对象放在哪里?我的意思是progressposition。 - Skizo-ozᴉʞS ツ
我需要更详细地研究你的答案... :) 当你想显示EmptyView时,你需要在数据集中插入null对象,并在你想隐藏EmptyView时将其移除。 - Yash Sampat
在哪个部分呢?在onviewholdercreated吗? - Skizo-ozᴉʞS ツ
这取决于您在Activity / Fragment中正在做什么...您想在哪里显示EmptyView... - Yash Sampat
哦,我的意思是,在进行未匹配查询的比较时,我应该添加这个对吗? - Skizo-ozᴉʞS ツ
显示剩余10条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接