Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Pro RecyclerView

Pro RecyclerView

Pro RecyclerView talk at 360andev / Denver / 2016

Yigit Boyar

July 29, 2016
Tweet

Other Decks in Programming

Transcript

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

    it properly & efficiently • (a.k.a best practices)
  2. 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!
  3. Complexity Overdraft • A lot of long tail, one -

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

    Click Listener vs View Click Listener • setItemsCanFocus • why not?
  5. Reboot • Elevates best practices • ViewHolder • De-couple via

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

    views I provide the views I animate the views Recycler View
  7. 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(); } }
  8. 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); }
  9. Bad API vs Good API {
 "user" : {
 "name"

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

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

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

  11. 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); } });
  12. 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
  13. 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; } … });
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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); } }
  19. 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)); } }
  20. 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; }
  21. 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); } } }
  22. RecyclerView Update Cycle on frame handle pending changes scroll to

    position notify data change smooth scroll all structural changes postponed until next layout
  23. 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.
  24. 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()); }
  25. 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); }
  26. Adapter::getItemViewType @Override public int getItemViewType(int position) { User user =

    mItems.get(position); if (user.isPremium()) { return TYPE_PREMIUM; } return TYPE_BASIC; }
  27. 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); }
  28. Adapter::getItemViewType @Override public int getItemViewType(int position) { User user =

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

    mItems.get(position); if (user.isPremium()) { return R.layout.premium; } return R.layout.basic; }
  30. 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 ;)
  31. 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
  32. 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);
  33. 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
  34. 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
  35. 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 :) )
  36. Old Habits Die Hard public void onBindViewHolder(ViewHolder vh, final int

    position) { vh.likeButton.setOnClickListener = new OnClickListener() { items[position].liked = true; notifyItemChanged(position); } }
  37. 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()
  38. 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
  39. Create public void onCreateViewHolder(int type) { if (type == HEADER)

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

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

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

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

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

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

    doInBackground() { List<Item> items = webservice.fetch(); adapter.setData(items); } void onPostExecute() { adapter.notifyDataSetChanged(); } } } NO NO NO
  46. 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(); } } }