Logo

dev-resources.site

for different kinds of informations.

How to use a RecyclerView to show images from storage

Published at
2/19/2019
Categories
android
recyclerview
Author
johnnymakestuff
Categories
2 categories in total
android
open
recyclerview
open
Author
15 person written this
johnnymakestuff
open
How to use a RecyclerView to show images from storage

final

The issue at hand

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.

Creating a new project

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:

finished

Adding a list fragment

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:

finished

Replacing dummy content

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);
        }
    }
}

Load pictures

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.

Downloading the pictures

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)

Aside: creating a plus icon with Asset Studio

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:

vector

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:

final

Full source can be found here

This article is cross posted from my blog

recyclerview Article's
25 articles in total
Favicon
How to Avoid Blinking in Android Recycler View
Favicon
Introducing RVTimeTracker - RecyclerView Time Tracker
Favicon
RecyclerView Last Item Extra Bottom Margin
Favicon
RecyclerView pagination scroll listener in Android
Favicon
Mostrando os Pokémon com Coil em Android
Favicon
How to create an Expandable Item in Android RecyclerView?
Favicon
Listener de clique no RecyclerView com Kotlin
Favicon
Cloud Firestore Android example – CRUD Operations with RecyclerView
Favicon
Kotlin Firestore example - CRUD Operations with RecyclerView | Android
Favicon
6 Things You Might Not Know About RecyclerView
Favicon
Implementing ListView inside RecyclerView and observing LiveData
Favicon
The Simplest Recycler EmptyView
Favicon
Learn to make list using RecyclerView
Favicon
ExpandableRecyclerView: Expandable Items With Room Database in Kotlin
Favicon
Generic RecyclerViewAdapter
Favicon
Android RecyclerView StickyHeader without external library
Favicon
Doing Android Long Lists Effectively
Favicon
Android RecyclerView single choice adapter
Favicon
Scrolling... Scrolling... RecyclerView for Android with Kotlin!
Favicon
Small RecyclerView XML feature
Favicon
How to use a RecyclerView to show images from storage
Favicon
RecyclerView - II, adding onClick to list items
Favicon
GKE: Ingress Controllers
Favicon
RecyclerView - I
Favicon
Merge Multiple adapters with MergeAdapter

Featured ones: