Background
Google plus (google+) app has a nice viewing of images on the "highlights" category.
For each section on this screen, they made a header that contains a clickable text and a button to select all photos of this section. for each section they also show the photos in a grid-like manner.
Here's how it looks like :
Here's another more updated image: link .
For some reason, the images here show a sharing button instead of selections, but that's not the issue I wish to talk about.
The problem
I need to have a similar viewing of photos (including button/s on the headers) , but also make the top header always be visible (AKA "pinned header" , like on this project) .
In fact, I don't even care if it will be pinned (though it could be a nice feature).
What I've tried
I've found only 2 libraries that have pinned header gridViews:
StickyGridHeaders - it seemed fine. the API and code design is very nice . However, i've played with it on some devices and found out it crashes with a very weird exception. i've reported about it here, but as I look at the other issues, I think this project won't get fixed anytime soon.
AStickyHeader - this one doesn't have any crashes and bugs that I can find, but it lacks good code design, and it's not so customizable. the header cannot be clicked and it cannot have a button like on Google-plus. i've tried adding it but for some reason the button isn't shown. I've reported about my remarks on it here.
The question
Is there anyone who have tried to handle such a thing?
Any library available or a modification to the libraries I've tried that allow to have what I've written?
since i can't find any other solution, i've decided to make my own solution (code based on another code i've made, here)
it's used on a ListView instead, but it works quite well. you just set the adapter on the listView and you are good to go. you can set exactly how the headers look like and how each cell look like.
it works by having 2 types of rows: header-rows and cells-rows .
it's not the best solution, since it creates extra views instead of having the ListView/GridView (or whatever you use) put the cells correctly, but it works fine and it doesn't crash
it also doesn't have items clicking (since it's for listView), but it shouldn't be hard to add for whoever uses this code.
sadly it also doesn't have the header as a pinned header, but maybe it's possible to be used with this library (PinnedHeaderListView) .
here's the code :
public abstract class HeaderGridedListViewAdapter<SectionData, ItemType> extends BaseAdapter {
private static final int TYPE_HEADER_ROW = 0;
private static final int TYPE_CELLS_ROW = 1;
private final int mNumColumns;
private final List<Row<SectionData, ItemType>> mRows = new ArrayList<Row<SectionData, ItemType>>();
private final int mCellsRowHeight;
private final Context mContext;
public HeaderGridedListViewAdapter(final Context context, final List<Section<SectionData, ItemType>> sections,
final int numColumns, final int cellsRowHeight) {
this.mContext = context;
this.mNumColumns = numColumns;
this.mCellsRowHeight = cellsRowHeight;
for (final Section<SectionData, ItemType> section : sections) {
// add header
Row<SectionData, ItemType> row = new Row<SectionData, ItemType>();
row.section = section;
row.type = TYPE_HEADER_ROW;
mRows.add(row);
int startIndex = 0;
// add section rows
for (int cellsLeft = section.getItemsCount(); cellsLeft > 0;) {
row = new Row<SectionData, ItemType>();
row.section = section;
row.startIndex = startIndex;
row.type = TYPE_CELLS_ROW;
cellsLeft -= Math.min(mNumColumns, cellsLeft);
startIndex += mNumColumns;
mRows.add(row);
}
}
}
@Override
public int getViewTypeCount() {
return 2;
}
@Override
public int getItemViewType(final int position) {
return getItem(position).type;
}
@Override
public int getCount() {
return mRows.size();
}
@Override
public Row<SectionData, ItemType> getItem(final int position) {
return mRows.get(position);
}
@Override
public long getItemId(final int position) {
return position;
}
@Override
public View getView(final int position, final View convertView, final ViewGroup parent) {
final Row<SectionData, ItemType> item = getItem(position);
switch (item.type) {
case TYPE_CELLS_ROW:
LinearLayout rowLayout = (LinearLayout) convertView;
if (rowLayout == null) {
rowLayout = new LinearLayout(mContext);
rowLayout.setOrientation(LinearLayout.HORIZONTAL);
rowLayout.setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT, mCellsRowHeight));
rowLayout.setWeightSum(mNumColumns);
}
final int childCount = rowLayout.getChildCount();
// reuse previous views of the row if possible
for (int i = 0; i < mNumColumns; ++i) {
// reuse old views if possible
final View cellConvertView = i < childCount ? rowLayout.getChildAt(i) : null;
// fill cell with data
final View cellView = getCellView(item.section, item.startIndex + i, cellConvertView, rowLayout);
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) cellView.getLayoutParams();
if (layoutParams == null) {
layoutParams = new LinearLayout.LayoutParams(0, mCellsRowHeight, 1);
cellView.setLayoutParams(layoutParams);
} else {
final boolean needSetting = layoutParams.weight != 1 || layoutParams.width != 0
|| layoutParams.height != mCellsRowHeight;
if (needSetting) {
layoutParams.width = 0;
layoutParams.height = mCellsRowHeight;
layoutParams.weight = 1;
cellView.setLayoutParams(layoutParams);
}
}
if (cellConvertView == null)
rowLayout.addView(cellView);
}
return rowLayout;
case TYPE_HEADER_ROW:
return getHeaderView(item.section, convertView, parent);
}
throw new UnsupportedOperationException("cannot create this type of row view");
}
@Override
public boolean areAllItemsEnabled() {
return false;
}
@Override
public boolean isEnabled(final int position) {
return false;
}
/** should handle getting a single header view */
public abstract View getHeaderView(Section<SectionData, ItemType> section, View convertView, ViewGroup parent);
/**
* should handle getting a single cell view. <br/>
* NOTE:read the parameters description carefully !
*
* @param section
* the section that this cell belongs to
* @param positionWithinSection
* the position within the section that we need to fill the data with. note that if it's larger than what
* the section can give you, it means we need an empty cell (same the the others, but shouldn't show
* anything, can be invisible if you wish)
* @param convertView
* a recycled row cell. you must use it when it's not null, and fill it with data
* @param parent
* the parent of the view. you should use it for inflating the view (but don't attach the view to the
* parent)
*/
public abstract View getCellView(Section<SectionData, ItemType> section, int positionWithinSection,
View convertView, ViewGroup parent);
// ////////////////////////////////////
// Section//
// /////////
public static class Section<SectionData, ItemType> {
private final List<ItemType> mItems;
private final SectionData mSectionData;
public Section(final SectionData sectionData, final List<ItemType> items) {
this.mSectionData = sectionData;
this.mItems = items;
}
public SectionData getSectionData() {
return mSectionData;
}
public int getItemsCount() {
return mItems.size();
}
public ItemType getItem(final int posInSection) {
return mItems.get(posInSection);
}
@Override
public String toString() {
return mSectionData;
}
}
// ////////////////////////////////////
// Row//
// /////
private static class Row<SectionData, ItemType> {
int type, startIndex;
Section<SectionData, ItemType> section;
}
}