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
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
20250426 GDGoC 合同新歓 - GDGoC のススメ
getty708
0
110
SwiftDataのカスタムデータストアを試してみた
1mash0
0
140
Носок на сок
bo0om
0
1.1k
flutter_kaigi_mini_4.pdf
nobu74658
0
140
エンジニアが挑む、限界までの越境
nealle
1
320
Amazon CloudWatchの地味だけど強力な機能紹介!
itotsum
0
230
Making TCPSocket.new "Happy"!
coe401_
1
3.1k
Thank you <💅>, What's the Next?
ahoxa
1
590
Ruby on Railroad: The Power of Visualizing CFG
ydah
0
290
KANNA Android の技術的課題と取り組み
watabee
0
190
「理解」を重視したAI活用開発
fast_doctor
0
270
複雑なフォームの jotai 設計 / Designing jotai(state) for Complex Forms #layerx_frontend
izumin5210
6
1.5k
Featured
See All Featured
Evolution of real-time – Irina Nazarova, EuRuKo, 2024
irinanazarova
8
690
Building an army of robots
kneath
305
45k
Adopting Sorbet at Scale
ufuk
76
9.3k
Automating Front-end Workflow
addyosmani
1370
200k
Navigating Team Friction
lara
185
15k
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
45
9.5k
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
331
21k
The Power of CSS Pseudo Elements
geoffreycrofte
75
5.8k
ReactJS: Keep Simple. Everything can be a component!
pedronauck
667
120k
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
120
51k
Designing for humans not robots
tammielis
253
25k
Designing Experiences People Love
moore
142
24k
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