Pro RecyclerView

Pro RecyclerView

Pro RecyclerView talk at 360andev / Denver / 2016

46fbc3248107250cb09c0d69919b62ad?s=128

Yigit Boyar

July 29, 2016
Tweet

Transcript

  1. Pro RecyclerView Yiğit Boyar / Google

  2. Define: Pro • Know the past and today • Use

    it properly & efficiently • (a.k.a best practices)
  3. Part I Know The Past and Today

  4. ListView • Repeated & Consistent Content

  5. ListView • or sometimes, not so consistent • A lot

    of data, not enough memory
  6. ListView • Smoke and mirrors! • If it looks right,

    it is right • Only create & layout the views user can see • Lay out more as needed, re-use!
  7. Complexity Overdraft • A lot of long tail, one -

    off features • Undefined behavior when APIs are mixed • Undefined behavior = undefined API
  8. Duplicate Features • Focused View vs Selected Item • Item

    Click Listener vs View Click Listener • setItemsCanFocus • why not?
  9. Animations

  10. ¯\_(ϑ)_/¯

  11. More Complex Layouts • GridView • Horizontal List View •

    StaggeredGridView
  12. Reboot • Elevates best practices • ViewHolder • De-couple via

    well defined components • Recycler • Do less • Use framework focus • Smart adapters
  13. RecyclerView Components Layout Manager Adapter Item Animator I position the

    views I provide the views I animate the views Recycler View
  14. Part II Best Practices

  15. View::requestLayout

  16. View::requestLayout requestLayout ViewGroup ViewGroup View ViewGroup view ViewGroup View ViewGroup

    View
  17. View::requestLayout ViewGroup ViewGroup View ViewGroup view ViewGroup View ViewGroup View

    nextFrame measure
  18. View::requestLayout ViewGroup ViewGroup View ViewGroup view ViewGroup View ViewGroup View

    layout
  19. View::requestLayout onBindViewHolder(ViewHolder vh, int position) { .... imageLoader.loadImage(vh.image, user.profileUrl, R.drawable.placeHolder);

    }
  20. imageView.setImageBitmap(bitmap)

  21. imageView.setImageBitmap(bitmap) imageView.requestLayout();

  22. imageView.setImageBitmap(bitmap) imageView.requestLayout(); itemView.requestLayout();

  23. imageView.setImageBitmap(bitmap) imageView.requestLayout(); itemView.requestLayout(); recyclerView.requestLayout(); unless setHasFixedSize == true

  24. ImageView.java Since 2011 void setImageDrawable(Drawable drawable){ if (mDrawable != drawable)

    { int oldWidth = mDrawableWidth; int oldHeight = mDrawableHeight; updateDrawable(drawable); if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) { requestLayout(); } invalidate(); } }
  25. TextView.java ¯\_(ϑ)_/¯

  26. How Do I Know?

  27. RecyclerView.java

  28. Resizing Items

  29. imageLoader.load( viewHolder.imageView); cache miss

  30. desired

  31. reality: bad item height Clock in the wrong column

  32. AspectRatioImageView.java private float mAspectRatio; @Override protected void onMeasure(int wSpec, int

    hSpec) { int width = MeasureSpec.getSize(wSpec); int height = (int) (width * mAspectRatio); setMeasuredDimension(width, height); }
  33. Bad API vs Good API {
 "user" : {
 "name"

    : "Michael",
 }
 } "photoUrl" : "https://..."

  34. Bad API vs Good API {
 "user" : {
 "name"

    : "Michael",
 }
 } "photoUrl" : {
 "width" : 300,
 "height" : 500,
 "url" : "https://...",
 "palette" : {}
 }

  35. DataBinding void onBindViewHolder(ViewHolder vh, int pos) { vh.binding.setItem(items.get(pos)); vh.binding.executePendingBindings(); }

  36. Data Updates

  37. updated list arrives void onFetched(List<News> news) { myAdapter.setNews(news); myAdapter.notifyDataSetChanged(); }

  38. None
  39. onBind onBind onBind onBind onBind create new VH

  40. long getItemId(int position) { news.get(position).getId(); }

  41. onBind unnecessary onBind, measure, layout

  42. SortedList SortedList<Item> mySortedList = new SortedList<Item>(Item.class, new SortedListAdapterCallback<Item>(myAdapter) { @Override

    public int compare(Item item1, Item item2) { return item1.id - item2.id; } @Override public boolean areItemsTheSame(Item item1, Item item2) { return item1.id == item2.id; } @Override public boolean areContentsTheSame(Item oldItem, Item newItem) { return oldItem.text.equals(newItem.text); } });
  43. SortedList void onFetched(List<News> newsList) { mySortedList.addAll(newsList); }

  44. Sort By Votes

  45. SortedList SortedList<Item> mySortedList = new SortedList<Item>(Item.class, new SortedListAdapterCallback<Item>(myAdapter) { @Override

    public int compare(Item item1, Item item2) { return item1.id - item2.id; } … }); update compare method
  46. SortedList SortedList<Item> mySortedList = new SortedList<Item>(Item.class, new SortedListAdapterCallback<Item>(myAdapter) { @Override

    public int compare(Item item1, Item item2) { return item2.votes - item1.votes; } … });
  47. SortedList Internals Article Votes Sharks To Finals 10 add(“To Rx…”,

    9) 7 > 9 ? Shake The Phone 8 San Francisco Dating 7 To Rx or Not To Rx 5 Do Not Sleep 3
  48. SortedList Internals Article Votes Sharks To Finals 10 add(“To Rx…”,

    9) 10 > 12 ? Shake The Phone 8 San Francisco Dating 7 To Rx or Not To Rx 5 Do Not Sleep 3
  49. SortedList Internals Article Votes Sharks To Finals 10 add(“To Rx…”,

    9) 8 > 9 ? Shake The Phone 8 San Francisco Dating 7 To Rx or Not To Rx 5 Do Not Sleep 3
  50. SortedList Internals Article Votes Sharks To Finals 10 Shake The

    Phone 8 San Francisco Dating 7 To Rx or Not To Rx 5 Do Not Sleep 3 To Rx or Not To Rx 9
  51. SortedList::updateItemAt Map<Integer, Item> items; // item id -> Item void

    insert(Item item) { Item existing = items.put(item.id, item); if (existing == null) { mySortedList.add(item); } else { int ind = mySortedList.indexOf(existing); mySortedList.updateItemAt(ind, item); } }
  52. Introducing…

  53. DiffUtil

  54. DiffUtil DiffResult result = DiffUtil.calculateDiff(new MyCallback(oldList, newList)); myAdapter.setItems(newList); result.dispatchUpdatesTo(myAdapter);

  55. DiffUtil.Callback class MyCallback extends DiffUtil.Callback { @Override public int getOldListSize()

    { return mOld.size(); } @Override public int getNewListSize() { return mNew.size(); } @Override public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { return mOld.get(oldItemPosition).id == mNew.get(newItemPosition).id; } @Override public boolean areContentsTheSame(int oldItemPosition, int newItemPosition){ return mOld.get(oldItemPosition).equals(mNew.get(newItemPosition)); } }
  56. DiffUtil.Callback @Override @Nullable public Object getChangePayload(int oldItemPosition, int newItemPosition) {

    Item oldItem = mOldItems.get(oldItemPosition); Item newItem = mNewItems.get(newItemPosition); if (oldItem.votes != newItem.votes) { return VOTES; } return null; }
  57. Adapter::onBindViewHolder @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position,List<Object> payloads) {

    if (payloads.isEmpty()) { onBindViewHolder(holder, position); } else { if (payloads.contains(VOTES)) { holder.voteCount.setText("" + item.votes); } } }
  58. DiffUtil Available in 24.2

  59. Resource Management

  60. ViewHolder Lifecycle onCreate onViewAttachedToWindow onViewDetachedFromWindow onRecycled onBindViewHolder the same item

    stop playing video start playing video
  61. ViewHolder Lifecycle onCreate onViewAttachedToWindow onViewDetachedFromWindow onRecycled onBindViewHolder might be a

    different item release video resources acquire video resources
  62. RecyclerView is Async

  63. RecyclerView Update Cycle on frame handle pending changes

  64. RecyclerView Update Cycle on frame handle pending changes scroll to

    position notify data change smooth scroll all structural changes postponed until next layout
  65. RecyclerView::scrollToPosition recyclerView.scrollToPosition(15); int x = layoutManager.getFirstVisibleItemPosition(); x == 15 ?

  66. RecyclerView::scrollToPosition recyclerView.scrollToPosition(15); int x = layoutManager.getFirstVisibleItemPosition(); x == 15 ?

  67. RecyclerView::scrollToPosition onInit void onCreate(SavedInstanceState state) { .... mRecyclerView.scrollToPosition(selectedPosition); mRecyclerView.setAdapter(myAdapter); }

    will it work? ✓
  68. RecyclerView::scrollToPosition onInit void onCreate(SavedInstanceState state) { .... mRecyclerView.scrollToPosition(selectedPosition); model.loadItems(items ->

    mRecyclerView.setAdapter( new ItemAdapter(items)); ); } will it work? ✓
  69. RecyclerView::scrollToPosition onInit void onCreate(SavedInstanceState state) { .... mRecyclerView.scrollToPosition(selectedPosition); model.loadItems(items ->

    mRecyclerView.setAdapter( new ItemAdapter(items)); ); } will it work? ✓ No layout happens until both Adapter and LayoutManager are set.
  70. ViewHolder ++

  71. Sad ViewHolder class ViewHolder { TextView title; TextView body; ImageView

    icon; } void onBindViewHolder(ViewHolder vh, int pos) { Item item = items.get(pos); title.setText(item.getTitle()); body.setText(item.getBody()); imageLoader.loadImage(icon, item.IconUrl()); }
  72. Happy ViewHolder class ViewHolder { ... public bindTo(Item item, ImageLoader

    imageLoader) { title.setText(item.getTitle()); body.setText(item.getBody()); imageLoader.loadImage(icon, item.IconUrl()); } } void onBindViewHolder(ViewHolder vh, int position) { vh.bindTo(items.get(position), mImageLoader); }
  73. View Types

  74. Adapter::getItemViewType @Override public int getItemViewType(int position) { User user =

    mItems.get(position); if (user.isPremium()) { return TYPE_PREMIUM; } return TYPE_BASIC; }
  75. Adapter::onCreateViewHolder public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view;

    switch (viewType) { case TYPE_PREMIUM: view = mLayoutInflater.inflate(R.layout.premium, parent, false); break; case TYPE_BASIC: view = mLayoutInflater.inflate(R.layout.basic, parent, false); break; } return new UserViewHolder(view); }
  76. Adapter::getItemViewType @Override public int getItemViewType(int position) { User user =

    mItems.get(position); if (user.isPremium()) { return TYPE_PREMIUM; } return TYPE_BASIC; }
  77. Adapter::getItemViewType @Override public int getItemViewType(int position) { User user =

    mItems.get(position); if (user.isPremium()) { return R.layout.premium; } return R.layout.basic; }
  78. Adapter::onCreateViewHolder public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view

    = mLayoutInflater.inflate(viewType, parent, false); return new UserViewHolder(view); } it is the R.layout id ;)
  79. Click Listeners

  80. Where Is My Click Listener ? https://commons.wikimedia.org/wiki/File:Trollface.png

  81. There Is Your Click Listener http://stackoverflow.com/questions/24885223/why-doesnt-recyclerview-have-onitemclicklistener-and-how-recyclerview-is-dif

  82. There Is Your Click Listener ItemClickListener prevents children clicks http://stackoverflow.com/questions/24885223/why-doesnt-recyclerview-have-onitemclicklistener-and-how-recyclerview-is-dif

  83. ItemClickListener class MyAdapter { ItemClickListener itemClickListener; public onCreateViewHolder(...) { final

    ViewHolder vh = ....; myViewHolder.itemView.setOnClickListener({ int pos = vh.getAdapterPosition(); if (pos != NO_POSITION) { itemClickListener.onClick(items[pos]); } }); } } ItemClick NOT View.Click
  84. Adapter Position vs Layout Position

  85. Positions: Adapter vs Layout London İstanbul San Francisco New York

    Barcelona Paris AP LP 0 0 1 1 2 2 3 3 4 4 5 5 move(2,5);
  86. Positions: Adapter vs Layout London İstanbul San Francisco New York

    Barcelona Paris move(2,5); AP LP 0 0 1 1 5 2 2 3 3 4 4 5
  87. Positions: Adapter vs Layout London İstanbul New York Barcelona Paris

    San Francisco onLayout AP LP 0 0 1 1 2 2 3 3 4 4 5 5
  88. Things That Makes Me Sad :(

  89. MySuperSolidRecyclerView class MySuperSolidRecyclerView extends RecyclerView { public void onLayout() {

    try { super.onLayout(); } catch (Throwable t) { // ignore } } }
  90. MySuperSolidRecyclerView class MySuperSolidRecyclerView extends RecyclerView { public void onLayout() {

    try { super.onLayout(); } catch (Throwable t) { // ignore } } } NO NO NO NO fix your crash! (or report a bug :) )
  91. Old Habits Die Hard public void onBindViewHolder(ViewHolder vh, final int

    position) { vh.likeButton.setOnClickListener = new OnClickListener() { items[position].liked = true; notifyItemChanged(position); } }
  92. Old Habits Die Hard public void onBindViewHolder(ViewHolder vh, final int

    position) { vh.likeButton.setOnClickListener = new OnClickListener() { items[position].liked = true; notifyItemChanged(position); } } NO vh.getAdapterPosition()
  93. Old Habits Die Hard public void onBindViewHolder(ViewHolder vh, final int

    position) { vh.likeButton.setOnClickListener = new OnClickListener() { items[position].liked = true; notifyItemChanged(position); } } NO NO NO use onCreateVH
  94. Create public void onCreateViewHolder(int type) { if (type == HEADER)

    { if (headerVH == null) { headerVH = new HeaderViewHolder(...); } return headerVH; } }
  95. Create public void onCreateViewHolder(int type) { if (type == HEADER)

    { if (headerVH == null) { headerVH = new HeaderViewHolder(...); } return headerVH; } } NO NO NO NO
  96. Create public void onCreateViewHolder(int type) { if (type == HEADER)

    { if (headerVH == null) { headerVH = new HeaderViewHolder(...); } return headerVH; } } NO NO NO NO
  97. Fooling* The RecyclerView void refreshData() { new AsyncTask(...) { void

    doInBackground() { List<Item> items = webservice.fetch(); adapter.setData(items); adapter.notifyDataSetChanged(); } } }
  98. Fooling* The RecyclerView void refreshData() { new AsyncTask(...) { void

    doInBackground() { List<Item> items = webservice.fetch(); adapter.setData(items); adapter.notifyDataSetChanged(); } } } NO NO NO
  99. Fooling* The RecyclerView void refreshData() { new AsyncTask(...) { void

    doInBackground() { List<Item> items = webservice.fetch(); adapter.setData(items); } void onPostExecute() { adapter.notifyDataSetChanged(); } } }
  100. Fooling* The RecyclerView void refreshData() { new AsyncTask(...) { void

    doInBackground() { List<Item> items = webservice.fetch(); adapter.setData(items); } void onPostExecute() { adapter.notifyDataSetChanged(); } } } NO NO NO
  101. Fooling* The RecyclerView void refreshData() { new AsyncTask(...) { List<Item>

    doInBackground() { List<Item> items = webservice.fetch(); return items; } void onPostExecute(List<Item> items) { adapter.setData(items); adapter.notifyDataSetChanged(); } } }
  102. Thank You!

  103. Certificate of Achievement awarded to: 360|AnDev Attendee for outstanding knowledge

    on RecyclerView