• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
Dinosaurs and Androids: The Listview Evolution
 

Dinosaurs and Androids: The Listview Evolution

on

  • 636 views

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 ...

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.

Statistics

Views

Total Views
636
Views on SlideShare
636
Embed Views
0

Actions

Likes
1
Downloads
2
Comments
0

0 Embeds 0

No embeds

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

    Dinosaurs and Androids: The Listview Evolution Dinosaurs and Androids: The Listview Evolution Presentation Transcript

    • Dinosaurs and Androids:The ListView evolution Jorge J. Barroso / Fernando Cejas Tech Lead Android Core / Android Developer jbarroso@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
    • ScrapViews view ListView Sunday, November 10, 13 view view Adapter
    • /** * 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
    • /** * 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
    • Sunday, November 10, 13
    • Demo http://www.flickr.com/photos/farahmandnia/ 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
    • @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
    • Demo http://www.flickr.com/photos/farahmandnia/ Sunday, November 10, 13
    • 3 Optimization Where are we wasting time? http://www.flickr.com/photos/kabacchi/ Sunday, November 10, 13
    • ew i tV e G Adapter View LruCache <Bitmap> Bitmap Sunday, November 10, 13
    • Demo http://www.flickr.com/photos/farahmandnia/ Sunday, November 10, 13
    • WHY DOESN’T IT WORK? Sunday, November 10, 13
    • View dispatchDraw onDraw ViewGroup ViewGroup getDrawingCache onDraw onDraw dispatchDraw View Sunday, November 10, 13 onDraw
    • /** * 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
    • 4 Mixed List Remember one convertView for each type. Headers and Footers http://www.flickr.com/photos/keesey/ 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() */ @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
    • 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
    • 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
    • Demo http://www.flickr.com/photos/farahmandnia/ Sunday, November 10, 13
    • 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
    • /** * 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
    • /** * 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
    • Demo http://www.flickr.com/photos/farahmandnia/ Sunday, November 10, 13
    • Renders 6 Our solution http://www.flickr.com/photos/toughkidcst/ Sunday, November 10, 13
    • time overhead viewHolder GetView (RenderModel) Adapter Render Builder Abstract Render RenderModel Render Impl Sunday, November 10, 13
    • 7 Sunday, November 10, 13 DIFF IS FUN http://www.flickr.com/photos/josephwuorigami/
    • 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.com http://corporate.tuenti.com/en/dev/blog jbarroso@tuenti.com / fcejas@tuenti.com @flipper83 / @fernando_cejas Sunday, November 10, 13