Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get an offset in RecyclerView ItemDecorator

I have written two ItemDecorator's for RecyclerView. Each adds some top offset in getItemOffsets(). Let's say:

  • First decorator adds 20dp top offset
  • Second decrator adds 30dp top offset

Now, when I add both of them to RecyclerView, each item is correctly offsetted by 50dp, that's good.

But here comes the question: How do I get this offset in onDraw/onDrawOver?

Usually decorators draw their stuff by traversing parent.getChildAt(i) stuff and getting child.getTop() for example to draw above child view of RecyclerView.

But in this case, doing so would mix up the drawing of other decorator, because it would also use child.getTop().

So at the moment it seems like both decorators need to know about each other and each other's height.

Am I missing something here? I hope I am.

EDIT: I reported an issue to Android issue tracker and it seems this will be worked on. Star it to keep track of progress: https://code.google.com/p/android/issues/detail?id=195746

like image 312
dimsuz Avatar asked Nov 23 '15 22:11

dimsuz


1 Answers

tl;dr No you are not missing anything. But you can get the values needed in getItemOffsets, albeit it seems a little bit dirty to me.

Basically there is only one option of getting the decorated height other than managing decorations yourself: LayoutManager.getDecoratedTop();

onDraw

In onDraw you get the whole canvas of the recyclerView, c.getClipBounds() does not hold any information. Albeit the javadoc of adding decorations says that decorations should just draw withing their bounds.

Also, getting parent.getLayoutManager().getDecoratedTop() will give you the same values in every decoration, since it's already too late here, those values are for layouting purposes.

We are too late, layouting is done and we missed it.

getItemOffsets

Please note that I tested the following with a LinearLayoutManager and it might as well not work with the others (Most definitely not with most custom ones). Since I am relying on measuring happening between those calls, which is not documented, the given solution might break with some future version.

I just felt I needed that disclaimer. I found a working solution (watch the mOffset):

@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
    mOffset = parent.getLayoutManager().getTopDecorationHeight(view);
    outRect.set(0, 10, 0, 0);
}

This works, because the recyclerview, when calculating the total inset of the child, is updating the views package local LayoutParams variable. While we cannot access the layout parameter variable itself, calling getTopDecorationHeight actually uses the (currently) dirty inset, giving us the proper value.
Hence, you can get the offset of the previous decorations before applying your own!

To apply it, just remove the other decorations offset when drawing your decoration:

c.drawRect(child.getLeft() + child.getTranslationX(),
        mOffset + layoutManager.getDecoratedTop(child) + child.getTranslationY(),
        child.getRight() + child.getTranslationX(),
        child.getBottom() + child.getTranslationY(), paint);

This will actually apply the other decorations offset, then draw your own from the correct top.

Some Problems

This is now a basic, working solution for the default usecase. BUT. If you have different offsets depending on the item VIEW_TYPE or the adapter position things get tricky.

You will either duplicate your logic and keep various offsets for various view types or you have to store / retrieve the offset for every view or viewtype.

To do so, you could either add some custom tag with view.setTag(key, object) or doing something similar with the RecyclerView.State object that's getting passed around.

like image 122
David Medenjak Avatar answered Nov 17 '22 15:11

David Medenjak