Hiding views in RecyclerView
Asked Answered
R

6

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?

Rely answered 19/12, 2014 at 22:29 Comment(3)
Have you tried RecyclerView.getLayoutManager().removeViewAt() developer.android.com/reference/android/support/v7/widget/…Mistrustful
you should never call those methods directly in LM. They are for LM to manage childrenBurgess
Hello. Check out my answer here.Oblation
B
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 ?

Burgess answered 21/12, 2014 at 3:6 Comment(5)
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.Rely
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 thereBurgess
Oh you're right, I just realized I was setting the height incorrectly. Ill mark your answer as correct.Rely
Note that if you're using a decorator (e.g. to add a divider between your items), your "invisible" items will still be decorated.Ca
@Burgess 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 itKellner
H
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 */    

        }
    }
}
Herakleion answered 18/2, 2016 at 11:30 Comment(3)
While this works, it causes lots of measurement on recycling and makes scrolling very janky.Jovia
@Jovia But if you have a lot hidden items in list, you should think about some other algorithm of displaying them. Change Layout Manager for example.Herakleion
Do we need to mess with LayoutParams, wouldn't just View.GONE do the trick?Faggot
B
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!

Bibliology answered 5/8, 2015 at 7:58 Comment(1)
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...Normanormal
R
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...
} 
Rely answered 21/12, 2014 at 3:8 Comment(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.Wifely
really smart answerQuadragesimal
G
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

Geophyte answered 9/11, 2016 at 18:37 Comment(0)
G
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);
Giliana answered 19/8, 2016 at 18:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.