RecyclerView is a ViewGroup that helps us to display a scrollable list of elements. It is one of the most used UI components in the Android apps and it involves having a data source, an adapter, and the RecyclerView. Other than displaying the content, RecyclerView is efficiently reusing the views that have scrolled off the screen by recycling them.
📃Components Overview
- Data source — list of objects that could be obtained from a local database, or as a result of a http request, or even it could be a list with some predefined values.
- RecyclerView is a scrolling list for list items and to have access at it we must add some dependencies in the gradle file
- Layout for one item of data — XML file
- Layout manager handles the organization of UI components in a view (there are 3 predefined ways to display the items)
- View holder has view information for displaying one item
- Adapter connects data to the RecyclerView
- Set the Adapter to the RecyclerView and we are done 🙂
LayoutManager
Adapter
- We are talking about applying the adapter design pattern as an intermediary between the source data we want to display and the view
- Adapter is a structural design pattern that allows objects with incompatible interfaces to collaborate
- Responsibile to create, updade, add and delete items from the list
- It suppose to implement the abstract class RecyclerView.Adapter
ViewHolder
- Used by the adapter to prepare one item view for each element from the datasource
- The layout of the item is specified in an XML resource file, it’s like a 1:1 relation between the view and the element from the list of objects provided as a datasource
- Can have clickable elements and it is placed by the layout manager
- We must implement the abstract class RecyclerView.ViewHolder
👩🏻💻Implementation steps
- Get the data by creating a new object class (data source)
- Add the RecyclerView dependency to app/build.gradle file and add RecyclerView to layout
- Create XML layout for item
- Define the LayoutManager in activity
- Extend RecyclerView.ViewHolder in a separated class
- Extend RecyclerView.Adapter in a separated class
- In onCreate of activity, create a RecyclerView with adapter and layout manager
To cover the implementation steps we will display a list of emails. The data source is hardcoded, right now we are focusing on learning how to use RecyclerView.
Step 1: Get the data by creating a new object class (data source)
public class Email { | |
private int id; | |
private String fromName; | |
private String subject; | |
private String shortBody; | |
public Email(int id, String fromName, String subject, String shortBody) { | |
this.id = id; | |
this.fromName = fromName; | |
this.subject = subject; | |
this.shortBody = shortBody; | |
} | |
public int getId() { | |
return id; | |
} | |
public void setId(int id) { | |
this.id = id; | |
} | |
public String getFromName() { | |
return fromName; | |
} | |
public void setFromName(String fromName) { | |
this.fromName = fromName; | |
} | |
public String getSubject() { | |
return subject; | |
} | |
public void setSubject(String subject) { | |
this.subject = subject; | |
} | |
public String getShortBody() { | |
return shortBody; | |
} | |
public void setShortBody(String shortBody) { | |
this.shortBody = shortBody; | |
} | |
@Override | |
public String toString() { | |
return "Email{" + | |
"id='" + id + '\'' + | |
", fromName='" + fromName + '\'' + | |
", title='" + subject + '\'' + | |
", shortBody='" + shortBody + '\'' + | |
'}'; | |
} | |
} |
private void inbox() { | |
emails = new ArrayList<>(); | |
Email email = null; | |
for (int i = 0; i < 25; i++) { | |
email = new Email(0, "Magda " + i, "Hello Android " + i, "This is an intro about Android"); | |
emails.add(email); | |
} | |
} |
Step 2: Add the RecyclerView dependency to app/build.gradle file and add RecyclerView to layout
implementation 'androidx.recyclerview:recyclerview:1.2.0-beta01' |
<?xml version="1.0" encoding="utf-8"?> | |
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
android:layout_width="match_parent" | |
android:layout_height="match_parent" | |
android:orientation="vertical"> | |
<androidx.recyclerview.widget.RecyclerView | |
android:id="@+id/recyclerViewEmails" | |
android:layout_width="match_parent" | |
android:layout_height="match_parent" /> | |
</LinearLayout> |
Step 3: Create XML layout for item
<?xml version="1.0" encoding="utf-8"?> | |
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
xmlns:tools="http://schemas.android.com/tools" | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content" | |
android:orientation="vertical" | |
android:id="@+id/linearLayoutEmail" | |
android:padding="@dimen/small_padding"> | |
<TextView | |
android:id="@+id/textViewFrom" | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content" | |
android:textStyle="bold" | |
tools:text="Magda" /> | |
<TextView | |
android:id="@+id/textViewSubject" | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content" | |
android:paddingTop="@dimen/extra_small_padding" | |
android:textColor="@color/purple_700" | |
tools:text="Android Fundamentals" /> | |
<TextView | |
android:id="@+id/textViewBody" | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content" | |
android:paddingTop="@dimen/extra_small_padding" | |
tools:text="this is a welcome email ...." /> | |
</LinearLayout> |
Step 4: Define the LayoutManager in activity
private void setEmailsLayoutManager() { | |
recyclerViewEmails.setLayoutManager(new LinearLayoutManager(this)); | |
} |
Step 5: Extend RecyclerView.ViewHolder in a separated class
public class EmailViewHolder extends RecyclerView.ViewHolder{ | |
private final TextView textViewFrom, textViewSubject, textViewBody; | |
private final LinearLayout linearLayoutEmail; | |
public EmailViewHolder(@NonNull View itemView) { | |
super(itemView); | |
textViewFrom = itemView.findViewById(R.id.textViewFrom); | |
textViewSubject = itemView.findViewById(R.id.textViewSubject); | |
textViewBody = itemView.findViewById(R.id.textViewBody); | |
linearLayoutEmail = itemView.findViewById(R.id.linearLayoutEmail); | |
} | |
public TextView getTextViewFrom() { | |
return textViewFrom; | |
} | |
public TextView getTextViewSubject() { | |
return textViewSubject; | |
} | |
public TextView getTextViewBody() { | |
return textViewBody; | |
} | |
public LinearLayout getLinearLayoutEmail() { | |
return linearLayoutEmail; | |
} | |
} |
Step 6: Extend RecyclerView.Adapter in a separated class
public class EmailAdapter extends RecyclerView.Adapter<EmailViewHolder> { | |
private List<Email> emails; | |
private Context context; | |
public EmailAdapter(Context context, List<Email> emails) { | |
this.emails = emails; | |
this.context = context; | |
} | |
// creates the items and add them to the RecyclerView, just the layout | |
@NonNull | |
@Override | |
public EmailViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { | |
View itemView = LayoutInflater.from(context).inflate(R.layout.email_item, parent, false); | |
return new EmailViewHolder(itemView); | |
} | |
// binds (displays) the content from the list of emails for each item | |
@Override | |
public void onBindViewHolder(@NonNull EmailViewHolder holder, int position) { | |
Email currentEmail = emails.get(position); | |
holder.getTextViewFrom().setText(currentEmail.getFromName()); | |
holder.getTextViewSubject().setText(currentEmail.getSubject()); | |
holder.getTextViewBody().setText(currentEmail.getShortBody()); | |
} | |
// we tell to the Recycler View how many items to display | |
@Override | |
public int getItemCount() { | |
return emails.size(); | |
} | |
} |
Step 7: In onCreate of activity, create a RecyclerView with adapter and layout manager
private void setEmailsAdapter() { | |
recyclerViewEmails.setAdapter(new EmailAdapter(this, emails)); | |
} | |
private void displayEmailsList() { | |
// data source - checked | |
inbox(); | |
// layout manager - checked | |
setEmailsLayoutManager(); | |
// adapter - checked | |
setEmailsAdapter(); | |
} |
🔔Notify the Adapter
To not impact the speed of rendering the UI elements for a RecyclerView make sure that you’re not calling notifyDataSetChanged(), setAdapter(Adapter), or swapAdapter(Adapter, boolean) for small updates. [official recommendation] The solution is to use SortedListor DiffUtilto create minimal updates when the data source has changed.
public class EmailDiffCallback extends DiffUtil.Callback { | |
private List<Email> oldList; | |
private List<Email> newList; | |
public EmailDiffCallback(List<Email> oldList, List<Email> newList) { | |
this.oldList = oldList; | |
this.newList = newList; | |
} | |
@Override | |
public int getOldListSize() { | |
return oldList.size(); | |
} | |
@Override | |
public int getNewListSize() { | |
return newList.size(); | |
} | |
@Override | |
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { | |
// add a unique ID property on Email and expose a getId() method | |
return oldList.get(oldItemPosition).getId() == newList.get(newItemPosition).getId(); | |
} | |
@Override | |
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { | |
Email oldEmail = oldList.get(oldItemPosition); | |
Email newEmail = newList.get(newItemPosition); | |
if (oldEmail.getFromName() == newEmail.getFromName() && oldEmail.getSubject() == newEmail.getSubject() && oldEmail.getShortBody() == newEmail.getShortBody()) { | |
return true; | |
} | |
return false; | |
} | |
} |
// NOT OK | |
void onNewEmailsArrivedNotRecommended(List<Email> newEmails) { | |
emailAdapter.setEmails(newEmails); | |
emailAdapter.notifyDataSetChanged(); | |
} | |
// OK | |
void onNewDataArrivedFastRendering(List<Email> newEmails) { | |
List<Email> oldEmails = emailAdapter.getEmails(); | |
DiffUtil.DiffResult result = DiffUtil.calculateDiff(new EmailDiffCallback(oldEmails, newEmails)); | |
emailAdapter.setEmails(newEmails); | |
result.dispatchUpdatesTo(emailAdapter); | |
} |
✨Item decorators
We could set dividers between the items using DividerItemDecoration
private void setItemDecorator() { | |
RecyclerView.ItemDecoration itemDecoration = new | |
DividerItemDecoration(this, DividerItemDecoration.VERTICAL); | |
recyclerViewEmails.addItemDecoration(itemDecoration); | |
} |
💫Swipe to refresh
Step 1: Add a new dependency in the gradle file
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' |
Step 2: Wrap the RecyclerView in a SwipeRefreshLayout
<?xml version="1.0" encoding="utf-8"?> | |
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
android:id="@+id/swipeContainer" | |
android:layout_width="match_parent" | |
android:layout_height="match_parent"> | |
<androidx.recyclerview.widget.RecyclerView | |
android:id="@+id/recyclerViewEmails" | |
android:layout_width="match_parent" | |
android:layout_height="match_parent" /> | |
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> |
Step 3: Update the code in the Adapter
// Clean all elements of the recycler | |
public void clear() { | |
emails.clear(); | |
notifyDataSetChanged(); | |
} | |
// Add a list of items | |
public void addAll(List<Email> list) { | |
emails.addAll(list); | |
notifyDataSetChanged(); | |
} |
Step 4: Setup SwipeRefreshLayout
private void setupSwipeToRefresh() { | |
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { | |
@Override | |
public void onRefresh() { | |
// Make sure you call swipeRefreshLayout.setRefreshing(false) | |
// once the network request has completed successfully. | |
inbox(); | |
} | |
}); | |
// Configure the refreshing colors | |
swipeRefreshLayout.setColorSchemeResources(android.R.color.holo_blue_bright, | |
android.R.color.holo_green_light, | |
android.R.color.holo_orange_light, | |
android.R.color.holo_red_light); | |
} |
You could check the full source code here. (different branches for each section)
📚Learn more
- Android Vitals — Slow Rendering
- Create dynamic lists with RecyclerView
- RecyclerView ins and outs — Google I/O 2016
- Yigit Boyar: Pro RecyclerView
Enjoy and feel free to leave a comment if something is not clear or if you have questions. And if you like it please share!
Thank you for reading! 🙂
Follow me on: Twitter | Medium | Dev.to
Originally published at http://magdamiu.com on December 28, 2020.
Top comments (0)