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
生成AIで加速するテスト実装 - ロリポップ for Gamersの事例と 生成AIエディタの活用
kinosuke01
0
140
From the Wild into the Clouds - Laravel Meetup Talk
neverything
0
180
Better Code Design in PHP
afilina
0
180
5分で理解する SOLID 原則 #phpcon_nagoya
shogogg
1
400
PEPCは何を変えようとしていたのか
ken7253
3
300
仕様変更に耐えるための"今の"DRY原則を考える
mkmk884
9
3.2k
機能が複雑化しても 頼りになる FactoryBotの話
tamikof
1
230
苦しいTiDBへの移行を乗り越えて快適な運用を目指す
leveragestech
0
1.2k
Generating OpenAPI schema from serializers throughout the Rails stack - Kyobashi.rb #5
envek
1
420
ナレッジイネイブリングにAIを活用してみる ゆるSRE勉強会 #9
nealle
0
170
Swift Testingのモチベを上げたい
stoticdev
2
190
「個人開発マネタイズ大全」が教えてくれたこと
bani24884
1
290
Featured
See All Featured
Designing Dashboards & Data Visualisations in Web Apps
destraynor
231
53k
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
46
2.4k
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
129
19k
Designing on Purpose - Digital PM Summit 2013
jponch
117
7.1k
Building an army of robots
kneath
303
45k
Helping Users Find Their Own Way: Creating Modern Search Experiences
danielanewman
29
2.5k
YesSQL, Process and Tooling at Scale
rocio
172
14k
Fantastic passwords and where to find them - at NoRuKo
philnash
51
3k
Making Projects Easy
brettharned
116
6k
How to Ace a Technical Interview
jacobian
276
23k
Building a Modern Day E-commerce SEO Strategy
aleyda
38
7.1k
Agile that works and the tools we love
rasmusluckow
328
21k
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