$30 off During Our Annual Pro Sale. View Details »
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Pro RecyclerView
Search
Yigit Boyar
July 29, 2016
Programming
44
32k
Pro RecyclerView
Pro RecyclerView talk at 360andev / Denver / 2016
Yigit Boyar
July 29, 2016
Tweet
Share
Other Decks in Programming
See All in Programming
Combinatorial Interview Problems with Backtracking Solutions - From Imperative Procedural Programming to Declarative Functional Programming - Part 1
philipschwarz
PRO
0
110
CSC305 Lecture 14
javiergs
PRO
0
330
なあ兄弟、 余白の意味を考えてから UI実装してくれ!
ktcryomm
10
8.9k
All(?) About Point Sets
hole
0
230
Stay Hacker 〜九州で生まれ、Perlに出会い、コミュニティで育つ〜
pyama86
2
2.8k
GraalVM Native Image トラブルシューティング機能の最新状況(2025年版)
ntt_dsol_java
0
170
モデル駆動設計をやってみよう Modeling Forum2025ワークショップ/Let’s Try Model-Driven Design
haru860
0
200
[堅牢.py #1] テストを書かない研究者に送る、最初にテストを書く実験コード入門 / Let's start your ML project by writing tests
shunk031
11
6.2k
AIの弱点、やっぱりプログラミングは人間が(も)勉強しよう / YAPC AI and Programming
kishida
13
5.5k
競馬で学ぶ機械学習の基本と実践 / Machine Learning with Horse Racing
shoheimitani
14
14k
Promise.tryで実現する新しいエラーハンドリング New error handling with Promise try
bicstone
3
1.7k
Full-Cycle Reactivity in Angular: SignalStore mit Signal Forms und Resources
manfredsteyer
PRO
0
150
Featured
See All Featured
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
234
17k
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
659
61k
VelocityConf: Rendering Performance Case Studies
addyosmani
333
24k
The World Runs on Bad Software
bkeepers
PRO
72
12k
Rails Girls Zürich Keynote
gr2m
95
14k
Why You Should Never Use an ORM
jnunemaker
PRO
60
9.6k
Unsuck your backbone
ammeep
671
58k
Building an army of robots
kneath
306
46k
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
333
22k
[RailsConf 2023 Opening Keynote] The Magic of Rails
eileencodes
31
9.8k
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
49
3.2k
jQuery: Nuts, Bolts and Bling
dougneiner
65
8k
Transcript
Pro RecyclerView Yiğit Boyar / Google
Define: Pro • Know the past and today • Use
it properly & efficiently • (a.k.a best practices)
Part I Know The Past and Today
ListView • Repeated & Consistent Content
ListView • or sometimes, not so consistent • A lot
of data, not enough memory
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!
Complexity Overdraft • A lot of long tail, one -
off features • Undefined behavior when APIs are mixed • Undefined behavior = undefined API
Duplicate Features • Focused View vs Selected Item • Item
Click Listener vs View Click Listener • setItemsCanFocus • why not?
Animations
¯\_(ϑ)_/¯
More Complex Layouts • GridView • Horizontal List View •
StaggeredGridView
Reboot • Elevates best practices • ViewHolder • De-couple via
well defined components • Recycler • Do less • Use framework focus • Smart adapters
RecyclerView Components Layout Manager Adapter Item Animator I position the
views I provide the views I animate the views Recycler View
Part II Best Practices
View::requestLayout
View::requestLayout requestLayout ViewGroup ViewGroup View ViewGroup view ViewGroup View ViewGroup
View
View::requestLayout ViewGroup ViewGroup View ViewGroup view ViewGroup View ViewGroup View
nextFrame measure
View::requestLayout ViewGroup ViewGroup View ViewGroup view ViewGroup View ViewGroup View
layout
View::requestLayout onBindViewHolder(ViewHolder vh, int position) { .... imageLoader.loadImage(vh.image, user.profileUrl, R.drawable.placeHolder);
}
imageView.setImageBitmap(bitmap)
imageView.setImageBitmap(bitmap) imageView.requestLayout();
imageView.setImageBitmap(bitmap) imageView.requestLayout(); itemView.requestLayout();
imageView.setImageBitmap(bitmap) imageView.requestLayout(); itemView.requestLayout(); recyclerView.requestLayout(); unless setHasFixedSize == true
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(); } }
TextView.java ¯\_(ϑ)_/¯
How Do I Know?
RecyclerView.java
Resizing Items
imageLoader.load( viewHolder.imageView); cache miss
desired
reality: bad item height Clock in the wrong column
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); }
Bad API vs Good API { "user" : { "name"
: "Michael", } } "photoUrl" : "https://..."
Bad API vs Good API { "user" : { "name"
: "Michael", } } "photoUrl" : { "width" : 300, "height" : 500, "url" : "https://...", "palette" : {} }
DataBinding void onBindViewHolder(ViewHolder vh, int pos) { vh.binding.setItem(items.get(pos)); vh.binding.executePendingBindings(); }
Data Updates
updated list arrives void onFetched(List<News> news) { myAdapter.setNews(news); myAdapter.notifyDataSetChanged(); }
None
onBind onBind onBind onBind onBind create new VH
long getItemId(int position) { news.get(position).getId(); }
onBind unnecessary onBind, measure, layout
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); } });
SortedList void onFetched(List<News> newsList) { mySortedList.addAll(newsList); }
Sort By Votes
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
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; } … });
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
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
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
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
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); } }
Introducing…
DiffUtil
DiffUtil DiffResult result = DiffUtil.calculateDiff(new MyCallback(oldList, newList)); myAdapter.setItems(newList); result.dispatchUpdatesTo(myAdapter);
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)); } }
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; }
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); } } }
DiffUtil Available in 24.2
Resource Management
ViewHolder Lifecycle onCreate onViewAttachedToWindow onViewDetachedFromWindow onRecycled onBindViewHolder the same item
stop playing video start playing video
ViewHolder Lifecycle onCreate onViewAttachedToWindow onViewDetachedFromWindow onRecycled onBindViewHolder might be a
different item release video resources acquire video resources
RecyclerView is Async
RecyclerView Update Cycle on frame handle pending changes
RecyclerView Update Cycle on frame handle pending changes scroll to
position notify data change smooth scroll all structural changes postponed until next layout
RecyclerView::scrollToPosition recyclerView.scrollToPosition(15); int x = layoutManager.getFirstVisibleItemPosition(); x == 15 ?
RecyclerView::scrollToPosition recyclerView.scrollToPosition(15); int x = layoutManager.getFirstVisibleItemPosition(); x == 15 ?
RecyclerView::scrollToPosition onInit void onCreate(SavedInstanceState state) { .... mRecyclerView.scrollToPosition(selectedPosition); mRecyclerView.setAdapter(myAdapter); }
will it work? ✓
RecyclerView::scrollToPosition onInit void onCreate(SavedInstanceState state) { .... mRecyclerView.scrollToPosition(selectedPosition); model.loadItems(items ->
mRecyclerView.setAdapter( new ItemAdapter(items)); ); } will it work? ✓
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.
ViewHolder ++
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()); }
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 Types
Adapter::getItemViewType @Override public int getItemViewType(int position) { User user =
mItems.get(position); if (user.isPremium()) { return TYPE_PREMIUM; } return TYPE_BASIC; }
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); }
Adapter::getItemViewType @Override public int getItemViewType(int position) { User user =
mItems.get(position); if (user.isPremium()) { return TYPE_PREMIUM; } return TYPE_BASIC; }
Adapter::getItemViewType @Override public int getItemViewType(int position) { User user =
mItems.get(position); if (user.isPremium()) { return R.layout.premium; } return R.layout.basic; }
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 ;)
Click Listeners
Where Is My Click Listener ? https://commons.wikimedia.org/wiki/File:Trollface.png
There Is Your Click Listener http://stackoverflow.com/questions/24885223/why-doesnt-recyclerview-have-onitemclicklistener-and-how-recyclerview-is-dif
There Is Your Click Listener ItemClickListener prevents children clicks http://stackoverflow.com/questions/24885223/why-doesnt-recyclerview-have-onitemclicklistener-and-how-recyclerview-is-dif
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
Adapter Position vs Layout Position
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);
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
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
Things That Makes Me Sad :(
MySuperSolidRecyclerView class MySuperSolidRecyclerView extends RecyclerView { public void onLayout() {
try { super.onLayout(); } catch (Throwable t) { // ignore } } }
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 :) )
Old Habits Die Hard public void onBindViewHolder(ViewHolder vh, final int
position) { vh.likeButton.setOnClickListener = new OnClickListener() { items[position].liked = true; notifyItemChanged(position); } }
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()
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
Create public void onCreateViewHolder(int type) { if (type == HEADER)
{ if (headerVH == null) { headerVH = new HeaderViewHolder(...); } return headerVH; } }
Create public void onCreateViewHolder(int type) { if (type == HEADER)
{ if (headerVH == null) { headerVH = new HeaderViewHolder(...); } return headerVH; } } NO NO NO NO
Create public void onCreateViewHolder(int type) { if (type == HEADER)
{ if (headerVH == null) { headerVH = new HeaderViewHolder(...); } return headerVH; } } NO NO NO NO
Fooling* The RecyclerView void refreshData() { new AsyncTask(...) { void
doInBackground() { List<Item> items = webservice.fetch(); adapter.setData(items); adapter.notifyDataSetChanged(); } } }
Fooling* The RecyclerView void refreshData() { new AsyncTask(...) { void
doInBackground() { List<Item> items = webservice.fetch(); adapter.setData(items); adapter.notifyDataSetChanged(); } } } NO NO NO
Fooling* The RecyclerView void refreshData() { new AsyncTask(...) { void
doInBackground() { List<Item> items = webservice.fetch(); adapter.setData(items); } void onPostExecute() { adapter.notifyDataSetChanged(); } } }
Fooling* The RecyclerView void refreshData() { new AsyncTask(...) { void
doInBackground() { List<Item> items = webservice.fetch(); adapter.setData(items); } void onPostExecute() { adapter.notifyDataSetChanged(); } } } NO NO NO
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(); } } }
Thank You!
Certificate of Achievement awarded to: 360|AnDev Attendee for outstanding knowledge
on RecyclerView