Created
September 27, 2013 21:53
-
-
Save dustin-graham/6735760 to your computer and use it in GitHub Desktop.
Doing ListViews with "delegates" and "CellRenderers"
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public interface CellRenderer { | |
public static int CELL_TYPE_SCHEDULE = 0; | |
public static int CELL_TYPE_HISTORY = 1; | |
void renderRowCell(String workoutId, Date dateScheduled, double meters, double otherValue, int calories, String title, String workoutType); | |
void renderRowCell(String workoutId, Date dateScheduled, double meters, double otherValue, int calories, String title, String workoutType, String thumbnailURL); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public interface CellRendererFactory { | |
CellRenderer newCellRenderer(Context context); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class WorkoutListAdapter extends CursorAdapter { | |
private int mRowViewType; | |
public interface WorkoutListItemFormatHelper { | |
String getDisplayDatePrefix(); | |
} | |
private WeakReference<WorkoutListItemFormatHelper> mListFormatHelper; | |
/* | |
* use these as keys for a SparseArray to inform this adapter which indexes | |
* in the provided cursor should map data to which views | |
*/ | |
public static int ID_INDEX = 0; | |
public static int WORKOUT_TITLE_INDEX = 1; | |
public static int WORKOUT_CREATED_DATE_INDEX = 2; | |
public static int WORKOUT_TYPE_INDEX = 3; | |
public static int WORKOUT_DISTANCE_INDEX = 4; | |
public static int WORKOUT_MAX_INCLINE_INDEX = 5; | |
public static int WORKOUT_CALORIES_INDEX = 6; | |
public static int LOCAL_IMAGE_PATH_INDEX = 7; | |
public static int WORKOUT_ID_INDEX = 8; | |
public static int WORKOUT_ROUTE_ID_INDEX = 9; | |
public static int WORKOUT_OTHER_INDEX = 10; | |
public static int PAGINATED_INDEX_COLUMN = 11;// Only set this if your data | |
// set is paginated | |
public static int N_A = -1; | |
private int mIdIndex; | |
private int mWorkoutLocalImagePathIndex; | |
private int mWorkoutTitleIndex; | |
private int mWorkoutCreatedDateIndex; | |
private int mWorkoutTypeIndex; | |
private int mWorkoutDistanceIndex; | |
private int mWorkoutMaxInclineIndex; | |
private int mWorkoutCaloriesIndex; | |
private int mWorkoutIdIndex; | |
private int mPaginatedIndex; | |
private int mRouteIdIndex; | |
private int mOtherIndex; | |
private CellRendererFactory mCellFactory; | |
public WorkoutListAdapter(Context context, Cursor c, int flags, SparseIntArray projectionMap, CellRendererFactory cellFactory) { | |
super(context, c, flags); | |
this.setCursorIndexes(projectionMap); | |
this.mCellFactory = cellFactory; | |
} | |
private void setCursorIndexes(SparseIntArray projectionMap) { | |
mIdIndex = projectionMap.get(ID_INDEX); | |
mWorkoutLocalImagePathIndex = projectionMap.get(LOCAL_IMAGE_PATH_INDEX); | |
mWorkoutTitleIndex = projectionMap.get(WORKOUT_TITLE_INDEX); | |
mWorkoutCreatedDateIndex = projectionMap.get(WORKOUT_CREATED_DATE_INDEX); | |
mWorkoutTypeIndex = projectionMap.get(WORKOUT_TYPE_INDEX); | |
mWorkoutDistanceIndex = projectionMap.get(WORKOUT_DISTANCE_INDEX); | |
mWorkoutMaxInclineIndex = projectionMap.get(WORKOUT_MAX_INCLINE_INDEX); | |
mWorkoutCaloriesIndex = projectionMap.get(WORKOUT_CALORIES_INDEX); | |
mWorkoutIdIndex = projectionMap.get(WORKOUT_ID_INDEX); | |
mRouteIdIndex = projectionMap.get(WORKOUT_ROUTE_ID_INDEX); | |
mOtherIndex = projectionMap.get(WORKOUT_OTHER_INDEX); | |
mPaginatedIndex = projectionMap.get(PAGINATED_INDEX_COLUMN);// this is 0 | |
// if it's | |
// not set | |
// by the | |
// projection | |
// map | |
} | |
/* | |
* static ViewHolder behavior is handled by the custom list view | |
* (non-Javadoc) | |
* | |
* @see android.support.v4.widget.CursorAdapter#bindView(android.view.View, | |
* android.content.Context, android.database.Cursor) | |
*/ | |
@Override | |
public void bindView(View view, Context context, Cursor cursor) { | |
CellRenderer listItem = (CellRenderer) view; | |
Long id = cursor.getLong(mIdIndex); | |
String localImagePath = mWorkoutLocalImagePathIndex != N_A ? cursor.getString(mWorkoutLocalImagePathIndex) | |
: null; | |
String workoutTitle = cursor.getString(mWorkoutTitleIndex); | |
String workoutCreatedDate = mWorkoutCreatedDateIndex != N_A ? cursor.getString(mWorkoutCreatedDateIndex) : null; | |
String workoutType = mWorkoutCreatedDateIndex != N_A ? cursor.getString(mWorkoutTypeIndex) : null; | |
double workoutMeters = cursor.getDouble(mWorkoutDistanceIndex); | |
String distanceLabel = IfitPrefHelper.getDistanceUnit(); | |
double workoutMaxIncline = cursor.getDouble(mWorkoutMaxInclineIndex); | |
double workoutCalories = cursor.getDouble(mWorkoutCaloriesIndex); | |
String workoutId = cursor.getString(mWorkoutIdIndex); | |
double other = cursor.getDouble(mOtherIndex); | |
int pageIndex = 0; | |
if(cursor.getString(mRouteIdIndex)!=null && cursor.getString(mRouteIdIndex).trim().length() != 0) workoutType = WorkoutType.GOOGLE_WORKOUT; | |
if (mPaginatedIndex != 0) { | |
pageIndex = cursor.getInt(mPaginatedIndex); | |
} | |
listItem.renderRowCell(workoutId, DateHelper.dateFromISO8601(workoutCreatedDate),workoutMeters,other,(int)workoutCalories,workoutTitle,workoutType); | |
} | |
public int getPageIndex(Cursor cursor) { | |
if (cursor.getCount() == 0) | |
return 0; | |
int pageIndex = 0; | |
if (mPaginatedIndex > 0 && cursor.getColumnCount() - 1 >= mPaginatedIndex) { | |
pageIndex = cursor.getInt(mPaginatedIndex); | |
} | |
return pageIndex; | |
} | |
@Override | |
public View newView(Context context, Cursor cursor, ViewGroup parent) { | |
View listItem = (View) mCellFactory.newCellRenderer(context); | |
bindView(listItem, context, cursor); | |
return listItem; | |
} | |
@Override | |
public boolean areAllItemsEnabled() { | |
return false; | |
} | |
public Cursor swapCursor(Cursor newCursor, SparseIntArray projectionMap, CellRendererFactory cellFactory) { | |
mCellFactory = cellFactory; | |
setCursorIndexes(projectionMap); | |
return super.swapCursor(newCursor); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class WorkoutListDelegate implements Parcelable, CellRendererFactory { | |
public static final int NO_RES = -1; | |
protected int mMaxPage = 1; //default value for which page to order if the delegate is configured to paginate data | |
protected static String[] DEFAULT_WORKOUT_PROJECTION = new String[] { WorkoutTable._ID, | |
WorkoutTable.WORKOUT_TITLE, | |
WorkoutTable.DATE_SCHEDULED, | |
WorkoutTable.WORKOUT_TYPE, | |
WorkoutTable.DISTANCE, | |
WorkoutTable.MAX_INCLINE, | |
WorkoutTable.CALORIES, | |
WorkoutTable.IMAGE_URL, | |
WorkoutTable.WORKOUT_ID, | |
WorkoutTable.ROUTE_ID, | |
WorkoutTable.DURATION}; | |
protected static SparseIntArray DEFAULT_WORKOUT_PROJECTION_MAP; | |
static { | |
DEFAULT_WORKOUT_PROJECTION_MAP = new SparseIntArray(); | |
DEFAULT_WORKOUT_PROJECTION_MAP.append(WorkoutListAdapter.ID_INDEX, 0); | |
DEFAULT_WORKOUT_PROJECTION_MAP.append(WorkoutListAdapter.WORKOUT_TITLE_INDEX, 1); | |
DEFAULT_WORKOUT_PROJECTION_MAP.append(WorkoutListAdapter.WORKOUT_CREATED_DATE_INDEX, 2); | |
DEFAULT_WORKOUT_PROJECTION_MAP.append(WorkoutListAdapter.WORKOUT_TYPE_INDEX, 3); | |
DEFAULT_WORKOUT_PROJECTION_MAP.append(WorkoutListAdapter.WORKOUT_DISTANCE_INDEX, 4); | |
DEFAULT_WORKOUT_PROJECTION_MAP.append(WorkoutListAdapter.WORKOUT_MAX_INCLINE_INDEX, 5); | |
DEFAULT_WORKOUT_PROJECTION_MAP.append(WorkoutListAdapter.WORKOUT_CALORIES_INDEX, 6); | |
DEFAULT_WORKOUT_PROJECTION_MAP.append(WorkoutListAdapter.LOCAL_IMAGE_PATH_INDEX, 7); | |
DEFAULT_WORKOUT_PROJECTION_MAP.append(WorkoutListAdapter.WORKOUT_ID_INDEX, 8); | |
DEFAULT_WORKOUT_PROJECTION_MAP.append(WorkoutListAdapter.WORKOUT_ROUTE_ID_INDEX, 9); | |
DEFAULT_WORKOUT_PROJECTION_MAP.append(WorkoutListAdapter.WORKOUT_OTHER_INDEX, 10); | |
// DEFAULT_WORKOUT_PROJECTION_MAP.append(WorkoutListAdapter.LOCAL_IMAGE_PATH_INDEX, WorkoutListAdapter.N_A); | |
} | |
protected String mUserId; | |
public String getUserId() { | |
return mUserId; | |
} | |
public static final Parcelable.Creator<WorkoutListDelegate> CREATOR = new Parcelable.Creator<WorkoutListDelegate>() { | |
public WorkoutListDelegate createFromParcel(Parcel in) { | |
return new WorkoutListDelegate(in); | |
} | |
public WorkoutListDelegate[] newArray(int size) { | |
return new WorkoutListDelegate[size]; | |
} | |
}; | |
public WorkoutListDelegate() { | |
} | |
protected WorkoutListDelegate(Parcel in) { | |
} | |
/** | |
* Convenience accessor for the workout list title regardless of it coming | |
* from a resource string or a dynamic string | |
* | |
* @param context | |
* @return | |
*/ | |
public String getListName(Context context) { | |
if (getWorkoutListNameResId() == NO_RES) { | |
return getWorkoutCollectionName(); | |
} else { | |
return context.getResources().getString(getWorkoutListNameResId()); | |
} | |
} | |
public int getWorkoutListNameResId() { | |
return R.string.app_name; | |
} | |
public String getWorkoutCollectionName() { | |
return null; | |
} | |
public int getWorkoutListIconResource() { | |
return R.drawable.common_signin_btn_icon_dark; | |
} | |
public int getAuxiliaryOptionButtonTitleResId(Context context) { | |
return NO_RES; | |
} | |
public void onAuxiliaryOptionSelected(Context context) { | |
} | |
public boolean hasLearnMoreOption() { | |
return false; | |
} | |
public void onLearnMoreOptionSelected() { | |
} | |
public Uri getUri() { | |
return IfitDatabaseProvider.WORKOUT_CONTENT_URI; | |
} | |
public SparseIntArray getProjectionMap() { | |
return DEFAULT_WORKOUT_PROJECTION_MAP; | |
} | |
public String[] getProjection() { | |
return DEFAULT_WORKOUT_PROJECTION; | |
} | |
public String getSelection() { | |
return null; | |
} | |
public String[] getSelectionArgs() { | |
return null; | |
} | |
public String getSortOrder() { | |
return null; | |
} | |
public boolean implementsOnReadyToSync() { | |
return false; | |
} | |
public void onReadyToSync(Context context) { | |
} | |
public Class getDataSynchronizerClass() { | |
return null; | |
} | |
public void onUserIdChanged(String userId) { | |
mUserId = userId; | |
} | |
/* | |
* the lastPageIndex indicates for what page we have reached the bottom of | |
*/ | |
public void onReachedEndOfContent(int lastPageIndex, Context context) { | |
if (isPaginaged() && lastPageIndex >= mMaxPage) {//make sure that we don't request a page more than once | |
//ensures that we only ever ask for pages that are only 1 more than our actual content has | |
mMaxPage = lastPageIndex + 1; | |
onReadyToSync(context); | |
} | |
} | |
protected boolean isPaginaged() { | |
return false; | |
} | |
public int getPageSize() { | |
return 20; | |
} | |
public Cursor findWorkout(Context context, long id) { | |
return null; | |
} | |
/** | |
* This method allows delegates to specify a special fragment to display if the user is logged out. Null by default. | |
* @return | |
*/ | |
public Fragment getLoggedOutFragment() { | |
return null; | |
} | |
@Override | |
public CellRenderer newCellRenderer(Context context) { | |
return new WorkoutListItem(context); | |
} | |
@Override | |
public int describeContents() { | |
return 0; | |
} | |
@Override | |
public void writeToParcel(Parcel dest, int flags) { | |
} | |
public void onListItemClick(Context context, long id){ | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class WorkoutListFragment extends ListFragment implements LoaderManager.LoaderCallbacks<Cursor>, AbsListView.OnScrollListener { | |
public static final String LIST_DELEGATE = "list_delegate"; | |
private WorkoutListDelegate mWorkoutListDelegate; | |
private WorkoutListAdapter mListAdapter; | |
private int mPageSize; | |
public static WorkoutListFragment createWorkoutListFragment(WorkoutListDelegate delegate) { | |
WorkoutListFragment fragment = new WorkoutListFragment(); | |
Bundle args = new Bundle(); | |
args.putParcelable(LIST_DELEGATE,delegate); | |
fragment.setArguments(args); | |
return fragment; | |
} | |
@Override | |
public void onCreate(Bundle savedInstanceState) { | |
Log.d("WorkoutListFragment", "onCreate"); | |
super.onCreate(savedInstanceState); | |
mWorkoutListDelegate = getArguments().getParcelable(LIST_DELEGATE); | |
mWorkoutListDelegate.onUserIdChanged(UserSession.getInstance(getActivity()).getUserId()); | |
mPageSize = mWorkoutListDelegate.getPageSize(); | |
mListAdapter = new WorkoutListAdapter(getActivity(),null,0,mWorkoutListDelegate.getProjectionMap(), mWorkoutListDelegate); | |
setListAdapter(mListAdapter); | |
if (mWorkoutListDelegate.implementsOnReadyToSync()) { | |
mWorkoutListDelegate.onReadyToSync(getActivity()); | |
} | |
} | |
@Override | |
public void onViewCreated(View view, Bundle savedInstanceState) { | |
super.onViewCreated(view, savedInstanceState); | |
getListView().setOnScrollListener(this); | |
} | |
@Override | |
public void onResume() { | |
super.onResume(); | |
getLoaderManager().restartLoader(0,null,this); | |
BusProvider.getInstance().register(this); | |
} | |
@Override | |
public void onPause() { | |
super.onPause(); | |
BusProvider.getInstance().unregister(this); | |
} | |
@Override | |
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) { | |
return new CursorLoader(getActivity(), mWorkoutListDelegate.getUri(), mWorkoutListDelegate.getProjection(), mWorkoutListDelegate.getSelection(), mWorkoutListDelegate.getSelectionArgs(), mWorkoutListDelegate.getSortOrder()); | |
} | |
@Override | |
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) { | |
mListAdapter.swapCursor(cursor); | |
} | |
@Override | |
public void onLoaderReset(Loader<Cursor> cursorLoader) { | |
mListAdapter.swapCursor(null); | |
} | |
@Subscribe | |
public void onLoginSucceeded(AuthenticationEvent successEvent) { | |
.....code | |
getLoaderManager().restartLoader(0, null, this); | |
if (mWorkoutListDelegate.implementsOnReadyToSync()) { | |
mWorkoutListDelegate.onReadyToSync(getActivity()); | |
} | |
} | |
@Override | |
public void onListItemClick(ListView l, View v, int position, long id) { | |
mWorkoutListDelegate.onListItemClick(getActivity(),id); | |
} | |
@Override | |
public void onScroll(AbsListView mView, int mFirstVisibleItem, | |
int mVisibleItemCount, int mTotalItemCount) { | |
if (mVisibleItemCount == 0) | |
return; | |
final int lastItemIndex = (mFirstVisibleItem + mVisibleItemCount) - 1; | |
if (getListView() != null && getListView().getCount() > 0 | |
&& lastItemIndex >= (mTotalItemCount - 1) - (mPageSize * 0.66)) { | |
Cursor cursor = (Cursor) getListAdapter().getItem(lastItemIndex - 1); | |
int pageIndex = mListAdapter.getPageIndex(cursor); | |
// if (mSyncService != null) { | |
// mSyncService.synchronize(UserSession.getInstance(getActivity()).getUserId(), UserSession.getInstance(getActivity()).getAccessToken(),pageIndex,20); | |
// } | |
mWorkoutListDelegate.onReachedEndOfContent(pageIndex, getActivity()); | |
} | |
} | |
@Override | |
public void onScrollStateChanged(AbsListView mView, int mScrollState) { | |
// TODO Auto-generated method stub | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class WorkoutListItem extends LinearLayout implements CellRenderer { | |
private static final int NOT_PAGINATED_PAGE_INDEX = 0; | |
public static class WorkoutViewHolder { | |
TextView workoutTitle; | |
ImageView workoutThumbnail; | |
TextView distanceValue; | |
TextView distanceLabel; | |
TextView caloriesValue; | |
TextView durationValue; | |
TextView dayOfWeek; | |
TextView monthDay; | |
} | |
private WorkoutViewHolder mViewHolder; | |
private String mWorkoutId; | |
private String mDateCreatedFormat = "MM/dd/yyyy"; | |
private DateFormat mDateCreatedFormatter; | |
private int mPageIndex; | |
private DateFormat dateFormat = new SimpleDateFormat("MMddyyHHmmss", | |
Locale.US); | |
public WorkoutListItem(Context context) { | |
this(context, null); | |
} | |
public WorkoutListItem(Context context, AttributeSet attrs) { | |
super(context, attrs); | |
mDateCreatedFormatter = new SimpleDateFormat(mDateCreatedFormat, Locale.US); | |
inflate(context, R.layout.list_view_dashboard_schedule_item, this); | |
setViewHolder(new WorkoutViewHolder()); | |
getViewHolder().workoutThumbnail = (ImageView) findViewById(R.id.workoutListItemThumbnail); | |
getViewHolder().workoutTitle = (TextView) findViewById(R.id.workoutTitleTextView); | |
getViewHolder().distanceValue = (TextView) findViewById(R.id.workoutDistanceLargeUnitTextView); | |
getViewHolder().distanceLabel = (TextView) findViewById(R.id.workoutDistanceLargeUnitLabelTextView); | |
getViewHolder().caloriesValue = (TextView) findViewById(R.id.workoutCaloriesTextView); | |
getViewHolder().durationValue = (TextView) findViewById(R.id.workoutDurationValueTextView); | |
getViewHolder().dayOfWeek = (TextView) findViewById(R.id.scheduleDayOfTheWeekTextView); | |
getViewHolder().monthDay = (TextView) findViewById(R.id.scheduleMonthDayTextView); | |
} | |
public WorkoutListItem(Context context, AttributeSet attrs, int defStyle) { | |
this(context, attrs); | |
} | |
@Override | |
public void renderRowCell(String workoutId, Date dateScheduled, double meters, double otherValue, int calories, String title, String workoutType) { | |
renderRowCell(workoutId, dateScheduled, meters, otherValue, calories, title, workoutType, null); | |
} | |
@Override | |
public void renderRowCell(String workoutId, Date dateScheduled, double meters, double otherValue, int calories, String title, String workoutType, String thumbnailURL) { | |
mWorkoutId = workoutId; | |
getViewHolder().workoutTitle.setText(title); | |
if (dateScheduled != null) { | |
String dayOfWeek = new SimpleDateFormat("EE").format(dateScheduled); | |
String monthDay = new SimpleDateFormat("MMM d").format(dateScheduled); | |
getViewHolder().dayOfWeek.setText(dayOfWeek); | |
getViewHolder().monthDay.setText(monthDay); | |
} else { | |
Log.e("WorkoutListItem", "date scheduled was null"); | |
} | |
getViewHolder().durationValue.setText(StringUtils.formatElapsedTime((long) otherValue)); | |
String distanceStr = String.format("%1$.2f", IfitPrefHelper.getDistanceValue(meters)); | |
getViewHolder().distanceValue.setText(distanceStr); | |
getViewHolder().distanceLabel.setText(IfitPrefHelper.getDistanceUnit()); | |
getViewHolder().caloriesValue.setText(String.valueOf(calories)); | |
ImageView workoutThumbnail = getViewHolder().workoutThumbnail; | |
if (workoutType.equals(WorkoutType.GOOGLE_WORKOUT)) { | |
workoutThumbnail.setBackgroundResource(R.color.dark_blue); | |
workoutThumbnail.setImageResource(R.drawable.ic_icn_dashboard_list_workout_map); | |
} else { | |
workoutThumbnail.setBackgroundResource(R.color.purple); | |
workoutThumbnail.setImageResource(R.drawable.ic_icn_dashboard_list_workout_manual); | |
} | |
} | |
public WorkoutViewHolder getViewHolder() { | |
return mViewHolder; | |
} | |
public void setViewHolder(WorkoutViewHolder viewHolder) { | |
mViewHolder = viewHolder; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment