Skip to content
This repository has been archived by the owner on Mar 24, 2023. It is now read-only.

Support for a Gridview layout #8

Open
jacobtabak opened this issue Nov 4, 2014 · 19 comments
Open

Support for a Gridview layout #8

jacobtabak opened this issue Nov 4, 2014 · 19 comments

Comments

@jacobtabak
Copy link
Contributor

Is there a good implementation of a grid layout manager for RecyclerView?

If so, is it worthwhile to implement sticky headers for it?

How do we deal with the case where the beginning of a row has a different header than the middle of a row or the end of a row?

@ghost
Copy link

ghost commented Nov 9, 2014

+1

@jacobtabak
Copy link
Contributor Author

Do you have any comments or thoughts on the issues I brought up in the OP?

@asgarddesigns
Copy link

I am looking at implementing something similar at the moment. Definitely worth looking at I think! In our case we want to be able to switch between list and grid view.

The only implementation I have come across is this one: https://github.com/antoniolg/RecyclerViewExtensions/blob/master/library/src/main/java/com/antonioleiva/recyclerviewextensions/GridLayoutManager.java

not sure if it is any good, the repo states it is still in development.

In the case of a mixed row, it should be treated as a break, so you will have partial rows - I can't think of a reason to do it any other way!

@jacobtabak
Copy link
Contributor Author

Too bad, it looks like that project hasn't been touched for months. I think you're definitely right about the mixed row behavior.

Recently I've been thinking that a more robust way doing sticky headers would be in the layout manager itself rather than a decorator. The biggest problem with the decorator approach is we don't get the benefits of item animators or view recycling for free.

@asgarddesigns
Copy link

Ahh, after bringing in those classes, I went to import and there's a gridlayoutmanager already in the support libs!

I have it working but it's not spitting the rows. Will have to look into it further.

@jacobtabak
Copy link
Contributor Author

Wow, nice. There's also a staggered grid layout manager. That's really fantastic.

@MFlisar
Copy link

MFlisar commented Apr 3, 2015

Splitting the rows will work if you add a header view and set it's span to the gridviews span size. For example, let your items (which include the header items) implement an interface like int getSpanSize() and return 1 for all normal items and spanSize for the header items. And afterwards use it like following:

GridLayoutManager layoutManager = new GridLayoutManager(activity, spanSize);
layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
        @Override
        public int getSpanSize(int position) {
            return adapterStatistics.getItem(position).getSpanSize();
        }
  });

Actually, this does only help if the headers are part of the adapter's data itself... No stickyness, but that's how I use headers in GridViews currently...

@asgarddesigns
Copy link

So I do have a basic implementation for grid headers, I don't really have the time at the moment to test this works for anything other than my own use-cases though.

import android.graphics.Canvas;
import android.graphics.Rect;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.SparseArray;
import android.view.View;

import com.timehop.stickyheadersrecyclerview.HeaderPositionCalculator;
import com.timehop.stickyheadersrecyclerview.StickyRecyclerHeadersAdapter;
import com.timehop.stickyheadersrecyclerview.caching.HeaderProvider;
import com.timehop.stickyheadersrecyclerview.caching.HeaderViewCache;
import com.timehop.stickyheadersrecyclerview.calculation.DimensionCalculator;
import com.timehop.stickyheadersrecyclerview.rendering.HeaderRenderer;
import com.timehop.stickyheadersrecyclerview.util.LinearLayoutOrientationProvider;
import com.timehop.stickyheadersrecyclerview.util.OrientationProvider;

/**
 * @author Ben Neill
 * @since 22/06/15.
 */
public class StickyRecyclerGridHeadersDecoration extends RecyclerView.ItemDecoration {
    private final StickyRecyclerHeadersAdapter mAdapter;
    private final SparseArray<Rect> mHeaderRects;
    private final HeaderProvider mHeaderProvider;
    private final OrientationProvider mOrientationProvider;
    private final HeaderPositionCalculator mHeaderPositionCalculator;
    private final HeaderRenderer mRenderer;
    private final DimensionCalculator mDimensionCalculator;
    private final int colCount;

    public StickyRecyclerGridHeadersDecoration(StickyRecyclerHeadersAdapter adapter, int colCount) {
        this(adapter, new LinearLayoutOrientationProvider(), new DimensionCalculator(), colCount);
    }

    private StickyRecyclerGridHeadersDecoration(StickyRecyclerHeadersAdapter adapter, OrientationProvider orientationProvider, DimensionCalculator dimensionCalculator, int colCount) {
        this(adapter, orientationProvider, dimensionCalculator, new HeaderRenderer(orientationProvider), new HeaderViewCache(adapter, orientationProvider), colCount);
    }

    private StickyRecyclerGridHeadersDecoration(StickyRecyclerHeadersAdapter adapter, OrientationProvider orientationProvider, DimensionCalculator dimensionCalculator, HeaderRenderer headerRenderer, HeaderProvider headerProvider, int colCount) {
        this(adapter, headerRenderer, orientationProvider, dimensionCalculator, headerProvider, new HeaderPositionCalculator(adapter, headerProvider, orientationProvider, dimensionCalculator), colCount);
    }

