The RecyclerView widget is a more advanced and flexible version of ListView. It manages and optimizes the view holder bindings according to the scrolling position, and recycles the views so that it uses only a small number of views for a large number of list items. Seeing as the RecyclerView sample app is outdated and doesn’t even compile, this tutorial aims to show a relatively quick way to add a RecyclerView to modern Android Studio projects, and use it to display a list of random images we’ll download to our device.
Make a new project (or open an existing one). When creating the project, we’ll choose to add a scrolling activity for this example, but you can choose any layout you want.
Run it now for a small sanity check:
Right click on the project folder -> add -> fragment (list) -> finish
This creates a RecyclerView with lots of boilerplate code. Let’s go over the added classes:
MyItemRecyclerViewAdapter
- Creates the view holder which, well, holds the views for items in the list and binds the data to the views inside the view holder.
ItemFragment
- The fragment that holds and initializes the adapter.
Dummy/DummyContent
- Dummy items for populating the list. We’ll replace those with our picture items.
fragment_item_list.xml
- contains the RecyclerView widget.
fragment_item.xml
- layout of each item in the list.
Now we need to add the fragment we created to our activity.
In content_scrolling.xml
replace the TextView with:
<fragment android:name="com.example.myapplication.ItemFragment"
android:id="@+id/main_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
We’ll also make the activity implement our interaction listener interface:
implements ItemFragment.OnListFragmentInteractionListener
After adding this to the activity class, you’ll have to implement the onListFragmentInteraction method, you can do it automatically with the suggestion window. This is the auto-generated method that’s added:
@Override
public void onListFragmentInteraction(DummyContent.DummyItem item) {
}
Run the project now to see that the list shows and scrolls:
In android studio, rename DummyContent.java
to PictureContent.java
(and the class name), and move it out of the dummy
package. Delete the dummy package.
We’ll also delete the DummyItem
class, and create a POJO class PictureItem
in a new file, containing the picture URI and creation date:
class PictureItem {
public Uri uri;
public String date;
}
In PictureContent
replace the DummyItem
creation with a PictureItem
creation:
public class PictureContent {
static final List<PictureItem> ITEMS = new ArrayList<>();
public static void loadImage(File file) {
PictureItem newItem = new PictureItem();
newItem.uri = Uri.fromFile(file);
newItem.date = getDateFromUri(newItem.uri);
addItem(newItem);
}
private static void addItem(PictureItem item) {
ITEMS.add(0, item);
}
}
Now we’ll update fragment_item.xml
to display our image item with an ImageView
for the image and a TextView
for the creation date:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="@+id/item_image_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/app_name"
android:scaleType="centerInside"
android:padding="10dp" />
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/text_margin"
android:text="@string/created_at"
android:textAppearance="?attr/textAppearanceListItem" />
<TextView
android:id="@+id/item_date_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/text_margin"
android:textAppearance="?attr/textAppearanceListItem" />
</LinearLayout>
</LinearLayout>
Finally, in MyItemRecyclerViewAdapter
, replace the content to bind our new data fields to our new views:
public class MyItemRecyclerViewAdapter extends RecyclerView.Adapter<MyItemRecyclerViewAdapter.ViewHolder> {
private final List<PictureItem> mValues;
private final OnListFragmentInteractionListener mListener;
public MyItemRecyclerViewAdapter(List<PictureItem> items, OnListFragmentInteractionListener listener) {
mValues = items;
mListener = listener;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.fragment_item, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
holder.mItem = mValues.get(position);
holder.mImageView.setImageURI(mValues.get(position).uri);
holder.mDateView.setText(mValues.get(position).date);
holder.mView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (null != mListener) {
// Notify the active callbacks interface (the activity, if the
// fragment is attached to one) that an item has been selected.
mListener.onListFragmentInteraction(holder.mItem);
}
}
});
}
@Override
public int getItemCount() {
return mValues.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
public final View mView;
public final ImageView mImageView;
public final TextView mDateView;
public PictureItem mItem;
public ViewHolder(View view) {
super(view);
mView = view;
mImageView = view.findViewById(R.id.item_image_view);
mDateView = view.findViewById(R.id.item_date_tv);
}
}
}
Now we’ll populate the list with images saved in the device storage. Add the images loading methods to PictureContent
:
public static void loadSavedImages(File dir) {
ITEMS.clear();
if (dir.exists()) {
File[] files = dir.listFiles();
for (File file : files) {
String absolutePath = file.getAbsolutePath();
String extension = absolutePath.substring(absolutePath.lastIndexOf("."));
if (extension.equals(".jpg")) {
loadImage(file);
}
}
}
}
private static String getDateFromUri(Uri uri){
String[] split = uri.getPath().split("/");
String fileName = split[split.length - 1];
String fileNameNoExt = fileName.split("\\.")[0];
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString = format.format(new Date(Long.parseLong(fileNameNoExt)));
return dateString;
}
public static void loadImage(File file) {
PictureItem newItem = new PictureItem();
newItem.uri = Uri.fromFile(file);
newItem.date = getDateFromUri(newItem.uri);
addItem(newItem);
}
We’re going to call loadSavedImages
from our activity ScrollingActivity
, so we first need to get a reference to the recycler view. Add two fields:
private RecyclerView.Adapter recyclerViewAdapter;
private RecyclerView recyclerView;
Which will be lazy loaded in onCreate
:
if (recyclerViewAdapter == null) {
Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.main_fragment);
recyclerView = (RecyclerView) currentFragment.getView();
recyclerViewAdapter = ((RecyclerView) currentFragment.getView()).getAdapter();
}
And in onResume
we’ll add a call to loadSavedImages
:
@Override
protected void onResume() {
super.onResume();
runOnUiThread(new Runnable() {
@Override
public void run() {
loadSavedImages(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS));
recyclerViewAdapter.notifyDataSetChanged();
}
});
}
Notice we’re loading the files from
DIRECTORY_DOWNLOADS
which is a convensional folder for storing downloaded files.
We’ll download random pictures from Lorem Picsum whenever clicking the Floating Action Button, using the built in DownloadManager class.
Add the download method to PictureContent
:
public static void downloadRandomImage(DownloadManager downloadmanager, Context context) {
long ts = System.currentTimeMillis();
Uri uri = Uri.parse(context.getString(R.string.image_download_url));
DownloadManager.Request request = new DownloadManager.Request(uri);
request.setTitle("My File");
request.setDescription("Downloading");
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE);
request.setVisibleInDownloadsUi(false);
String fileName = ts + ".jpg";
request.setDestinationInExternalFilesDir(context, Environment.DIRECTORY_DOWNLOADS, fileName);
downloadmanager.enqueue(request);
}
This downloads the file to DIRECTORY_DOWNLOADS
with the current timestamp as file name.
Set image_download_url
in strings.xml
:
<string name="image_download_url">https://picsum.photos/200/300/?random</string>
Don’t forget to add the INTERNET permission to the manifest
Now we need to handle the download complete event. Add the following to activity’s onCreate
:
onComplete = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String filePath="";
DownloadManager.Query q = new DownloadManager.Query();
q.setFilterById(intent.getExtras().getLong(DownloadManager.EXTRA_DOWNLOAD_ID));
Cursor c = downloadManager.query(q);
if (c.moveToFirst()) {
int status = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));
if (status == DownloadManager.STATUS_SUCCESSFUL) {
String downloadFileLocalUri = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
filePath = Uri.parse(downloadFileLocalUri).getPath();
}
}
c.close();
PictureContent.loadImage(new File(filePath));
recyclerViewAdapter.notifyItemInserted(0);
progressBar.setVisibility(View.GONE);
fab.setVisibility(View.VISIBLE);
}
};
context.registerReceiver(onComplete, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
This is the (quite verbose) way of getting the downloaded file name when the download manager completes the download. After getting filePath
we call PictureContent.loadImage
, which adds it to our list.
Notice the call to
recyclerViewAdapter.notifyItemInserted(0)
. This will cause the list to refresh with the new item we’ve inserted (at index 0)
As the final touchup, we’ll update the FAB’s icon, using Android Studio’s Asset Studio, for creating a vector material icon.
Right-click the res folder and select New > Vector Asset.
Click the Button and search for the keyword add
:
This will give us the plus material icon. Change color to white, and save the xml in the drawable folder.
That’s it! Now we have a scrolling RecyclerView showing the downloaded pictures:
Full source can be found here