Dinosaurs, like all living things, evolved, slowly and gradually, from previously existing creatures. The same happened with Listviews, they also evolved from pre-existing ancestors. Dinosaurs didn’t spring suddenly into existence two hundred million years ago, huge, toothy, and hungry for grub. Listviews on Android appeared since the beginning and they have been changing over the time. As you know, ListView is a fundamental component in Android, one of the most widely used widgets and also the most complex one.
In this talk, Jorge Barroso and Fernando Cejas will explain the evolution of this widget from previous versions of Android (based on its source code), mistakes that have been made in its implementation, giving examples and showing tips on how should be used when developing cool Android applications. Also, stuff like features, optimization, quirks and limitations will take place in this talk.
4. public abstract class AbsListView
extends AdapterView<ListAdapter>
public abstract class AdapterView
<T extends Adapter> extends
ViewGroup
Sunday, November 10, 13
6. /**
* Obtain the view and add it to our list of children. The view can be made
* fresh, converted from an unused view, or used as is if it was in the
* recycle bin.
*
* @param position Logical position in the list
* @param y Top or bottom edge of the view to add
* @param flow If flow is true, align top edge to y. If false, align bottom
*
edge to y.
* @param childrenLeft Left edge where children should be positioned
* @param selected Is this position selected?
* @return View that was added
*/
private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
boolean selected) {
View child;
if (!mDataChanged) {
// Try to use an existing view for this position
child = mRecycler.getActiveView(position);
if (child != null) {
// Found it -- we're using an existing child
// This just needs to be positioned
setupChild(child, position, y, flow, childrenLeft, selected, true);
return child;
}
}
// Make a new view for this position, or convert an unused view if possible
child = obtainView(position, mIsScrap);
// This needs to be positioned and measured
setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
return child;
}
Sunday, November 10, 13
ListView
7. /**
* Get a view and have it show the data associated with the specified
* position. This is called when we have already discovered that the view is
* not available for reuse in the recycle bin. The only choices left are
* converting an old view or making a new one.
*
* @param position The position to display
* @param isScrap Array of at least 1 boolean, the first entry will become true if
*
the returned view was taken from the scrap heap, false if otherwise.
*
* @return A view displaying the data associated with the specified position
*/
View obtainView(int position, boolean[] isScrap) {
isScrap[0] = false;
View scrapView;
scrapView = mRecycler.getTransientStateView(position);
if (scrapView != null) {
return scrapView;
}
scrapView = mRecycler.getScrapView(position);
View child;
if (scrapView != null) {
child = mAdapter.getView(position, scrapView, this);
...
}
Sunday, November 10, 13
AbsListView
11. Your code might call findViewById() frequently during the
scrolling of ListView, which can slow down performance. Even
when the Adapter returns an inflated view for recycling, you
still need to look up the elements and update them. A way
around repeated use of findViewById() is to use the "view
holder" design pattern.
Sunday, November 10, 13
12. @Override
protected View findViewTraversal(int id) {
if (id == mID) {
return this;
}
final View[] where = mChildren;
final int len = mChildrenCount;
for (int i = 0; i < len; i++) {
View v = where[i];
if ((v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) {
v = v.findViewById(id);
if (v != null) {
return v;
}
}
}
return null;
}
ViewGroup
Sunday, November 10, 13
19. /**
* This is where the invalidate() work actually happens. A full invalidate()
* causes the drawing cache to be invalidated, but this function can be called with
* invalidateCache set to false to skip that invalidation step for cases that do not
* need it (for example, a component that remains at the same dimensions with the same
* content).
*
* @param invalidateCache Whether the drawing cache for this view should be invalidated as
* well. This is usually true for a full invalidate, but may be set to false if the
* View's contents or dimensions have not changed.
*/
void invalidate(boolean invalidateCache) {
if (skipInvalidate()) {
return;
}
if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS) ||
(invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) ||
(mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED || isOpaque() != mLastIsOpaque) {
mLastIsOpaque = isOpaque();
mPrivateFlags &= ~PFLAG_DRAWN;
mPrivateFlags |= PFLAG_DIRTY;
if (invalidateCache) {
mPrivateFlags |= PFLAG_INVALIDATED;
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
}
final AttachInfo ai = mAttachInfo;
final ViewParent p = mParent;
//noinspection PointlessBooleanExpression,ConstantConditions
if (!HardwareRenderer.RENDER_DIRTY_REGIONS) {
if (p != null && ai != null && ai.mHardwareAccelerated) {
// fast-track for GL-enabled applications; just invalidate the whole hierarchy
// with a null dirty rect, which tells the ViewAncestor to redraw everything
p.invalidateChild(this, null);
return;
}
}
}
}
if (p != null && ai != null) {
final Rect r = ai.mTmpInvalRect;
r.set(0, 0, mRight - mLeft, mBottom - mTop);
// Don't call invalidate -- we don't want to internally scroll
// our own bounds
p.invalidateChild(this, r);
}
Sunday, November 10, 13
View
20. 4
Mixed List
Remember one convertView for each type. Headers and Footers
http://www.flickr.com/photos/keesey/
Sunday, November 10, 13
21. /**
* Sets the data behind this ListView.
*
* The adapter passed to this method may be wrapped by a {@link
WrapperListAdapter},
* depending on the ListView features currently in use. For instance, adding
* headers and/or footers will cause the adapter to be wrapped.
*
* @param adapter The ListAdapter which is responsible for maintaining the
*
data backing this list and for producing a view to represent an
*
item in that data set.
*
* @see #getAdapter()
*/
@Override
public void setAdapter(ListAdapter adapter) {
if (mAdapter != null && mDataSetObserver != null) {
mAdapter.unregisterDataSetObserver(mDataSetObserver);
}
resetList();
mRecycler.clear();
if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos,
adapter);
} else {
mAdapter = adapter;
}
...
}
ListView
Sunday, November 10, 13
22. public View getView(int position, View convertView, ViewGroup parent) {
// Header (negative positions will throw an ArrayIndexOutOfBoundsException)
int numHeaders = getHeadersCount();
if (position < numHeaders) {
return mHeaderViewInfos.get(position).view;
}
// Adapter
final int adjPosition = position - numHeaders;
int adapterCount = 0;
if (mAdapter != null) {
adapterCount = mAdapter.getCount();
if (adjPosition < adapterCount) {
return mAdapter.getView(adjPosition, convertView, parent);
}
}
}
// Footer (off-limits positions will throw an ArrayIndexOutOfBoundsException)
return mFooterViewInfos.get(adjPosition - adapterCount).view;
HeaderViewListAdapter
Sunday, November 10, 13
27. lv_list.setRecyclerListener(new AbsListView.RecyclerListener() {
@Override
public void onMovedToScrapHeap(View view) {
}
});
/**
* Sets the recycler listener to be notified whenever a View is set aside in
* the recycler for later reuse. This listener can be used to free resources
* associated to the View.
*
* @param listener The recycler listener to be notified of views set aside
*
in the recycler.
*
* @see android.widget.AbsListView.RecycleBin
* @see android.widget.AbsListView.RecyclerListener
*/
public void setRecyclerListener(RecyclerListener listener) {
mRecycler.mRecyclerListener = listener;
}
AbsListView
Sunday, November 10, 13
28. /**
* Set whether this view is currently tracking transient state that the
* framework should attempt to preserve when possible. This flag is reference counted,
* so every call to setHasTransientState(true) should be paired with a later call
* to setHasTransientState(false).
*
* <p>A view with transient state cannot be trivially rebound from an external
* data source, such as an adapter binding item views in a list. This may be
* because the view is performing an animation, tracking user selection
* of content, or similar.</p>
*
* @param hasTransientState true if this view has transient state
*/
public void setHasTransientState(boolean hasTransientState) {
mTransientStateCount = hasTransientState ? mTransientStateCount + 1 :
mTransientStateCount - 1;
if (mTransientStateCount < 0) {
mTransientStateCount = 0;
Log.e(VIEW_LOG_TAG, "hasTransientState decremented below 0: " +
"unmatched pair of setHasTransientState calls");
}
if ((hasTransientState && mTransientStateCount == 1) ||
(!hasTransientState && mTransientStateCount == 0)) {
// update flag if we've just incremented up from 0 or decremented down to 0
mPrivateFlags2 = (mPrivateFlags2 & ~PFLAG2_HAS_TRANSIENT_STATE) |
(hasTransientState ? PFLAG2_HAS_TRANSIENT_STATE : 0);
if (mParent != null) {
try {
mParent.childHasTransientStateChanged(this, hasTransientState);
} catch (AbstractMethodError e) {
Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() +
" does not fully implement ViewParent", e);
}
}
}
}
View
Sunday, November 10, 13
29. /**
* Starts the underlying Animator for a set of properties. We use a single animator that
* simply runs from 0 to 1, and then use that fractional value to set each property
* value accordingly.
*/
private void startAnimation() {
mView.setHasTransientState(true);
ValueAnimator animator = ValueAnimator.ofFloat(1.0f);
ArrayList<NameValuesHolder> nameValueList =
(ArrayList<NameValuesHolder>) mPendingAnimations.clone();
mPendingAnimations.clear();
int propertyMask = 0;
int propertyCount = nameValueList.size();
for (int i = 0; i < propertyCount; ++i) {
NameValuesHolder nameValuesHolder = nameValueList.get(i);
propertyMask |= nameValuesHolder.mNameConstant;
}
mAnimatorMap.put(animator, new PropertyBundle(propertyMask, nameValueList));
if (mPendingSetupAction != null) {
mAnimatorSetupMap.put(animator, mPendingSetupAction);
mPendingSetupAction = null;
}
if (mPendingCleanupAction != null) {
...
}
ViewPropertyAnimator
Sunday, November 10, 13