    private StickyRecyclerGridHeadersDecoration(StickyRecyclerHeadersAdapter adapter, HeaderRenderer headerRenderer, OrientationProvider orientationProvider, DimensionCalculator dimensionCalculator, HeaderProvider headerProvider, HeaderPositionCalculator headerPositionCalculator, int colCount) {
        this.colCount = colCount;
        this.mHeaderRects = new SparseArray<>();
        this.mAdapter = adapter;
        this.mHeaderProvider = headerProvider;
        this.mOrientationProvider = orientationProvider;
        this.mRenderer = headerRenderer;
        this.mDimensionCalculator = dimensionCalculator;
        this.mHeaderPositionCalculator = headerPositionCalculator;
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        int itemPosition = parent.getChildAdapterPosition(view);
        for (int i = 0; i <= colCount - 1; i++) {
            if (mHeaderPositionCalculator.hasNewHeader(itemPosition - i, false)) {
                View header = getHeaderView(parent, itemPosition - i);
                setItemOffsetsForHeader(outRect, header, mOrientationProvider.getOrientation(parent));
            }
        }
    }

    private void setItemOffsetsForHeader(Rect itemOffsets, View header, int orientation) {
        final Rect headerMargins = this.mDimensionCalculator.getMargins(header);
        if (orientation == LinearLayoutManager.VERTICAL) {
            itemOffsets.top = header.getHeight() + headerMargins.top + headerMargins.bottom;
        } else {
            itemOffsets.left = header.getWidth() + headerMargins.left + headerMargins.right;
        }

    }

    @Override
    public void onDrawOver(Canvas canvas, RecyclerView parent, RecyclerView.State state) {
        super.onDrawOver(canvas, parent, state);
        this.mHeaderRects.clear();
        if (parent.getChildCount() > 0 && this.mAdapter.getItemCount() > 0) {
            for (int i = 0; i < parent.getChildCount(); ++i) {
                View itemView = parent.getChildAt(i);
                int position = parent.getChildPosition(itemView);
                if (this.hasStickyHeader(i, position) || this.mHeaderPositionCalculator.hasNewHeader(position, false)) {
                    View header = this.mHeaderProvider.getHeader(parent, position);
                    Rect headerOffset = this.mHeaderPositionCalculator.getHeaderBounds(parent, header, itemView, this.hasStickyHeader(i, position));
                    this.mRenderer.drawHeader(parent, canvas, header, headerOffset);
                    this.mHeaderRects.put(position, headerOffset);
                }
            }

        }
    }

    private boolean hasStickyHeader(int listChildPosition, int indexInList) {
        return listChildPosition <= 0 && this.mAdapter.getHeaderId(indexInList) >= 0;
    }

    public View getHeaderView(RecyclerView parent, int position) {
        return this.mHeaderProvider.getHeader(parent, position);
    }
}

@jotish
Copy link

jotish commented Jan 16, 2016

@asgarddesigns Does the above implementation work with GridLayoutManager?

@asgarddesigns
Copy link

@jotish Yep, it works fine with the support lib version of layoutManager. You may have to override the span size in order to split lines across headers though, something like this:-

    GridLayoutManager layoutManager = new GridLayoutManager(getActivity(), adapter.getColumnCount());
    layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
        @Override
        public int getSpanSize(int position) {
            if (position + 1 >= getItemCount()) {
                return 1;
            } else {
                if (getHeaderId(position) != getHeaderId(position + 1)) {
                    return columnCount - (position % columnCount);
                } else {
                    return 1;
                }
            }
        }
    });

@xiaolongyuan
Copy link

@asgarddesigns
this.mDimensionCalculator.getMargins
this.mHeaderPositionCalculator.getHeaderBounds

Does not exist the method ??

@TobiasBuchholz
Copy link

You can use the init methods for that:

final Rect headerMargins = new Rect();
this.mDimensionCalculator.initMargins(headerMargins, header);


Rect headerOffset = new Rect();
this.mHeaderPositionCalculator.initHeaderBounds(headerOffset, parent, header, itemView, this.hasStickyHeader(i, position));

@xiaolongyuan
Copy link

@TobiasBuchholz Thanks.

   GridLayoutManager layoutManager = new GridLayoutManager(getActivity(), adapter.getColumnCount());
    layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
        @Override
        public int getSpanSize(int position) {
            if (position + 1 >= getItemCount()) {
                return 1;
            } else {
                if (getHeaderId(position) != getHeaderId(position + 1)) {
                    return columnCount - (position % columnCount);
                } else {
                    return 1;
                }
            }
        }
    });

@asgarddesigns this solution but did not correctly

@asgarddesigns
Copy link

@xiaolongyuan Looks like I am still on 0.4.2 when those methods still existed!

@Mariovc
Copy link

Mariovc commented Feb 1, 2016

I have done a fix, that works for me. Take a look to this PR #106

@cbeyls
Copy link

cbeyls commented Jun 1, 2016

Does your StickyRecyclerGridHeadersDecoration also work with StaggeredGridLayoutManager ?

@santhoshrao519
Copy link

@asgarddesigns where to declare columnCount in fragment/activity? Please help

@santhoshrao519
Copy link

@xiaolongyuan what is columnCount and where to declare it, please help out

@santhoshrao519
Copy link

santhoshrao519 commented Jun 12, 2018

I have added columncount=2 but the following output it showing when using the demo.
please look at https://ibb.co/h7Rgxo

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants