Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Pro RecyclerView
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
Yigit Boyar
July 29, 2016
Programming
32k
44
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Pro RecyclerView
Pro RecyclerView talk at 360andev / Denver / 2016
Yigit Boyar
July 29, 2016
Other Decks in Programming
See All in Programming
週末にAI-DLCを本気で回したら$1,600溶けた
hbashimizu
0
130
AI × TiDD / 2026.09.05 Redmine 大阪
tokudiro
1
120
WebMCP Challenge に星空観察アプリで参加した話
okajun35
0
120
Vibes Containers 〜AIで変わるコンテナ設計と運用〜
tkikuc
3
520
AIを上手に使っていこうとしたら越境せざるを得なくなった話 〜実践1年で見えた境界を越えなければならない理由と進め方〜 / Crossing borders with AI
tomoyakitaura
4
1.1k
XHTMLが残したもの
yosuke_furukawa
PRO
2
590
AI駆動開発にグラフDBを重ねてみた
satoshi256kbyte
2
770
MIZARU@SPAJAM2026 第二回予選
1901drama
0
110
巨大モノリシックアプリ モダン化大作戦
ktcryomm
0
350
App Storeの外へ──日本のiOSサイドローディング入門 for iOSDC Japan 2026
yuukiw00w
0
130
Intent as Code
shoppingjaws
4
660
go-spidermonkeyでAIエージェントのCode Modeを実装する
syumai
3
1.5k
Featured
See All Featured
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
32
4.7k
How to Talk to Developers About Accessibility
jct
2
540
We Have a Design System, Now What?
morganepeng
55
8.3k
Test your architecture with Archunit
thirion
2
2.4k
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
PRO
202
76k
How To Speak Unicorn (iThemes Webinar)
marktimemedia
1
570
Pawsitive SEO: Lessons from My Dog (and Many Mistakes) on Thriving as a Consultant in the Age of AI
davidcarrasco
0
240
Sam Torres - BigQuery for SEOs
techseoconnect
PRO
0
540
Beyond borders and beyond the search box: How to win the global "messy middle" with AI-driven SEO
davidcarrasco
3
240
Skip the Path - Find Your Career Trail
mkilby
1
220
Marketing Yourself as an Engineer | Alaka | Gurzu
gurzu
0
300
The Art of Programming - Codeland 2020
erikaheidi
57
14k
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