dinosaurs and androids: the listview evolution
Post on 10-May-2015
1.021 Views
Preview:
DESCRIPTION
TRANSCRIPT
Dinosaurs and Androids:The ListView evolutionJorge J. Barroso / Fernando CejasTech Lead Android Core / Android Developerjbarroso@tuenti.com / fcejas@tuenti.com@flipper83 / @fernando_cejas / @TuentiEng
Code samples:https://github.com/android10/Inside_Android_ListView
Sunday, November 10, 13
Sunday, November 10, 13
1 Morphology
Component or Layout?
Sunday, November 10, 13
public abstract class AbsListView extends AdapterView<ListAdapter>
public abstract class AdapterView<T extends Adapter> extends
ViewGroup
Sunday, November 10, 13
ListView Adapter
view view view
ScrapViews
Sunday, November 10, 13
/** * 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
/** * 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
Sunday, November 10, 13
http://www.flickr.com/photos/farahmandnia/
Demo
Sunday, November 10, 13
2 ViewHolders
Is this really necessary?
http://www.flickr.com/photos/rojam/Sunday, November 10, 13
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
@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
http://www.flickr.com/photos/farahmandnia/
Demo
Sunday, November 10, 13
3 Optimization
Where are we wasting time?
http://www.flickr.com/photos/kabacchi/Sunday, November 10, 13
Adapter
View
LruCache<Bitmap>
Bitmap
GetView
Sunday, November 10, 13
http://www.flickr.com/photos/farahmandnia/
Demo
Sunday, November 10, 13
WHYDOESN’T
ITWORK?
Sunday, November 10, 13
View
ViewGroup
ViewGroup
View
onDraw
onDraw
onDraw
onDraw
dispatchDraw
dispatchDrawgetDrawingCache
Sunday, November 10, 13
/** * 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
Remember one convertView for each type. Headers and Footers
http://www.flickr.com/photos/keesey/
4 Mixed List
Sunday, November 10, 13
/** * 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
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
ListView - addFooterView
Sunday, November 10, 13
5 Animation
Let’s animate items...
Sunday, November 10, 13
- View Animation- Drawable Animation- Property Animation
Sunday, November 10, 13
http://www.flickr.com/photos/farahmandnia/
Demo
Sunday, November 10, 13
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
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
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
http://www.flickr.com/photos/farahmandnia/
Demo
Sunday, November 10, 13
6Renders
http://www.flickr.com/photos/toughkidcst/
Our solution
Sunday, November 10, 13
Adapter
AbstractRender
RenderBuilder
RenderImpl
GetView(RenderModel)
RenderModel
time overheadviewHolder
Sunday, November 10, 13
7 DIFF IS FUN http://www.flickr.com/photos/josephwuorigami/Sunday, November 10, 13
Did someone notice it?
Sunday, November 10, 13
Sunday, November 10, 13
Test Coverage ^_^
Sunday, November 10, 13
They are also human!
Sunday, November 10, 13
http://jobs.tuenti.comhttp://corporate.tuenti.com/en/dev/blogjbarroso@tuenti.com / fcejas@tuenti.com@flipper83 / @fernando_cejas
Sunday, November 10, 13
top related