Adapter / Material Design / RecyclerView / ViewHolder

Material – Part 4

Previously in this series we’ve looked at applying some aspects of Material design to our simple RSS app. In this article we’re going to look at replacing our ListView implementation with RecyclerView. While this won’t have any effect of the user, it will be an enabler which will allow us to apply some more material goodness which would be difficult to achieve with ListView.

Image Source: http://www.google.com/design/spec/material-design/introduction.html#introduction-goals
Image Source: Google Material Design
I’m not going to provide a full explanation of RecyclerView here because it has been covered in detail elsewhere:

Also worthy of a mention is Lucas Rocha’s Two Way View which can make implementing RecyclerView even easier still. The only reason I am not using Lucas’ really useful library is that I prefer to focus on what is actually part of the Android SDK – I would certainly consider it for a project whose purpose was not to explain aspects of the Android SDK.

The first thing that we need to do is include the recyclerview support library in our build.gradle:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 21
    buildToolsVersion "21.1.1"

    defaultConfig {
        applicationId "com.stylingandroid.materialrss"
        minSdkVersion 14
        targetSdkVersion 21
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile 'com.mcxiaoke.volley:library:1.0.7'
    compile "com.android.support:appcompat-v7:21.0.0"
    compile "com.android.support:cardview-v7:21.0.0"
    compile "com.android.support:recyclerview-v7:21.0.0"
}

apply from: "${project(':').projectDir}/config/static_analysis.gradle"

Next we need to change FeedAdapter to implement RecyclerView.Adapter instead of ArrayAdapter:

public class FeedAdapter extends RecyclerView.Adapter<FeedAdapter.ViewHolder> {
    private DateFormat dateFormat = SimpleDateFormat.getDateInstance(SimpleDateFormat.MEDIUM, Locale.getDefault());

    private List<Item> items;
    private ItemClickListener itemClickListener;

    public FeedAdapter(List<Item> objects, @NonNull ItemClickListener itemClickListener) {
        this.items = objects;
        this.itemClickListener = itemClickListener;
    }

    public interface ItemClickListener {
        public void itemClicked(Item item);
    }
    .
    .
    .
}

So we change the base class to RecyclerView.Adapter which is a generic class and we’re specifying a generic type of FeedAdapter.ViewHolder which is an inner class which will cover in due course. The constructor takes a list of objects to display in the list, and an ItemClickListener which will receive callbacks when the user clicks a list item.

Next we have to override getItemCount() to return the number of list items:

public class FeedAdapter extends RecyclerView.Adapter<FeedAdapter.ViewHolder> {
    .
    .
    .
    @Override
    public int getItemCount() {
        return items.size();
    }
    .
    .
    .
}

Next we need to override onCreateViewHolder() which will get called whenever the RecyclerView does not have have a view that it can recycle. In this we should simply inflate our view and create a ViewHolder (we’ll cover this in a moment) which will be used to hold references to the Views within the layout that we’ll later need to bind data to:

public class FeedAdapter extends RecyclerView.Adapter<FeedAdapter.ViewHolder> {
    .
    .
    .
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int position) {
        Context context = viewGroup.getContext();
        View parent = LayoutInflater.from(context).inflate(R.layout.feed_list_item, viewGroup, false);
        return ViewHolder.newInstance(parent);
    }
    .
    .
    .
}

The next method we need to override is onBindViewHolder() which will perform the data binding for the layout. Typically this will get called much more than onCreateViewHolder() because RecyclerView will re-use the layouts

public class FeedAdapter extends RecyclerView.Adapter<FeedAdapter.ViewHolder> {
    .
    .
    .
    @Override
    public void onBindViewHolder(ViewHolder viewHolder, int position) {
        final Item item = items.get(position);
        viewHolder.setTitle(item.getTitle());
        viewHolder.setDescription(Html.fromHtml(item.getDescription()));
        viewHolder.setDate(dateFormat.format(new Date(item.getPubDate())));
        viewHolder.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                itemClickListener.itemClicked(item);
            }
        });
    }
    .
    .
    .
}

Finally we have the ViewHolder which means that we perform the relatively heavy task of finding the views within the layout once, and hold references to them. This gets passed in to onBindViewHolder() so that we can easily bind the data to the Views:

public class FeedAdapter extends RecyclerView.Adapter<FeedAdapter.ViewHolder> {
    .
    .
    .
    public static final class ViewHolder extends RecyclerView.ViewHolder {
        private final View parent;
        private final TextView title;
        private final TextView description;
        private final TextView date;

        public static ViewHolder newInstance(View parent) {
            TextView title = (TextView) parent.findViewById(R.id.feed_item_title);
            TextView description = (TextView) parent.findViewById(R.id.feed_item_description);
            TextView date = (TextView) parent.findViewById(R.id.feed_item_date);
            return new ViewHolder(parent, title, description, date);
        }

        private ViewHolder(View parent, TextView title, TextView description, TextView date) {
            super(parent);
            this.parent = parent;
            this.title = title;
            this.description = description;
            this.date = date;
        }

        public void setTitle(CharSequence text) {
            title.setText(text);
        }

        public void setDescription(CharSequence text) {
            description.setText(text);
        }

        public void setDate(CharSequence text) {
            date.setText(text);
        }

        public void setOnClickListener(View.OnClickListener listener) {
            parent.setOnClickListener(listener);
        }
    }
    .
    .
}

That’s the Adapter switched over. Next we need to switch from ListView to RecyclerView in FeedListActivity and we’ll do that in the next article.

Apologies that there are no images or source code accompanying this article but what we have done thus far will nether compile nor run, so there’s nothing to see, and I don’t like publishing code which will not compile. This will all be rectified in the next article – I promise!

© 2014 – 2015, Mark Allison. All rights reserved.

Copyright © 2014 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.