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. Pro RecyclerView
    Yiğit Boyar / Google

    View Slide

  2. Define: Pro
    • Know the past and today
    • Use it properly & efficiently
    • (a.k.a best practices)

    View Slide

  3. Part I
    Know The Past and Today

    View Slide

  4. ListView
    • Repeated & Consistent
    Content

    View Slide

  5. ListView
    • or sometimes, not so
    consistent
    • A lot of data, not enough
    memory

    View Slide

  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!

    View Slide

  7. Complexity Overdraft
    • A lot of long tail, one - off features
    • Undefined behavior when APIs are mixed
    • Undefined behavior = undefined API

    View Slide

  8. Duplicate Features
    • Focused View vs Selected Item
    • Item Click Listener vs View Click Listener
    • setItemsCanFocus
    • why not?

    View Slide

  9. Animations

    View Slide

  10. ¯\_(ϑ)_/¯

    View Slide

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

    View Slide

  12. Reboot
    • Elevates best practices
    • ViewHolder
    • De-couple via well defined components
    • Recycler
    • Do less
    • Use framework focus
    • Smart adapters

    View Slide

  13. RecyclerView Components
    Layout
    Manager
    Adapter
    Item Animator
    I position
    the views
    I provide
    the views
    I animate
    the views
    Recycler View

    View Slide

  14. Part II
    Best Practices

    View Slide

  15. View::requestLayout

    View Slide

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

    View Slide

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

    View Slide

  18. View::requestLayout
    ViewGroup
    ViewGroup
    View
    ViewGroup
    view
    ViewGroup
    View
    ViewGroup
    View
    layout

    View Slide

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

    View Slide

  20. imageView.setImageBitmap(bitmap)

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  25. TextView.java
    ¯\_(ϑ)_/¯

    View Slide

  26. How Do I Know?

    View Slide

  27. RecyclerView.java

    View Slide

  28. Resizing Items

    View Slide

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

    View Slide

  30. desired

    View Slide

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

    View Slide

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

    View Slide

  33. Bad API vs Good API
    {

    "user" : {

    "name" : "Michael",

    }

    }
    "photoUrl" : "https://..."


    View Slide

  34. Bad API vs Good API
    {

    "user" : {

    "name" : "Michael",

    }

    }
    "photoUrl" : {

    "width" : 300,

    "height" : 500,

    "url" : "https://...",

    "palette" : {}

    }


    View Slide

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

    View Slide

  36. Data Updates

    View Slide

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

    View Slide

  38. View Slide

  39. onBind
    onBind
    onBind
    onBind
    onBind
    create new VH

    View Slide

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

    View Slide

  41. onBind
    unnecessary
    onBind, measure,
    layout

    View Slide

  42. SortedList
    SortedList mySortedList = new SortedList(Item.class,
    new SortedListAdapterCallback(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);
    }
    });

    View Slide

  43. SortedList
    void onFetched(List newsList) {
    mySortedList.addAll(newsList);
    }

    View Slide

  44. Sort By Votes

    View Slide

  45. SortedList
    SortedList mySortedList = new
    SortedList(Item.class,
    new
    SortedListAdapterCallback(myAdapter) {
    @Override
    public int compare(Item item1, Item
    item2) {
    return item1.id - item2.id;
    }

    });
    update compare
    method

    View Slide

  46. SortedList
    SortedList mySortedList = new
    SortedList(Item.class,
    new
    SortedListAdapterCallback(myAdapter) {
    @Override
    public int compare(Item item1, Item
    item2) {
    return item2.votes - item1.votes;
    }

    });

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  51. SortedList::updateItemAt
    Map 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);
    }
    }

    View Slide

  52. Introducing…

    View Slide

  53. DiffUtil

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  57. Adapter::onBindViewHolder
    @Override public void
    onBindViewHolder(RecyclerView.ViewHolder holder,
    int position,List payloads) {
    if (payloads.isEmpty()) {
    onBindViewHolder(holder, position);
    } else {
    if (payloads.contains(VOTES)) {
    holder.voteCount.setText("" + item.votes);
    }
    }
    }

    View Slide

  58. DiffUtil
    Available in 24.2

    View Slide

  59. Resource
    Management

    View Slide

  60. ViewHolder Lifecycle
    onCreate
    onViewAttachedToWindow
    onViewDetachedFromWindow
    onRecycled
    onBindViewHolder
    the same item
    stop playing video
    start playing video

    View Slide

  61. ViewHolder Lifecycle
    onCreate
    onViewAttachedToWindow
    onViewDetachedFromWindow
    onRecycled
    onBindViewHolder
    might be a different item
    release video
    resources
    acquire video
    resources

    View Slide

  62. RecyclerView is Async

    View Slide

  63. RecyclerView Update Cycle
    on frame
    handle pending changes

    View Slide

  64. RecyclerView Update Cycle
    on frame
    handle pending changes
    scroll to position
    notify data
    change
    smooth scroll
    all structural
    changes
    postponed until
    next layout

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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.

    View Slide

  70. ViewHolder ++

    View Slide

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

    View Slide

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

    View Slide

  73. View Types

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  79. Click Listeners

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  84. Adapter Position
    vs
    Layout Position

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  88. Things That Makes Me
    Sad :(

    View Slide

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

    View Slide

  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 :) )

    View Slide

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

    View Slide

  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()

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  102. Thank You!

    View Slide

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

    View Slide