Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Pro RecyclerView
Search
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
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
エンジニアと一緒にテストコードの設計と実装を改善した話
mototakatsu
0
250
jQueryをバージョンアップする前に使いたいjQuery Migrate
matsuo_atsushi
0
630
その問い、本当に正しいですか?AI時代のエンジニアに必要な哲学と認知科学 / ai-philosophy-cognitive-science
minodriven
14
6.6k
Vue × Nuxt × Oxc どこまで使える?実運用の現在地
andpad
0
350
ECSアプリログをFireLensでコスト削減しようとしたけど諦めた話 in Fargate×Node.js
akihisaikeda
2
4.2k
AIを活用したE2Eテスト実装効率化のあゆみ / ebisu-mobile-14-kotetu
kotetuco
0
160
Javaの型とAI時代に型が大事な理由 / java types and type in AI era
kishida
2
170
継続モナドとリアクティブプログラミング
yukikurage
2
260
TypeScript+Orvalで実現する型安全かつ堅牢でスケーラブルなマルチチャネル通知基盤 / TSKaigi Night talks ~after conference~
d0riven
0
380
AIエージェントで 変わるAndroid開発環境
takahirom
1
340
なぜ型を書くのか? TSKaigi2026で改めて考える #tskaigi_smarthr
kajitack
0
190
Language Server 使ってる? 〜VSCode と Zed の場合〜 / Are you using a Language Server? ~For VS Code and Zed~
handlename
0
830
Featured
See All Featured
The Illustrated Guide to Node.js - THAT Conference 2024
reverentgeek
1
410
Jamie Indigo - Trashchat’s Guide to Black Boxes: Technical SEO Tactics for LLMs
techseoconnect
PRO
0
250
Making the Leap to Tech Lead
cromwellryan
135
10k
sira's awesome portfolio website redesign presentation
elsirapls
0
290
The #1 spot is gone: here's how to win anyway
tamaranovitovic
3
1.1k
Mobile First: as difficult as doing things right
swwweet
225
10k
How Software Deployment tools have changed in the past 20 years
geshan
0
34k
We Have a Design System, Now What?
morganepeng
55
8.2k
The Organizational Zoo: Understanding Human Behavior Agility Through Metaphoric Constructive Conversations (based on the works of Arthur Shelley, Ph.D)
kimpetersen
PRO
0
380
Design in an AI World
tapps
1
260
The AI Search Optimization Roadmap by Aleyda Solis
aleyda
1
6k
Public Speaking Without Barfing On Your Shoes - THAT 2023
reverentgeek
1
450
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