Ответ 1
так как я не могу найти какое-либо другое решение, я решил сделать свое собственное решение (код на основе другого кода, который я сделал, здесь)
он используется вместо ListView, но он работает очень хорошо. вы просто устанавливаете адаптер в listView, и вам хорошо идти. вы можете точно определить, как выглядят заголовки и как выглядит каждая ячейка.
он работает, имея 2 типа строк: header-rows и cells-rows.
это не лучшее решение, так как оно создает дополнительные представления вместо того, чтобы ListView/GridView (или что-то, что вы используете) правильно помещал ячейки, но он отлично работает и не разбивается
он также не имеет элементов, которые нажимают (так как это для listView), но его не должно быть сложно добавить для тех, кто использует этот код.
К сожалению, у него также нет заголовка в качестве закрепленного заголовка, но, возможно, его можно использовать с этой библиотеки (PinnedHeaderListView).
здесь код:
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 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 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;
}
}