package com.twotoasters.multicolumnlistadapter; import android.content.Context; import android.content.res.Resources; import android.database.Cursor; import android.support.v4.widget.CursorAdapter; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.ViewGroup.MarginLayoutParams; import android.widget.LinearLayout; import com.twotoasters.multicolumnlistadapter.MultiColumnListAdapter.MultiColumnListViewHolder; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; /** * This class serves as a cursor adapter for a listview that is simulating a gridview. The advantage * of using this is that you can add headers and footers to your grid-looking listview. * * In order to accomplish the desired effect for your horizontal and vertical grid item spacing, be * sure to check out the getGridItemLayoutParams(int) method comments. */ public abstract class MultiColumnListAdapter<V extends MultiColumnListViewHolder> extends CursorAdapter { private static final String TAG = MultiColumnListAdapter.class.getSimpleName(); protected LayoutInflater inflater; protected int numColumns = 1; protected int gridItemHorizontalSpacing = 0; protected int gridItemVerticalSpacing = 0; public MultiColumnListAdapter(Context context, Cursor cursor, int numColumnsResId, int horizontalSpacingResId, int verticalSpacingResId) { super(context, cursor, 0); inflater = LayoutInflater.from(context); Resources res = context.getResources(); if (numColumnsResId > 0) { numColumns = res.getInteger(numColumnsResId); } if (horizontalSpacingResId > 0) { gridItemHorizontalSpacing = (int) res.getDimension(horizontalSpacingResId); } if (verticalSpacingResId > 0) { gridItemVerticalSpacing = (int) res.getDimension(verticalSpacingResId); } } /** * Create a new row view. */ @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { // create a row view LinearLayout row = new LinearLayout(context); row.setWeightSum(numColumns); V[] gridItemViewHolders = newGridItemViewHolderArray(numColumns); // inflate a grid item view for each column in the row for (int col = 0; col < numColumns; col++) { View gridItemView = newGridItemView(context, cursor, parent); LinearLayout.LayoutParams lp = newGridItemLayoutParams(col); gridItemViewHolders[col] = newGridItemViewHolder(gridItemView); row.addView(gridItemView, lp); } // save the grid item view holders in the row view holder RowViewHolder holder = new RowViewHolder(gridItemViewHolders); row.setTag(holder); return row; } /** * Bind data to an existing row view. */ @Override public void bindView(View convertView, final Context context, Cursor cursor) { int numGridItems = cursor.getCount(); int listRowIndex = cursor.getPosition(); RowViewHolder rowHolder = (RowViewHolder) convertView.getTag(); for (int col = 0; col < numColumns; col++) { V gridItemViewHolder = (V) rowHolder.gridItemHolders[col]; int gridItemIndex = listRowIndex * numColumns + col; if (gridItemIndex < numGridItems) { cursor.moveToPosition(gridItemIndex); setGridItemVisibility(gridItemViewHolder, true); updateGridItemLayoutParams(gridItemViewHolder, listRowIndex); bindGridItemView(gridItemViewHolder, context, cursor); } else { setGridItemVisibility(gridItemViewHolder, false); } } } @SuppressWarnings("unchecked") private V[] newGridItemViewHolderArray(int numColumns) { Class<V> gridItemViewHolderClass = getGridItemViewHolderClass(); return (V[]) Array.newInstance(gridItemViewHolderClass, numColumns); } private V newGridItemViewHolder(View gridItemView) { try { Class<V> gridItemViewHolderClass = getGridItemViewHolderClass(); Constructor<V> ctor = gridItemViewHolderClass.getConstructor(View.class); return ctor.newInstance(gridItemView); } catch (InstantiationException e) { Log.e(TAG, "Error while instantiating new grid item view holder", e); } catch (IllegalAccessException e) { Log.e(TAG, "Grid item view holder must have a public constructor", e); } catch (InvocationTargetException e) { Log.e(TAG, "Error while invoking grid item view holder constructor", e); } catch (NoSuchMethodException e) { Log.e(TAG, "Grid item view holder constructor must have public constructor with a view argument", e); } return null; } /** * Generate layout params for grid item view with appropriate horizontal spacing since the view never changes columns. */ private LinearLayout.LayoutParams newGridItemLayoutParams(int column) { LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT, 1); lp.leftMargin = (column == 0 ? gridItemHorizontalSpacing : gridItemHorizontalSpacing / 2); lp.rightMargin = (column == numColumns - 1 ? gridItemHorizontalSpacing : gridItemHorizontalSpacing / 2); return lp; } /** * Update layout params for grid item view with appropriate vertical spacing since view re-use could change the spacing. */ private void updateGridItemLayoutParams(MultiColumnListViewHolder gridItemViewHolder, int row) { if (gridItemViewHolder != null) { View gridItemView = gridItemViewHolder.getGridItemView(); if (gridItemView != null) { LayoutParams lp = gridItemView.getLayoutParams(); if (lp instanceof MarginLayoutParams) { MarginLayoutParams mlp = (MarginLayoutParams) lp; int numRows = getCount(); mlp.topMargin = (row == 0 ? gridItemVerticalSpacing : gridItemVerticalSpacing / 2); mlp.bottomMargin = (row == numRows - 1 ? gridItemVerticalSpacing : gridItemVerticalSpacing / 2); } } } } private void setGridItemVisibility(MultiColumnListViewHolder gridItemViewHolder, boolean visible) { if (gridItemViewHolder != null) { View gridItemView = gridItemViewHolder.getGridItemView(); if (gridItemView != null) { gridItemView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); } } } /** * Returns the number of rows of grid items. */ @Override public int getCount() { return (int) Math.ceil(super.getCount() / (float) numColumns); } public abstract View newGridItemView(Context context, Cursor cursor, ViewGroup parent); public abstract void bindGridItemView(V holder, final Context context, final Cursor cursor); public abstract Class<V> getGridItemViewHolderClass(); public interface MultiColumnListViewHolder { View getGridItemView(); } public static final class RowViewHolder { MultiColumnListViewHolder[] gridItemHolders; public RowViewHolder(MultiColumnListViewHolder[] gridItemHolders) { this.gridItemHolders = gridItemHolders; } } }