ListView
. As you are already known, RecyclerView
is a new component introduced in Android Lollipop, this component increases performances respect to ListView
. Moreover, respect to ListView
, RecyclerView
is much more customizable.Today, with this post, I would like to talk about making an endless
RecyclerView
and when data is loading, it will show a ProgressBar
at the bottom.
Adding dependencies to project
RecyclerView
denpendency to your app level build.gradle. In this sample project, I set each RecyclerView
item as a CardView
, so you must add it's dependency, too:
dependencies {
compile 'com.android.support:appcompat-v7:25.1.0'
compile 'com.android.support:recyclerview-v7:25.1.0'
compile 'com.android.support:cardview-v7:25.1.0'
}
Create layout files
ProgressBar
at the bottom of RecyclerView
when data is loading, it will have two item types. The normal item that to show info of user and loading item that place at bottom to show progress bar.Layout for the normal item:
item_recycler_view_row.xml
Layout for the loading item:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
card_view:cardUseCompatPadding="true">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:selectableItemBackground"
android:padding="10dp">
<TextView
android:id="@+id/txt_email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@android:color/black"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/txt_phone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/txt_email"
android:textColor="@android:color/black"
android:textSize="12sp" />
</RelativeLayout>
</android.support.v7.widget.CardView>
item_loading.xml
And you must put a <?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="wrap_content"
android:orientation="vertical">
<ProgressBar
android:id="@+id/progressBar1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" />
</LinearLayout>
RecyclerView
object to the main activity:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>
Create a POJO class
Contact.java
package info.devexchanges.endlessrecyclerview;
public class Contact {
private String email;
private String phone;
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
Define an interface for callbacks
OnLoadMoreListener
with an abstract method onLoadMore()
which will be invoked when we scroll the RecyclerView
to end:
OnLoadMoreListener.java
package info.devexchanges.endlessrecyclerview;
public interface OnLoadMoreListener {
void onLoadMore();
}
Configuration in RecyclerView adapter
RecyclerView
adapter (I named as ContactAdapter
), we have 2 item types then must create two ViewHolder like below:
// "Loading item" ViewHolder
private class LoadingViewHolder extends RecyclerView.ViewHolder {
public ProgressBar progressBar;
public LoadingViewHolder(View view) {
super(view);
progressBar = (ProgressBar) view.findViewById(R.id.progressBar1);
}
}
// "Normal item" ViewHolder
private class UserViewHolder extends RecyclerView.ViewHolder {
public TextView phone;
public TextView email;
public UserViewHolder(View view) {
super(view);
phone = (TextView) view.findViewById(R.id.txt_phone);
email = (TextView) view.findViewById(R.id.txt_email);
}
}
RecyclerView
:
private final int VIEW_TYPE_ITEM = 0;
private final int VIEW_TYPE_LOADING = 1;
Provide an OnLoadMoreListener
variable and set an "add method":
private OnLoadMoreListener onLoadMoreListener;
public void setOnLoadMoreListener(OnLoadMoreListener mOnLoadMoreListener) {
this.onLoadMoreListener = mOnLoadMoreListener;
}
In the constructor of this adapter class, we handle scroll event of the RecyclerView
here. This is the most important step, firstly, you must get LayoutManager
of RecyclerView
, detecting scroll to bottom in onScroll()
:
private boolean isLoading;
private Activity activity;
private List<Contact> contacts;
private int visibleThreshold = 5;
private int lastVisibleItem, totalItemCount;
public ContactAdapter(RecyclerView recyclerView, List<Contact> contacts, Activity activity) {
this.contacts = contacts;
this.activity = activity;
final LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
totalItemCount = linearLayoutManager.getItemCount();
lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition();
if (!isLoading && totalItemCount <= (lastVisibleItem + visibleThreshold)) {
if (onLoadMoreListener != null) {
onLoadMoreListener.onLoadMore();
}
isLoading = true;
}
}
});
}
Add some necessary methods to complete ContactAdapter
:
@Override
public int getItemViewType(int position) {
return contacts.get(position) == null ? VIEW_TYPE_LOADING : VIEW_TYPE_ITEM;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == VIEW_TYPE_ITEM) {
View view = LayoutInflater.from(activity).inflate(R.layout.item_recycler_view_row, parent, false);
return new UserViewHolder(view);
} else if (viewType == VIEW_TYPE_LOADING) {
View view = LayoutInflater.from(activity).inflate(R.layout.item_loading, parent, false);
return new LoadingViewHolder(view);
}
return null;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof UserViewHolder) {
Contact contact = contacts.get(position);
UserViewHolder userViewHolder = (UserViewHolder) holder;
userViewHolder.phone.setText(contact.getEmail());
userViewHolder.email.setText(contact.getPhone());
} else if (holder instanceof LoadingViewHolder) {
LoadingViewHolder loadingViewHolder = (LoadingViewHolder) holder;
loadingViewHolder.progressBar.setIndeterminate(true);
}
}
@Override
public int getItemCount() {
return contacts == null ? 0 : contacts.size();
}
public void setLoaded() {
isLoading = false;
}
Activity programmatically code
onCreate()
method of activity, we must call setOnLoadMoreListener()
and get new data inside onLoadMore()
. This is full code of the activity:
MainActivity.java
package info.devexchanges.endlessrecyclerview;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class MainActivity extends AppCompatActivity {
private List<Contact> contacts;
private ContactAdapter contactAdapter;
private Random random;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
contacts = new ArrayList<>();
random = new Random();
//set dummy data
for (int i = 0; i < 10; i++) {
Contact contact = new Contact();
contact.setPhone(phoneNumberGenerating());
contact.setEmail("DevExchanges" + i + "@gmail.com");
contacts.add(contact);
}
//find view by id and attaching adapter for the RecyclerView
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
contactAdapter = new ContactAdapter(recyclerView, contacts, this);
recyclerView.setAdapter(contactAdapter);
//set load more listener for the RecyclerView adapter
contactAdapter.setOnLoadMoreListener(new OnLoadMoreListener() {
@Override
public void onLoadMore() {
if (contacts.size() <= 20) {
contacts.add(null);
contactAdapter.notifyItemInserted(contacts.size() - 1);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
contacts.remove(contacts.size() - 1);
contactAdapter.notifyItemRemoved(contacts.size());
//Generating more data
int index = contacts.size();
int end = index + 10;
for (int i = index; i < end; i++) {
Contact contact = new Contact();
contact.setPhone(phoneNumberGenerating());
contact.setEmail("DevExchanges" + i + "@gmail.com");
contacts.add(contact);
}
contactAdapter.notifyDataSetChanged();
contactAdapter.setLoaded();
}
}, 5000);
} else {
Toast.makeText(MainActivity.this, "Loading data completed", Toast.LENGTH_SHORT).show();
}
}
});
}
private String phoneNumberGenerating() {
int low = 100000000;
int high = 999999999;
int randomNumber = random.nextInt(high - low) + low;
return "0" + randomNumber;
}
}
Running application
Conclusions
RecyclerView
and loading more data when scroll to end. This design is very popular in mobile application development, so I hope this post is helpful with your own work. Further, you can visit my previous post to learn solution with ListView
or read related tutorial posts on other site like:- A tut on CodePath
- A post from Mohamed Sobhy's blog