44

I have code like this

public static class MyViewHolder extends RecyclerView.ViewHolder {
    @InjectView(R.id.text)
    TextView label;

    public MyViewHolder(View itemView) {
        super(itemView);
        ButterKnife.inject(this, itemView);
    }

    public void hide(boolean hide) {
        label.setVisibility(hide ? View.GONE : View.VISIBLE);
    }
}

which maps to a single row in a RecyclerView. R.id.text is in fact the root view of the layout that gets inflated and passed in to the constructor here.

I'm using the default implementation of LinearLayoutManager.

In bindViewHolder, I call hide(true) on an instance of MyViewHolder, but instead of collapsing the row as expected, the row becomes invisible, maintaining its height and position in the RecyclerView. Has anyone else run into this issue?

How do you hide items in a RecyclerView?

Tony J Huang
  • 774
  • 1
  • 5
  • 11
  • Have you tried `RecyclerView.getLayoutManager().removeViewAt()` http://developer.android.com/reference/android/support/v7/widget/RecyclerView.LayoutManager.html#removeViewAt(int) – hoomi Dec 19 '14 at 22:48
  • you should never call those methods directly in LM. They are for LM to manage children – yigit Dec 21 '14 at 03:02
  • Hello. Check out my answer [here](https://stackoverflow.com/a/54272566/5670752). – Taslim Oseni Jan 20 '19 at 00:35

6 Answers6

44

There is no built in way to hide a child in RV but of course if its height becomes 0, it won't be visible :). I assume your root layout does have some min height (or exact height) that makes it still take space even though it is GONE.

Also, if you want to remove a view, remove it from the adapter, don't hide it. Is there a reason why you want to hide instead of remove ?

yigit
  • 37,683
  • 13
  • 72
  • 58
  • 1
    No, I actually tried to set the height to 0 for some rows. The LayoutManager does not respect row height changes. You're right though, the solution is to 'remove' the offending objects from the adapter. See my answer. – Tony J Huang Dec 21 '14 at 03:07
  • 1
    LayoutManager **does** respect item heights. I wonder how you are changing them. I assume you are calling view.setLayoutParams which will trigger a re-layout in the LayoutManager and it will get the new size. Something else should be wrong there – yigit Dec 21 '14 at 03:09
  • Oh you're right, I just realized I was setting the height incorrectly. Ill mark your answer as correct. – Tony J Huang Dec 21 '14 at 03:40
  • 1
    Note that if you're using a decorator (e.g. to add a divider between your items), your "invisible" items will still be decorated. – Marc Plano-Lesay Oct 06 '16 at 20:15
  • @yigit I have one reason on my case I have the first two items as constant items. So they are always the 1st and 2nd item on the list. First on the list is a controller, and the second item is an "add card". "Add card" appears when I press a button in the controller. The rest of the item are the standard representation of the list of models. I am trying to emulate the post feature in Facebook where you always have an "post what you think" card on top of the list, so I thought I could make a composite view via RecyclerView. But maybe this is just wrong way to do it – Neon Warge Dec 31 '16 at 12:29
42

Put method setVisibility(boolean isVisible) in ViewHolder.

You can change itemView params(width and height) for LayoutManager:

public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
    ...

    public void setVisibility(boolean isVisible){
        RecyclerView.LayoutParams param = (RecyclerView.LayoutParams)itemView.getLayoutParams();
        if (isVisible){
            param.height = LinearLayout.LayoutParams.WRAP_CONTENT;
            param.width = LinearLayout.LayoutParams.MATCH_PARENT;
            itemView.setVisibility(View.VISIBLE);
        }else{
            itemView.setVisibility(View.GONE);
            param.height = 0;
            param.width = 0;
        }
        itemView.setLayoutParams(param);
    }

    public ViewHolder(View itemView) {
        super(itemView);
        ...
    }
}

and change visibility for ItemDecoration (Divider):

public class DividerItemDecoration extends RecyclerView.ItemDecoration {
    ...

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        ...

        for (int i = 0; i < parent.getChildCount(); i++) {

            if (parent.getChildAt(i).getVisibility() == View.GONE) 
                continue;

            /* draw dividers */    

        }
    }
}
camelCaseCoder
  • 1,447
  • 19
  • 32
Dmitry Velychko
  • 1,505
  • 16
  • 14
12

You CAN do it!

First, you need to detect which position of item that you want to hide. You can custom getItemViewType to do it.

Next, on onCreateViewHolder, depend on the view type. You can do something like this:

if(viewType == TYPE_HIDE) {
                v = LayoutInflater.from(parent.getContext()).inflate(R.layout.empty_item, parent, false);
                vHolder = new ViewHolder(context, v, viewType, this);
                break;
        }
        return vHolder; 

-> empty item is a layout that have nothing, (in other word, it is default layout whenever created). or code:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

</LinearLayout>

Hope it help!

Lạng Hoàng
  • 1,790
  • 3
  • 17
  • 32
  • 3
    I think it'd be a good idea to set the `layout_height` to `0dp` or `wrap_content`, if you set it `match_parent` then each empty cell will take up the size of the parent view... – Sakiboy Jun 07 '16 at 03:42
11

Okay, so the way I did it in the end was I had my whole dataset, say, myObjects and I had scenarios where I would only want to show subsets of that dataset.

Since setting visibility of rows in RecyclerView doesn't cause the heights to collapse, and setting the heights of the rows did not appear to do anything either, what I had to do was just keep a secondary dataset called myObjectsShown which was nothing more than a List<Integer> that would index into myObjects to determine which objects would be displayed.

I would then intermittently update myObjectsShown to contain the correct indices.

Therefore,

public int getItemCount() {
    return myObjectsShown.size();
}

and

public void onBindViewHolder(MyViewHolder holder, int position) {
    Object myObject = myObjects.get(myObjectsShown.get(position));
    // bind object to viewholder here...
} 
Tony J Huang
  • 774
  • 1
  • 5
  • 11
  • 2
    This is pretty smart and simple. This should be the best answer because you really don't have to meddle with `getItemViewType` or anything else. But it does the job of dynamically hiding the child you don't want to show and also takes the height change in to consideration pretty well. – SergeantPeauts May 12 '16 at 21:17
  • really smart answer – Mosa Oct 31 '18 at 13:27
3

For hiding view in RecyclerView I hide/show view in OnBindViewHolder:

if (item.isShown) {
    vh.FooterLayout.setVisibility(View.Visible);
} else {
    vh.FooterLayout.setVisibility(View.Gone);
}

And for example - from activity I simply redraw needed item: _postListAdapter.notifyItemChanged(position)// if you want show/hide footer - position is amountOfPosts.size() and also change bool variable - amountOfPosts[amountOfPosts.size()].isShown

Jared Burrows
  • 54,294
  • 25
  • 151
  • 185
Ihor Levkivskyi
  • 341
  • 5
  • 14
2

For the sake of completeness, you should note that setting view visibility to GONE would not hide the margins. You need to do something like this :

       if(itemView.getVisibility() != GONE) itemView.setVisibility(GONE);
       RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) itemView.getLayoutParams();
       layoutParams.setMargins(0, 0, 0, 0);
       itemView.setLayoutParams(layoutParams);
dev
  • 11,071
  • 22
  • 74
  • 122