@Override public int getSectionForPosition(int position) { SectionAndItem<T> sectionAndItem = getSectionAndItem(position); if (sectionAndItem != null && sectionAndItem.getType() != SectionAndItem.Type.ACTIVITY_CIRCLE) { return Math.max( 0, Math.min(sectionKeys.indexOf(sectionAndItem.sectionKey), sectionKeys.size() - 1)); } return 0; }
@Override public int getPositionForSection(int section) { if (displaySections) { section = Math.max(0, Math.min(section, sectionKeys.size() - 1)); if (section < sectionKeys.size()) { return getPosition(sectionKeys.get(section), null); } } return 0; }
private void rebuildSections() { sectionKeys = new ArrayList<String>(); graphObjectsBySection = new HashMap<String, ArrayList<T>>(); graphObjectsById = new HashMap<String, T>(); displaySections = false; if (cursor == null || cursor.getCount() == 0) { return; } int objectsAdded = 0; cursor.moveToFirst(); do { T graphObject = cursor.getGraphObject(); if (!filterIncludesItem(graphObject)) { continue; } objectsAdded++; String sectionKeyOfItem = getSectionKeyOfGraphObject(graphObject); if (!graphObjectsBySection.containsKey(sectionKeyOfItem)) { sectionKeys.add(sectionKeyOfItem); graphObjectsBySection.put(sectionKeyOfItem, new ArrayList<T>()); } List<T> section = graphObjectsBySection.get(sectionKeyOfItem); section.add(graphObject); graphObjectsById.put(getIdOfGraphObject(graphObject), graphObject); } while (cursor.moveToNext()); if (sortFields != null) { final Collator collator = Collator.getInstance(); for (List<T> section : graphObjectsBySection.values()) { Collections.sort( section, new Comparator<GraphObject>() { @Override public int compare(GraphObject a, GraphObject b) { return compareGraphObjects(a, b, sortFields, collator); } }); } } Collections.sort(sectionKeys, Collator.getInstance()); displaySections = sectionKeys.size() > 1 && objectsAdded > DISPLAY_SECTIONS_THRESHOLD; }
public void prioritizeViewRange(int firstVisibleItem, int lastVisibleItem, int prefetchBuffer) { if ((lastVisibleItem < firstVisibleItem) || (sectionKeys.size() == 0)) { return; } // We want to prioritize requests for items which are visible but do not have pictures // loaded yet. We also want to pre-fetch pictures for items which are not yet visible // but are within a buffer on either side of the visible items, on the assumption that // they will be visible soon. For these latter items, we'll store the images in memory // in the hopes we can immediately populate their image view when needed. // Prioritize the requests in reverse order since each call to prioritizeRequest will just // move it to the front of the queue. And we want the earliest ones in the range to be at // the front of the queue, so all else being equal, the list will appear to populate from // the top down. for (int i = lastVisibleItem; i >= 0; i--) { SectionAndItem<T> sectionAndItem = getSectionAndItem(i); if (sectionAndItem.graphObject != null) { String id = getIdOfGraphObject(sectionAndItem.graphObject); ImageRequest request = pendingRequests.get(id); if (request != null) { ImageDownloader.prioritizeRequest(request); } } } // For items which are not visible, but within the buffer on either side, we want to // fetch those items and store them in a small in-memory cache of bitmaps. int start = Math.max(0, firstVisibleItem - prefetchBuffer); int end = Math.min(lastVisibleItem + prefetchBuffer, getCount() - 1); ArrayList<T> graphObjectsToPrefetchPicturesFor = new ArrayList<T>(); // Add the IDs before and after the visible range. for (int i = start; i < firstVisibleItem; ++i) { SectionAndItem<T> sectionAndItem = getSectionAndItem(i); if (sectionAndItem.graphObject != null) { graphObjectsToPrefetchPicturesFor.add(sectionAndItem.graphObject); } } for (int i = lastVisibleItem + 1; i <= end; ++i) { SectionAndItem<T> sectionAndItem = getSectionAndItem(i); if (sectionAndItem.graphObject != null) { graphObjectsToPrefetchPicturesFor.add(sectionAndItem.graphObject); } } for (T graphObject : graphObjectsToPrefetchPicturesFor) { URI uri = getPictureUriOfGraphObject(graphObject); final String id = getIdOfGraphObject(graphObject); // This URL already have been requested for pre-fetching, but we want to act in an LRU manner, // so move // it to the end of the list regardless. boolean alreadyPrefetching = prefetchedProfilePictureIds.remove(id); prefetchedProfilePictureIds.add(id); // If we've already requested it for pre-fetching, no need to do so again. if (!alreadyPrefetching) { downloadProfilePicture(id, uri, null); } } }
@Override public Object[] getSections() { if (displaySections) { return sectionKeys.toArray(); } else { return new Object[0]; } }
@Override public int getCount() { if (sectionKeys.size() == 0) { return 0; } // If we are not displaying sections, we don't display a header; otherwise, we have one header // per item in // addition to the actual items. int count = (displaySections) ? sectionKeys.size() : 0; for (List<T> section : graphObjectsBySection.values()) { count += section.size(); } // If we should show a cell with an activity circle indicating more data is coming, add it to // the count. if (shouldShowActivityCircleCell()) { ++count; } return count; }
SectionAndItem<T> getSectionAndItem(int position) { if (sectionKeys.size() == 0) { return null; } String sectionKey = null; T graphObject = null; if (!displaySections) { sectionKey = sectionKeys.get(0); List<T> section = graphObjectsBySection.get(sectionKey); if (position >= 0 && position < section.size()) { graphObject = graphObjectsBySection.get(sectionKey).get(position); } else { // We are off the end; we must be adding an activity circle to indicate more data is coming. assert dataNeededListener != null && cursor.areMoreObjectsAvailable(); // We return null for both to indicate this. return new SectionAndItem<T>(null, null); } } else { // Count through the sections; the "0" position in each section is the header. We decrement // position each time we skip forward a certain number of elements, including the header. for (String key : sectionKeys) { // Decrement if we skip over the header if (position-- == 0) { sectionKey = key; break; } List<T> section = graphObjectsBySection.get(key); if (position < section.size()) { // The position is somewhere in this section. Get the corresponding graph object. sectionKey = key; graphObject = section.get(position); break; } // Decrement by as many items as we skipped over position -= section.size(); } } if (sectionKey != null) { // Note: graphObject will be null if this represents a section header. return new SectionAndItem<T>(sectionKey, graphObject); } else { throw new IndexOutOfBoundsException("position"); } }
@Override public boolean isEmpty() { // We'll never populate sectionKeys unless we have at least one object. return sectionKeys.size() == 0; }