Expandable Gridview With View Recycling In Android
I am trying to implement a android activity where I have sections of items (for example car brands and their models). I want to be able to display the items in a grid (e.g. fixed t
Solution 1:
Here's my reinvented wheel (A lot of code is copy-pasted from AOSP's GridView).
package ua.snuk182.expandablegrid;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.ExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.LinearLayout;
publicclassExpandableGridViewextendsExpandableListView {
/**
* Disables stretching.
*
* @see #setStretchMode(int)
*/publicstaticfinalintNO_STRETCH=0;
/**
* Stretches the spacing between columns.
*
* @see #setStretchMode(int)
*/publicstaticfinalintSTRETCH_SPACING=1;
/**
* Stretches columns.
*
* @see #setStretchMode(int)
*/publicstaticfinalintSTRETCH_COLUMN_WIDTH=2;
/**
* Stretches the spacing between columns. The spacing is uniform.
*
* @see #setStretchMode(int)
*/publicstaticfinalintSTRETCH_SPACING_UNIFORM=3;
/**
* Creates as many columns as can fit on screen.
*
* @see #setNumColumns(int)
*/publicstaticfinalintAUTO_FIT= -1;
privateintmNumColumns= AUTO_FIT;
privateintmHorizontalSpacing=0;
privateint mRequestedHorizontalSpacing;
privateintmVerticalSpacing=0;
privateintmStretchMode= STRETCH_COLUMN_WIDTH;
privateint mColumnWidth;
privateint mRequestedColumnWidth;
privateint mRequestedNumColumns;
publicExpandableGridView(Context context) {
this(context, null);
}
publicExpandableGridView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
publicExpandableGridView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArraya= context.obtainStyledAttributes(attrs,
R.styleable.ExpandableGridView, defStyle, 0);
inthSpacing= a.getDimensionPixelOffset(
R.styleable.ExpandableGridView_horizontalSpacing, 0);
setHorizontalSpacing(hSpacing);
intvSpacing= a.getDimensionPixelOffset(
R.styleable.ExpandableGridView_verticalSpacing, 0);
setVerticalSpacing(vSpacing);
intindex= a.getInt(R.styleable.ExpandableGridView_stretchMode, STRETCH_COLUMN_WIDTH);
if (index >= 0) {
setStretchMode(index);
}
intcolumnWidth= a.getDimensionPixelOffset(R.styleable.ExpandableGridView_columnWidth, -1);
if (columnWidth > 0) {
setColumnWidth(columnWidth);
}
intnumColumns= a.getInt(R.styleable.ExpandableGridView_numColumns, 1);
setNumColumns(numColumns);
//I haven't dealt with gravity yet, so this is commented for now.../*index = a.getInt(R.styleable.ExpandableGridView_gravity, -1);
if (index >= 0) {
setGravity(index);
}*/
a.recycle();
}
@OverridepublicvoidsetAdapter(ExpandableListAdapter adapter) {
super.setAdapter(newExpandableGridInnerAdapter(adapter));
}
/**
* Set the amount of horizontal (x) spacing to place between each item
* in the grid.
*
* @param horizontalSpacing The amount of horizontal space between items,
* in pixels.
*
* @attr ref android.R.styleable#GridView_horizontalSpacing
*/publicvoidsetHorizontalSpacing(int horizontalSpacing) {
if (horizontalSpacing != mRequestedHorizontalSpacing) {
mRequestedHorizontalSpacing = horizontalSpacing;
requestLayout();
}
}
/**
* Returns the amount of horizontal spacing currently used between each item in the grid.
*
* <p>This is only accurate for the current layout. If {@link #setHorizontalSpacing(int)}
* has been called but layout is not yet complete, this method may return a stale value.
* To get the horizontal spacing that was explicitly requested use
* {@link #getRequestedHorizontalSpacing()}.</p>
*
* @return Current horizontal spacing between each item in pixels
*
* @see #setHorizontalSpacing(int)
* @see #getRequestedHorizontalSpacing()
*
* @attr ref android.R.styleable#GridView_horizontalSpacing
*/publicintgetHorizontalSpacing() {
return mHorizontalSpacing;
}
/**
* Returns the requested amount of horizontal spacing between each item in the grid.
*
* <p>The value returned may have been supplied during inflation as part of a style,
* the default GridView style, or by a call to {@link #setHorizontalSpacing(int)}.
* If layout is not yet complete or if GridView calculated a different horizontal spacing
* from what was requested, this may return a different value from
* {@link #getHorizontalSpacing()}.</p>
*
* @return The currently requested horizontal spacing between items, in pixels
*
* @see #setHorizontalSpacing(int)
* @see #getHorizontalSpacing()
*
* @attr ref android.R.styleable#GridView_horizontalSpacing
*/publicintgetRequestedHorizontalSpacing() {
return mRequestedHorizontalSpacing;
}
/**
* Set the amount of vertical (y) spacing to place between each item
* in the grid.
*
* @param verticalSpacing The amount of vertical space between items,
* in pixels.
*
* @see #getVerticalSpacing()
*
* @attr ref android.R.styleable#GridView_verticalSpacing
*/publicvoidsetVerticalSpacing(int verticalSpacing) {
if (verticalSpacing != mVerticalSpacing) {
mVerticalSpacing = verticalSpacing;
requestLayout();
}
}
/**
* Returns the amount of vertical spacing between each item in the grid.
*
* @return The vertical spacing between items in pixels
*
* @see #setVerticalSpacing(int)
*
* @attr ref android.R.styleable#GridView_verticalSpacing
*/publicintgetVerticalSpacing() {
return mVerticalSpacing;
}
/**
* Control how items are stretched to fill their space.
*
* @param stretchMode Either {@link #NO_STRETCH},
* {@link #STRETCH_SPACING}, {@link #STRETCH_SPACING_UNIFORM}, or {@link #STRETCH_COLUMN_WIDTH}.
*
* @attr ref android.R.styleable#GridView_stretchMode
*/publicvoidsetStretchMode(int stretchMode) {
if (stretchMode != mStretchMode) {
mStretchMode = stretchMode;
requestLayout();
}
}
publicintgetStretchMode() {
return mStretchMode;
}
/**
* Set the width of columns in the grid.
*
* @param columnWidth The column width, in pixels.
*
* @attr ref android.R.styleable#GridView_columnWidth
*/publicvoidsetColumnWidth(int columnWidth) {
if (columnWidth != mRequestedColumnWidth) {
mRequestedColumnWidth = columnWidth;
requestLayout();
}
}
/**
* Return the width of a column in the grid.
*
* <p>This may not be valid yet if a layout is pending.</p>
*
* @return The column width in pixels
*
* @see #setColumnWidth(int)
* @see #getRequestedColumnWidth()
*
* @attr ref android.R.styleable#GridView_columnWidth
*/publicintgetColumnWidth() {
return mColumnWidth;
}
/**
* Return the requested width of a column in the grid.
*
* <p>This may not be the actual column width used. Use {@link #getColumnWidth()}
* to retrieve the current real width of a column.</p>
*
* @return The requested column width in pixels
*
* @see #setColumnWidth(int)
* @see #getColumnWidth()
*
* @attr ref android.R.styleable#GridView_columnWidth
*/publicintgetRequestedColumnWidth() {
return mRequestedColumnWidth;
}
/**
* Set the number of columns in the grid
*
* @param numColumns The desired number of columns.
*
* @attr ref android.R.styleable#GridView_numColumns
*/publicvoidsetNumColumns(int numColumns) {
if (numColumns != mRequestedNumColumns) {
mRequestedNumColumns = numColumns;
requestLayout();
}
}
/**
* Get the number of columns in the grid.
* Returns {@link #AUTO_FIT} if the Grid has never been laid out.
*
* @attr ref android.R.styleable#GridView_numColumns
*
* @see #setNumColumns(int)
*/@ViewDebug.ExportedProperty
publicintgetNumColumns() {
return mNumColumns;
}
public ExpandableListAdapter getInnerAdapter() {
return ((ExpandableGridInnerAdapter)getExpandableListAdapter()).mInnerAdapter;
}
privatebooleandetermineColumns(int availableSpace) {
finalintrequestedHorizontalSpacing= mRequestedHorizontalSpacing;
finalintstretchMode= mStretchMode;
finalintrequestedColumnWidth= mRequestedColumnWidth;
booleandidNotInitiallyFit=false;
if (mRequestedNumColumns == AUTO_FIT) {
if (requestedColumnWidth > 0) {
// Client told us to pick the number of columns
mNumColumns = (availableSpace + requestedHorizontalSpacing) /
(requestedColumnWidth + requestedHorizontalSpacing);
} else {
// Just make up a number if we don't have enough info
mNumColumns = 2;
}
} else {
// We picked the columns
mNumColumns = mRequestedNumColumns;
}
if (mNumColumns <= 0) {
mNumColumns = 1;
}
switch (stretchMode) {
case NO_STRETCH:
// Nobody stretches
mColumnWidth = requestedColumnWidth;
mHorizontalSpacing = requestedHorizontalSpacing;
break;
default:
intspaceLeftOver= availableSpace - (mNumColumns * requestedColumnWidth) -
((mNumColumns - 1) * requestedHorizontalSpacing);
if (spaceLeftOver < 0) {
didNotInitiallyFit = true;
}
switch (stretchMode) {
case STRETCH_COLUMN_WIDTH:
// Stretch the columns
mColumnWidth = requestedColumnWidth + spaceLeftOver / mNumColumns;
mHorizontalSpacing = requestedHorizontalSpacing;
break;
case STRETCH_SPACING:
// Stretch the spacing between columns
mColumnWidth = requestedColumnWidth;
if (mNumColumns > 1) {
mHorizontalSpacing = requestedHorizontalSpacing +
spaceLeftOver / (mNumColumns - 1);
} else {
mHorizontalSpacing = requestedHorizontalSpacing + spaceLeftOver;
}
break;
case STRETCH_SPACING_UNIFORM:
// Stretch the spacing between columns
mColumnWidth = requestedColumnWidth;
if (mNumColumns > 1) {
mHorizontalSpacing = requestedHorizontalSpacing +
spaceLeftOver / (mNumColumns + 1);
} else {
mHorizontalSpacing = requestedHorizontalSpacing + spaceLeftOver;
}
break;
}
break;
}
return didNotInitiallyFit;
}
@OverrideprotectedvoidonMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
intwidthMode= MeasureSpec.getMode(widthMeasureSpec);
intwidthSize= MeasureSpec.getSize(widthMeasureSpec);
if (widthMode == MeasureSpec.UNSPECIFIED) {
if (mColumnWidth > 0) {
widthSize = mColumnWidth + getPaddingLeft() + getPaddingRight();
} else {
widthSize = getPaddingLeft() + getPaddingRight();
}
widthSize += getVerticalScrollbarWidth();
}
intchildWidth= widthSize - getPaddingLeft() - getPaddingRight();
determineColumns(childWidth);
}
privateclassExpandableGridInnerAdapterimplementsExpandableListAdapter {
privatefinal ExpandableListAdapter mInnerAdapter;
privateExpandableGridInnerAdapter(ExpandableListAdapter adapter) {
this.mInnerAdapter = adapter;
}
@OverridepublicintgetGroupCount() {
return mInnerAdapter.getGroupCount();
}
@OverridepublicintgetChildrenCount(int groupPosition) {
intrealCount= mInnerAdapter.getChildrenCount(groupPosition);
int count;
if (mNumColumns != AUTO_FIT) {
count = realCount > 0 ? (realCount + mNumColumns - 1) / mNumColumns : 0;
} else {
count = realCount;
}
return count;
}
@Overridepublic Object getGroup(int groupPosition) {
return mInnerAdapter.getGroup(groupPosition);
}
@Overridepublic Object getChild(int groupPosition, int childPosition) {
return mInnerAdapter.getChild(groupPosition, childPosition);
}
@OverridepubliclonggetGroupId(int groupPosition) {
return mInnerAdapter.getGroupId(groupPosition);
}
@OverridepubliclonggetChildId(int groupPosition, int childPosition) {
return0;
}
@OverridepublicbooleanhasStableIds() {
returnfalse;
}
@Overridepublic View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
return mInnerAdapter.getGroupView(groupPosition, isExpanded, convertView, parent);
}
@SuppressLint("InlinedApi")@Overridepublic View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
LinearLayoutrow= (LinearLayout) (convertView != null ? convertView : newLinearLayout(getContext()));
if (row.getLayoutParams() == null) {
row.setLayoutParams(newAbsListView.LayoutParams(AbsListView.LayoutParams.MATCH_PARENT, AbsListView.LayoutParams.WRAP_CONTENT, AbsListView.ITEM_VIEW_TYPE_IGNORE));
row.setPadding(0, mVerticalSpacing / 2, 0, mVerticalSpacing / 2);
row.setGravity(Gravity.CENTER_HORIZONTAL);
}
intgroupChildrenCount= mInnerAdapter.getChildrenCount(groupPosition);
intindex=0;
for (int i=mNumColumns * childPosition; i<(mNumColumns * (childPosition + 1)); i++, index++) {
View child;
ViewcachedChild= index < row.getChildCount() ? row.getChildAt(index) : null;
if (i<groupChildrenCount) {
if (cachedChild != null && cachedChild.getTag() == null) {
((ViewGroup)cachedChild.getParent()).removeView(cachedChild);
cachedChild = null;
}
child = mInnerAdapter.getChildView(groupPosition, i, i == (groupChildrenCount - 1), cachedChild, parent);
child.setTag(mInnerAdapter.getChild(groupPosition, i));
} else {
if (cachedChild != null && cachedChild.getTag() != null) {
((ViewGroup)cachedChild.getParent()).removeView(cachedChild);
cachedChild = null;
}
child = newView(getContext());
child.setTag(null);
}
if (!(child.getLayoutParams() instanceof LinearLayout.LayoutParams)) {
LinearLayout.LayoutParams params;
if (child.getLayoutParams() == null) {
params = newLinearLayout.LayoutParams(mColumnWidth, LayoutParams.WRAP_CONTENT, 1);
} else {
params = newLinearLayout.LayoutParams(mColumnWidth, child.getLayoutParams().height, 1);
}
child.setLayoutParams(params);
}
child.setPadding(mHorizontalSpacing / 2, 0, mHorizontalSpacing / 2, 0);
if (index == row.getChildCount()) {
row.addView(child, index);
} else {
child.invalidate();
}
}
return row;
}
@OverridepublicbooleanisChildSelectable(int groupPosition, int childPosition) {
returnfalse;
}
@OverridepublicvoidregisterDataSetObserver(DataSetObserver observer) {
mInnerAdapter.registerDataSetObserver(observer);
}
@OverridepublicvoidunregisterDataSetObserver(DataSetObserver observer) {
mInnerAdapter.unregisterDataSetObserver(observer);
}
@OverridepublicbooleanareAllItemsEnabled() {
return mInnerAdapter.areAllItemsEnabled();
}
@OverridepublicbooleanisEmpty() {
return mInnerAdapter.isEmpty();
}
@OverridepublicvoidonGroupExpanded(int groupPosition) {
mInnerAdapter.onGroupExpanded(groupPosition);
}
@OverridepublicvoidonGroupCollapsed(int groupPosition) {
mInnerAdapter.onGroupCollapsed(groupPosition);
}
/*@Override
public long getCombinedChildId(long groupId, long childId) {
return mInnerAdapter.getCombinedChildId(groupId, childId);
}
@Override
public long getCombinedGroupId(long groupId) {
return mInnerAdapter.getCombinedGroupId(groupId);
}*/publiclonggetCombinedChildId(long groupId, long childId) {
return0x8000000000000000L | ((groupId & 0x7FFFFFFF) << 32) | (childId & 0xFFFFFFFF);
}
publiclonggetCombinedGroupId(long groupId) {
return (groupId & 0x7FFFFFFF) << 32;
}
}
}
Corresponding attrs.xml.
<?xml version="1.0" encoding="utf-8"?><resources><declare-styleablename="ExpandableGridView"><attrname="horizontalSpacing"format="dimension" /><attrname="verticalSpacing"format="dimension" /><attrname="stretchMode"><enumname="none"value="0"/><enumname="spacingWidth"value="1" /><enumname="columnWidth"value="2" /><enumname="spacingWidthUniform"value="3" /></attr><attrname="columnWidth"format="dimension" /><attrname="numColumns"format="integer"min="0"><enumname="auto_fit"value="-1" /></attr></declare-styleable></resources>
Solution 2:
instead of using multiple expandable gridviews you could use this library https://github.com/TonicArtos/StickyGridHeaders, in which you can put one adapter with one gridView (the custom StickyGridHeadersView), and manage the different headers with specific view handlers for each kind of header/grid-element
Solution 3:
You can use this example of Expandable RecyclerView . It provides an Expandable RecyclerView with group items that can be individually expanded to show its children in a two-dimensional scrolling grid. Each grid item can be selected.
Post a Comment for "Expandable Gridview With View Recycling In Android"