April 13, 2015 / SharpMobileCode / 1 Comment
I learned something new last week that I felt I probably should have learned a long time ago. As I know from my previous experience in Android development, ListViews only scroll vertically. In the past, there was no out of the box support for horizontal ListViews in Android. Developers had to create them manually or use 3rd party libraries hosted on GitHub. If you examined a 3rd parties source code, you would see implementing a horizontal ListView is not an easy task. With the release of Android 4.4 KitKat (API Level 19), a new control was added to the Android SDK. This new control is called a RecyclerView, and is available via the Android Support Library v7.
Luckily for us Xamarin Developers, we have out of the box binding to this library, so we can use this just like native Java developers. In this post I’m going to show you how to implement a vertical ListView (just like a standard ListView), as well as a Horizontal ListView. But we are not going to use the old legacy ListView control or a 3rd party library. We are going to use the now out of the box RecyclerView to implement both. The good news is that the since this is available in the Android Support Library v7, this control can run on devices from the current version of Android, all the way down to API Level 7 (Android 2.1 Eclair). So this control remains backwards compatible with almost any device out there. So let’s get coding!
What is a RecyclerView?
First thing’s first. What is a RecyclerView? When I was looking for a modern implementation for a Horizontal ListView, I found it difficult to find one that met my client’s requirements. The majority had abandoned their projects on GitHub without explanation, so their code was somewhat outdated. But during my research I came across this little nugget. Even the native Java developers are starting to abandon their own custom libraries for ListViews in favor for something called a RecyclerView. The name does not make it obvious on what it does, so I did some more research. I come to find out that a RecyclerView is a really neat, and memory efficient, way to display large data sets in Android. You can display them Linearly, or in a Grid. It also supports the Adapter pattern, just like regular ListViews.
Unlike ListViews, RecyclerViews require use of the ViewHolder Pattern for memory efficiency. I’m not going to go into great detail on how a RecyclerView works. Xamarin has some really good documentation on how a RecyclerView works. So I highly recommend you read the Xamarin documentation before proceeding, especially if you have not used the ViewHolder Pattern in the past with ListViews. But in summary, RecyclerViews are the official replacement for ListViews. I admit I’m so late in the game in realizing this, but now it’s time to move on!
Android Project Requirements For RecyclerView
As I said previously, the RecyclerView can run on devices from Android 2.1 (Eclair) and above. However, there are some compile time requirements you need need to be aware of as a Developer. First you need to download the Android Support Library SDK using the Android SDK Manager. You will need at least Revision 21. If you don’t have revision 21, you need to update to the latest revision available for download.
Second, you’ll need to add a Nuget package called “Xamarin Support Library v7 RecyclerView”. This is the Xamarin C# bindings that allows you to access the RecyclerView in the Android Support Library v7.
Implementing a RecyclerView
Now that we have the necessary libraries for using a RecyclerView, we can start implementing vertical and horizontal ListView functionality. As you’ve probably read from the Xamarin documentation above, there are a few classes we need to implement. I’ll admit, implementing a RecyclerView is a little bit more complex than a ListView, but it’s not that much. You only have to implement one extra class, and add an EventHandler for ItemClick events, but it’s not that much code. Trust me, the flexibility you gain from replacing ListView with RecyclerViews is well worth the very little extra work you have to do.
So here are the steps you need to do in order to incorporate a RecyclerView. They are very similar to implementing an old legacy ListView, but with one extra strep. So let’s do everything step by step for completeness.
Step 1: Create your cell item XML Layout(s)
The first step is to create your XML Layout(s) for each individual cell item of your RecyclerView. In this example app, I’m going to create a Star Trek crew manifest application (**Nerd Alert!!**). The application will show a list of Starfleet crew members in a vertical scrolling list when the device is in Portrait, and horizontally when the device is in Landscape. This is a simple application that will not use any 3rd party libraries or tricks. All of these things are now standard to the Android SDK. Also note that Layouts can be just about anything you can design. This is just a simple example layout. Here’s the layout for the Portrait orientation for each individual crew member item.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
<LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”
android:orientation=“vertical”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:paddingRight=“10dp”
android:paddingBottom=“20dp”>
<RelativeLayout
android:orientation=“horizontal”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:minWidth=“25px”
android:minHeight=“25px”
android:paddingBottom=“4dp”>
<ImageView
android:id=“@+id/crewMemberItem_Photo”
android:layout_width=“120dp”
android:layout_height=“120dp”
android:layout_alignParentLeft=“true”
android:layout_centerVertical=“false”
android:padding=“5dp”
android:src=“@drawable/picard”/>
<TextView
android:id=“@+id/crewMemberItem_Name”
android:layout_toRightOf=“@id/crewMemberItem_Photo”
android:text=“Crew Member Name”
android:textAppearance=“?android:attr/textAppearanceLarge”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:paddingLeft=“5dp”/>
<TextView
android:id=“@+id/crewMemberItem_RankPosting”
android:layout_toRightOf=“@id/crewMemberItem_Photo”
android:layout_below=“@id/crewMemberItem_Name”
android:text=“Position”
android:textAppearance=“?android:attr/textAppearanceSmall”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:paddingLeft=“5dp”/>
</RelativeLayout>
</LinearLayout>
|
The resulting XML layout should look something like this:
The next thing we need to do is is create what the layout will look like when we turn our device horizontally. The XML layout will look something like below. It is very similar to our vertical view, but with some slight differences.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
<LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”
android:orientation=“vertical”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:paddingRight=“10dp”>
<ImageView
android:id=“@+id/crewMemberItem_Photo”
android:layout_width=“120dp”
android:layout_height=“120dp”
android:layout_gravity=“center_horizontal”
android:src=“@drawable/picard”/>
<TextView
android:id=“@+id/crewMemberItem_Name”
android:text=“Jean-Luc Picard”
android:textAppearance=“?android:attr/textAppearanceLarge”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_gravity=“center_horizontal”/>
<TextView
android:id=“@+id/crewMemberItem_RankPosting”
android:text=“Captain, USS Enterprise”
android:textAppearance=“?android:attr/textAppearanceSmall”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:paddingLeft=“5dp”
android:layout_gravity=“left”/>
</LinearLayout>
|
The landscape layout should look like this:
Step 2: Create your RecyclerView XML Layout
If you have implemented ListViews in the past, this step is very similar. Instead of using the <ListView> tag, you need to use the <android.support.v7.widget.RecyclerView> tag. Below is the code I use to define our RecyclerView layout. In this sample, this is my Main Activity Layout (Main.axml).
1
2
3
4
5
6
7
8
9
10
11
|
<LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”
android:orientation=“vertical”
android:layout_width=“fill_parent”
android:layout_height=“fill_parent”
android:gravity=“center”>
<android.support.v7.widget.RecyclerView
android:id=“@+id/mainActivity_recyclerView”
android:scrollbars=“vertical”
android:layout_width=“match_parent”
android:layout_height=“match_parent”/>
</LinearLayout>
|
Step 3: Create your ViewHolder for Your Cell Item Layout.
So far, the first two steps are the same as if we were implementing a legacy ListView. Step 3 here is an additional step we need to take in order to implement a RecyclerView. We need to create our ViewHolder for each individual Cell (Row) item. However, this step was purely optional for ListViews, but highly encouraged since it improves performance and memory management when scrolling through ListViews with large data sets. But if the ViewHolder pattern is not new to you, than this step is just like implementing it for a ListView. Again, if you are not familiar with the ViewHolder Pattern, I suggest you read Xamarin’s blog post about it.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
using System;
using Android.Support.V7.Widget;
using Android.Views;
using Android.Widget;
namespaceListViewsReinvented.Droid
{
publicclassCrewMemberItemViewHolder:RecyclerView.ViewHolder
{
publicImageViewCrewMemberPhoto{get;set;}
publicTextViewCrewMemberName{get;set;}
publicTextViewRankAndPosting{get;set;}
publicCrewMemberItemViewHolder(View itemView,Action<int>listener)
:base(itemView)
{
CrewMemberPhoto=itemView.FindViewById<ImageView>(Resource.Id.crewMemberItem_Photo);
CrewMemberName=itemView.FindViewById<TextView>(Resource.Id.crewMemberItem_Name);
RankAndPosting=itemView.FindViewById<TextView>(Resource.Id.crewMemberItem_RankPosting);
itemView.Click+=(sender,e)=>listener(base.Position);
}
}
}
|
There’s a few things to note here. First, our ViewHolder class needs to inherit from RecyclerView.ViewHolder. This is defined in the Android Support Library v7. Second, (and this is where the extra works comes into play) we need to implement a constructor that accepts two parameters. The first parameter is the item View that is being displayed. The second is an Action, also known as an Event Handler, that will fire when our itemView is clicked on. This is the same as wiring the ListView.ItemClick event handler in legacy ListViews. In this case, our ViewHolder needs to know about any clicks (taps) that may happen.
Step 4: Create Our Adapter For The RecyclerView
The RecyclerView control, just like a ListView, follows the adapter pattern, so we need to create an Adapter for it. Here is an example of our Adapter for our RecyclerView.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
|
using System;
using System.Collections.Generic;
using Android.Graphics;
using Android.Support.V7.Widget;
using Android.Views;
using Android.Content.Res;
namespaceListViewsReinvented.Droid
{
publicclassCrewMemberRecyclerViewAdapter:RecyclerView.Adapter
{
//Create an Event so that our our clients can act when a user clicks
//on each individual item.
publicevent EventHandler<int>ItemClick;
privateList<CrewMember>_crewMembers;
privatereadonly ImageManager _imageManager;
publicCrewMemberRecyclerViewAdapter(List<CrewMember>crewMembers,Resources resources)
{
_crewMembers=crewMembers;
_imageManager=newImageManager(resources);
}
//Must override, just like regular Adapters
publicoverrideintItemCount
{
get
{
return_crewMembers.Count;
}
}
//Must override, this inflates our Layout and instantiates and assigns
//it to the ViewHolder.
publicoverride RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent,intviewType)
{
//Inflate our CrewMemberItem Layout
View itemView=LayoutInflater.From(parent.Context).Inflate(Resource.Layout.CrewMemberItem,parent,false);
//Create our ViewHolder to cache the layout view references and register
//the OnClick event.
varviewHolder=newCrewMemberItemViewHolder(itemView,OnClick);
returnviewHolder;
}
//Must override, this is the important one. This method is used to
//bind our current data to your view holder. Think of this as the equivalent
//of GetView for regular Adapters.
publicoverride async voidOnBindViewHolder(RecyclerView.ViewHolder holder,intposition)
{
varviewHolder=holder asCrewMemberItemViewHolder;
varcurrentCrewMember=_crewMembers[position];
//Bind our data from our data source to our View References
viewHolder.CrewMemberName.Text=currentCrewMember.Name;
viewHolder.RankAndPosting.Text=String.Format(“{0}n{1}”,currentCrewMember.Rank,currentCrewMember.Posting);
varphotoBitmap=await _imageManager.GetScaledDownBitmapFromResourceAsync(currentCrewMember.PhotoResourceId,120,120);
viewHolder.CrewMemberPhoto.SetImageBitmap(photoBitmap);
}
//This will fire any event handlers that are registered with our ItemClick
//event.
privatevoidOnClick(intposition)
{
if(ItemClick!=null)
{
ItemClick(this,position);
}
}
//Since this example uses a lot of Bitmaps, we want to do some house cleaning
//and make them available for garbage collecting as soon as possible.
protectedoverride voidDispose(booldisposing)
{
base.Dispose(disposing);
if(_imageManager!=null)
{
_imageManager.Dispose();
}
}
}
}
|
There are a few things to note here. First, our Adapter needs to inherit from RecyclerView.Adapter. This is just like inheriting from BaseAdapter<T> when implementing a legacy ListView. Second, we need to override the following methods:
- int ItemCount
- RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
- void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
The ItemCount property is the equivalent of overriding the Count property on a legacy ListView Adapter. It returns the number of items that our RecyclerView will display.
The OnCreateViewHolder() method is new, if you are familiar with legacy ListViews. This is used to inflate our ItemView layout (CrewMemberItem.axml), create an instance of our ViewHolder, and then caches our inflated ItemView to your ViewHolder. This allows Android to recycle our inflated ViewItem for any future list items that will be shown on our device screen.
The OnBindViewHolder() method is the equivalent of the GetView() method on a legacy ListView Adapter. Well, sort of. In ListViews Adapters, the GetView was responsible for inflating your ItemView, and then binding our source data to the view. But with OnBindViewHolder() this is not the case. We don’t inflate any Views here. Instead, a ViewHolder is passed in as a parameter. Remember, that the ViewHolder has already been created, and it is now ready for us to use. In this method, we take our ViewHolder, and then populate it with data from our current list item (position in our data source).
Lastly, we need to do some extra work here. Unlike legacy ListView Adapters, RecyclerView.Adapters do not support “OnClick” (when the user taps on an item) events. This is a pretty big bummer, but it’s not that much work to add on to our Adapter. If you notice on Line 14 in the code above, I’ve created an EventHandler<int> that will allow your Activities to wire an event handler to handle “OnClick” events. In addition, the OnClick() method (lines 63-68) will fire an event handlers that are wired to our event. This is only a few extra lines of code, but it’s a real bummer that this isn’t built into the SDK out of the box.
Step 5: Assign a Layout Manager And Adapter to the RecyclerView
This is the final step. In our Main Activity (or any activity), we create an instance of our RecyclerView adapter. We then assign our adapter to our RecyclerView. This is just like working with ListViews. But we need to do one more thing before Android can display our RecyclerView. And this is where I jumped out of my chair and yelled “This is freaking awesome!” But first, here is the code for our Main Activity that creates inflates our RecyclerView.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
using Android.App;
using Android.OS;
using Android.Support.V7.Widget;
using Android.Content;
namespaceListViewsReinvented.Droid
{
[Activity(Label=“ListViews Reinvented”,MainLauncher=true,Icon=“@drawable/icon”)]
publicclassMainActivity:Activity
{
privateRecyclerView _recyclerView;
privateRecyclerView.LayoutManager _layoutManager;
privateCrewMemberRecyclerViewAdapter _adapter;
privateProgressDialog _progressDialog;
protectedoverride async voidOnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.Main);
_progressDialog=newProgressDialog(this);
_progressDialog.SetProgressStyle(ProgressDialogStyle.Spinner);
_progressDialog.SetMessage(“Loading crew manifest . . .”);
_progressDialog.Show();
//If the device is portrait, then show the RecyclerView in a vertical list,
//else show it in horizontal list.
_layoutManager=Resources.Configuration.Orientation==Android.Content.Res.Orientation.Portrait
?newLinearLayoutManager(this,LinearLayoutManager.Vertical,false)
:newLinearLayoutManager(this,LinearLayoutManager.Horizontal,false);
//Experiement with a GridLayoutManger! You can create some cool looking UI!
//This create a gridview with 2 rows that scrolls horizontally.
// _layoutManager = new GridLayoutManager(this, 2, GridLayoutManager.Horizontal, false);
//Create a reference to our RecyclerView and set the layout manager;
_recyclerView=FindViewById<RecyclerView>(Resource.Id.mainActivity_recyclerView);
_recyclerView.SetLayoutManager(_layoutManager);
//Get our crew member data. This could be a web service.
SharedData.CrewManifest=await CrewManifest.GetAllCrewAsync();
//Create the adapter for the RecyclerView with our crew data, and set
//the adapter. Also, wire an event handler for when the user taps on each
//individual item.
_adapter=newCrewMemberRecyclerViewAdapter(SharedData.CrewManifest,this.Resources);
_adapter.ItemClick+=OnItemClick;
_recyclerView.SetAdapter(_adapter);
_progressDialog.Dismiss();
}
privatevoidOnItemClick(objectsender,intposition)
{
varcrewProfileIntent=newIntent(this,typeof(CrewMemberProfileActivity));
crewProfileIntent.PutExtra(“index”,position);
crewProfileIntent.PutExtra(“imageResourceId”,SharedData.CrewManifest[position].PhotoResourceId);
StartActivity(crewProfileIntent);
}
}
|
The first thing we need to do is create an instance of a RecyclerView.LayoutManager (lines 30-32). A LayoutManger tells Android how to display our items for our RecyclerView. In this case, we want to the equivalent functionally of a legacy ListView. We create the layout manager like this:
newLinearLayoutManager(this,LinearLayoutManager.Vertical,false);
The LinearLayoutManager tells Android that our data should be displayed in a list. The second parameter of the constructor tells the Android OS that we want our list to scroll vertically. And if we wanted to display our list Horizontally, we just pass in the LinearLayoutManager.Horizontalconstant as a parameter! It’s that easy! No third party libraries are needed and this is now part of the Android SDK! So, on lines 30-32, I say if the device is in Portrait, then show a vertical list, else show the list horizontally. So the final output should look like this.
But There’s More!
Now that I’ve shown you how to replace your existing ListViews with the new RecyclerView, a RecyclerView also has other LayoutMangers. Another cool one is to use a GridLayoutManger. Comment out the code that assigns the LinearLayoutManager, and then uncomment the code that creates a GridLayoutManager instead (Line 36). Now the RecyclerView displays a Grid that scrolls Horizontally (or Vertically if you choose). So now with one class, we can create a Vertical/Horizontal linear list or create a Vertical/Horizontal GridView with just one class: the RecyclerView. It’s about freaking time!
Full Source Code
You can download the full source code to my sample application on GitHub. Fell free to look at the source code and learn the details of this awesome new (to me) control. I can tell you that from now on, I’ll be using RecyclerViews instead of ListViews. Feel free to try different layout managers and experiment.
https://github.com/SharpMobileCode/ListViewsReinvented
Summary
In this article, I explained how to use the new RecyclerView to display a list of data in a horizontal or vertical fashion. The RecyclerView now replaces the legacy ListView, as the ListView only supported a linear vertical display. Implementing a RecyclerView is not that much different then implementing a ListView. There are only just a few extra things we need to implement, such as the ViewHolder, and the “OnClick” events, but the very little extra work is a small price to pay for the flexibility and awesomeness the RecyclerView provides. I hope you learned something new, as I did. Download my sample application from GitHub and code away! And please feel free to leave a comment with any feedback you have!
#Horizontal ListView#ListView#monodroid#Xamarin#Xamarin.Android