Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RecyclerView add EmptyView

I'm trying to implement an EmptyView on my RecyclerView Adapter but I'm not getting any result.

I've followed this tutorial and this tip, but noone worked for me.

I've implemented:

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;

But it doesn't let me compile because they are differents ViewHolder, because I've created two ViewHolder classes but they extends Recycler.ViewHolder so I don't get it...

I'm trying to do this because I've got a SearchView and I want when the list is empty it shows an EmptyView, I've got it doing it programmatically but I prefer to add like a layout because I don't know that much how to put TextViews and Buttons programmatically.

Also if I put

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

It gives to me error because index is 0.

I've debugged the viewType and always is 1, then it won't join the if condition...

Deep on Android I found this :

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

But the thing is that no changes the value.

EDIT

I almost done it, I did this :

 @Override
public int getItemViewType(int position) {

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

But sometimes it returns 0 when the size() is 0... I don't get it, I'm using this SearchView, and sometimes when I type a letter that doesn't matches with any item of the list it doesn't show and sometimes it does...

Also other thing that happens is that when the layout popups it shows on the left of the screen when I put that is on center, but I think it's problem with RecyclerView because the layout puts inside of it.

RecyclerView layout :

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

And this is my 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>

The other way that I thought is to implement it programmatically as follow :

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

And :

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

PROBLEMS

-I only add the view using the @Jimeux answer but I'd like to do this on the Adapter, I got it, but not always shows the view even if the list is empty.

-At the time to put the emptyview.xml it puts inside of the RecyclerView then since I've put all of this xml at the center it shows on the right. I've tried to add the xml programmatically but it's like a chaos....

like image 617
Skizo-ozᴉʞS Avatar asked Jan 20 '16 00:01

Skizo-ozᴉʞS


1 Answers

Since you need to handle two different kind of views, it would be easier to use an intermediate list of business object for more easily binding them with views. Idea is to have a kind of placeholder in your list for representing empty state. Defining an intermediate layer is extremely useful in this sense for allowing you to consider eventual changes to be applied to your list in future (e.g. adding you element types). Moreover in this way you can more clearly separate your business model from ui representation (for example you can implement methods returning ui settings based on internal status of model objects).

You can proceed as follows:

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

Moreover, type of ViewHolder should be RecyclerView.ViewHolder (unless you decide to create an intermediate class even in this case).

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

Of course, as I wrote at the beginning you don't need to strictly define intermediate types for ui representation (EmptyItem and MyObjItem). You can even just use MyObj type and create a specific configuration for it that represent an empty placeholder. This approach is probably not the best in case in future you need to make your logic more complex by including for example new list item types.

like image 179
thetonrifles Avatar answered Oct 30 '22 19:10

thetonrifles