Dinosaurs and Androids: The Listview Evolution

38
Dinosaurs and Androids:The ListView evolution Jorge J. Barroso / Fernando Cejas Tech Lead Android Core / Android Developer [email protected] / [email protected] @flipper83 / @fernando_cejas / @TuentiEng Code samples: https://github.com/android10/Inside_Android_ListView Sunday, November 10, 13

description

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.

Transcript of Dinosaurs and Androids: The Listview Evolution

Page 1: Dinosaurs and Androids: The Listview Evolution

Dinosaurs and Androids:The ListView evolutionJorge J. Barroso / Fernando CejasTech Lead Android Core / Android [email protected] / [email protected]@flipper83 / @fernando_cejas / @TuentiEng

Code samples:https://github.com/android10/Inside_Android_ListView

Sunday, November 10, 13

Page 2: Dinosaurs and Androids: The Listview Evolution

Sunday, November 10, 13

Page 3: Dinosaurs and Androids: The Listview Evolution

1 Morphology

Component or Layout?

Sunday, November 10, 13

Page 4: Dinosaurs and Androids: The Listview Evolution

public abstract class AbsListView extends AdapterView<ListAdapter>

public abstract class AdapterView<T extends Adapter> extends

ViewGroup

Sunday, November 10, 13

Page 5: Dinosaurs and Androids: The Listview Evolution

ListView Adapter

view view view

ScrapViews

Sunday, November 10, 13

Page 6: Dinosaurs and Androids: The Listview Evolution

/** * 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;} ListView

Sunday, November 10, 13

Page 7: Dinosaurs and Androids: The Listview Evolution

/** * 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);

...

} AbsListViewSunday, November 10, 13

Page 8: Dinosaurs and Androids: The Listview Evolution

Sunday, November 10, 13

Page 9: Dinosaurs and Androids: The Listview Evolution

http://www.flickr.com/photos/farahmandnia/

Demo

Sunday, November 10, 13

Page 10: Dinosaurs and Androids: The Listview Evolution

2 ViewHolders

Is this really necessary?

http://www.flickr.com/photos/rojam/Sunday, November 10, 13

Page 12: Dinosaurs and Androids: The Listview Evolution

@Overrideprotected 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;}

ViewGroupSunday, November 10, 13

Page 13: Dinosaurs and Androids: The Listview Evolution

http://www.flickr.com/photos/farahmandnia/

Demo

Sunday, November 10, 13

Page 14: Dinosaurs and Androids: The Listview Evolution

3 Optimization

Where are we wasting time?

http://www.flickr.com/photos/kabacchi/Sunday, November 10, 13

Page 15: Dinosaurs and Androids: The Listview Evolution

Adapter

View

LruCache<Bitmap>

Bitmap

GetView

Sunday, November 10, 13

Page 16: Dinosaurs and Androids: The Listview Evolution

http://www.flickr.com/photos/farahmandnia/

Demo

Sunday, November 10, 13

Page 17: Dinosaurs and Androids: The Listview Evolution

WHYDOESN’T

ITWORK?

Sunday, November 10, 13

Page 18: Dinosaurs and Androids: The Listview Evolution

View

ViewGroup

ViewGroup

View

onDraw

onDraw

onDraw

onDraw

dispatchDraw

dispatchDrawgetDrawingCache

Sunday, November 10, 13

Page 19: Dinosaurs and Androids: The Listview Evolution

/** * 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); } }} View

Sunday, November 10, 13

Page 20: Dinosaurs and Androids: The Listview Evolution

Remember one convertView for each type. Headers and Footers

http://www.flickr.com/photos/keesey/

4 Mixed List

Sunday, November 10, 13

Page 21: Dinosaurs and Androids: The Listview Evolution

/** * 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() */@Overridepublic 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; }...}

ListViewSunday, November 10, 13

Page 22: Dinosaurs and Androids: The Listview Evolution

HeaderViewListAdapter

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;}

Sunday, November 10, 13

Page 23: Dinosaurs and Androids: The Listview Evolution

ListView - addFooterView

Sunday, November 10, 13

Page 24: Dinosaurs and Androids: The Listview Evolution

5 Animation

Let’s animate items...

Sunday, November 10, 13

Page 25: Dinosaurs and Androids: The Listview Evolution

- View Animation- Drawable Animation- Property Animation

Sunday, November 10, 13

Page 26: Dinosaurs and Androids: The Listview Evolution

http://www.flickr.com/photos/farahmandnia/

Demo

Sunday, November 10, 13

Page 27: Dinosaurs and Androids: The Listview Evolution

AbsListView

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;}

Sunday, November 10, 13

Page 28: Dinosaurs and Androids: The Listview Evolution

View

/** * 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); } } }}

Sunday, November 10, 13

Page 29: Dinosaurs and Androids: The Listview Evolution

ViewPropertyAnimator

/** * 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) {...}

Sunday, November 10, 13

Page 30: Dinosaurs and Androids: The Listview Evolution

http://www.flickr.com/photos/farahmandnia/

Demo

Sunday, November 10, 13

Page 31: Dinosaurs and Androids: The Listview Evolution

6Renders

http://www.flickr.com/photos/toughkidcst/

Our solution

Sunday, November 10, 13

Page 32: Dinosaurs and Androids: The Listview Evolution

Adapter

AbstractRender

RenderBuilder

RenderImpl

GetView(RenderModel)

RenderModel

time overheadviewHolder

Sunday, November 10, 13

Page 33: Dinosaurs and Androids: The Listview Evolution

7 DIFF IS FUN http://www.flickr.com/photos/josephwuorigami/Sunday, November 10, 13

Page 34: Dinosaurs and Androids: The Listview Evolution

Did someone notice it?

Sunday, November 10, 13

Page 35: Dinosaurs and Androids: The Listview Evolution

Sunday, November 10, 13

Page 36: Dinosaurs and Androids: The Listview Evolution

Test Coverage ^_^

Sunday, November 10, 13

Page 37: Dinosaurs and Androids: The Listview Evolution

They are also human!

Sunday, November 10, 13