





















































In this article by Nilanchala Panigrahy, author of the book Xamarin Mobile Application Development for Android Second Edition, will walk you through the activities related to creating and populating a ListView, which includes the following topics:
(For more resources related to this topic, see here.)
It is technically "possible to create and attach the user interface elements to your activity using C# code. However, it is a bit of a mess. We will go with the most common approach by declaring the XML-based layout.
Rather than deleting these files, let's give them more appropriate names and remove unnecessary content as follows:
We have just added a ListView widget to POIList.axml. Let's now open the Properties pad view in the designer window and edit some of its attributes:
There are five buttons at the top of the pad that switch the set of properties being edited. The @+id notation notifies the compiler that a new resource ID needs to be created to identify the widget in API calls, and listView1 identifies the name of the constant. Now, perform the following steps:
Prior to API level 8, fill_parent was used instead of match_parent to accomplish the same effect. In API level 8, fill_parent was deprecated and replaced with match_parent for clarity. Currently, both the constants are defined as the same value, so they have exactly the same effect. However, fill_ parent may be removed from the future releases of the API; so, going forward, match_parent should be used.
So far, we have added a ListView to RelativeLayout, let's now add a Progress Bar to the center of the screen.
The View.Visibility property" allows you to control whether a view is visible or not. It is based on the ViewStates enum, which defines the following values:
Value | Description |
Gone | This value tells the parent ViewGroup to treat the View as though it does not exist, so no space will be allocated in the layout |
Invisible | This value tells the parent ViewGroup to hide the content for the View; however, it occupies the layout space |
Visible | This value tells the parent ViewGroup to display the content of the View |
Click on the Source tab to switch the IDE context from visual designer to code, and see what we have built so far. Notice that the following code is generated for the POIList.axml layout:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
p1_layout_width="match_parent"
p1_layout_height="match_parent"
p1_id="@+id/relativeLayout1">
<ListView
p1_minWidth="25px"
p1_minHeight="25px"
p1_layout_width="match_parent"
p1_layout_height="match_parent"
p1_id="@+id/poiListView" />
<ProgressBar
p1_layout_width="wrap_content"
p1_layout_height="wrap_content"
p1_id="@+id/progressBar"
p1_layout_centerInParent="true"
p1_visibility="gone" />
</RelativeLayout>
When we created the" POIApp solution, along with the default layout, a default activity (MainActivity.cs) was created. Let's rename the MainActivity.cs file "to POIListActivity.cs:
namespace POIApp
{
[Activity (Label = "POIApp", MainLauncher = true, Icon = "@
drawable/icon")]
public class POIListActivity : Activity
{
protected override void OnCreate (Bundle savedInstanceState)
{
base.OnCreate (savedInstanceState);
}
}
}
namespace POIApp
{
[Activity (Label = "POIApp", MainLauncher = true, Icon = "@drawable/icon")]
public class POIListActivity : Activity
{
protected override void OnCreate (Bundle savedInstanceState)
{
base.OnCreate (savedInstanceState);
SetContentView (Resource.Layout.POIList);
}
}
}
Notice that in the preceding code snippet, the POIListActivity class uses some of the [Activity] attributes such as Label, MainLauncher, and Icon. During the build process, Xamarin.Android uses these attributes to create an entry in the AndroidManifest.xml file. Xamarin makes it easier by allowing all of the Manifest properties to set using attributes so that you never have to modify them manually in AndroidManifest.xml.
So far, we have "declared an activity and attached the layout to it. At this point, if you run the app on your Android device or emulator, you will notice that a blank screen will be displayed.
We now turn our attention to the" layout for each row in the ListView widget. "The" Android platform provides a number of default layouts out of the box that "can be used with a ListView widget:
Layout | Description |
SimpleListItem1 | A "single line with a single caption field |
SimpleListItem2 | A "two-line layout with a larger font and a brighter text color for the first field |
TwoLineListItem | A "two-line layout with an equal sized font for both lines and a brighter text color for the first line |
ActivityListItem | A "single line of text with an image view |
All of the preceding three layouts provide a pretty standard design, but for more control over content layout, a custom layout can also be created, which is what is needed for poiListView.
To create a new layout, perform the following steps:
Before we proceed to lay out the design for each of the row items in the list, we must draw on a piece of paper and analyze how the UI will look like. In our example, the POI data will be organized as follows:
There are a "number of ways to achieve this layout, but we will use RelativeLayout to achieve the same result. There is a lot going on in this diagram. Let's break it down as follows:
Now, our task is to get this definition into POIListItem.axml. The next few sections describe how to "accomplish this using the Content view of the designer when feasible and the Source view when required.
The RelativeLayout layout "manager allows its child views to be positioned relative to each other or relative to the container or another container. In our case, for building the row layout, as shown in the preceding diagram, we can use RelativeLayout as a top-level view group. When the POIListItem.axml layout file was created, by default a top-level LinearLayout was added. First, we need to change the top-level ViewGroup to RelativeLayout. The following section will take you through the steps to complete the layout design for the POI list row:
The padding property controls how much space will be placed around each item as a margin, and the height determines the height of each list row. Setting the Layout Width option to match_ parent will cause the POIListItem content to consume the entire width of the screen, while setting the Layout Height option to wrap_content will cause each row to be equal to the longest control.
<RelativeLayout
p1_minWidth="25px"
p1_minHeight="25px"
p1_layout_width="match_parent"
p1_layout_height="wrap_content"
p1_id="@+id/relativeLayout1"
p1_padding="5dp"/>
Android runs on a "variety of devices that offer different screen sizes and densities. When specifying dimensions, you can use a number of different units, including pixels (px), inches (in), and density-independent pixels (dp). Density-independent pixels are abstract units based on 1 dp being 1 pixel on a 160 dpi screen. At runtime, Android will scale the actual size up or down based on the actual screen density. It is a best practice to specify dimensions using density-independent pixels.
The ImageView widget in "Android is used to display the arbitrary image for different sources. In our case, we will download the images from the server and display them in the list. Let's add an ImageView widget to the left-hand side of the layout and set the following configurations:
p1:layout_centerVertical="true"
<ImageView
p1_src="@android:drawable/ic_menu_gallery"
p1_layout_width="65dp"
p1_layout_height="65dp"
p1_layout_marginRight="5dp"
p1_id="@+id/poiImageView" />
LinearLayout is one of the "most basic layout managers that organizes its child "views either horizontally or vertically based on the value of its orientation property. Let's add a LinearLayout view group that will be used to lay out "the POI name and address data as follows:
<LinearLayout
p1_orientation="vertical"
p1_minWidth="25px"
p1_minHeight="25px"
p1_layout_width="wrap_content"
p1_layout_height="wrap_content"
p1_layout_toRightOf="@id/poiImageView"
p1_id="@+id/linearLayout1"
p1_layout_centerVertical="true" />
Add the TextView classes to display the POI name and address:
Scale-independent pixels (sp) "are like dp units, but they are also scaled by the user's font size preference. Android allows users to select a font size in the Accessibility section of Settings. When font sizes are specified using sp, Android will not only take into account the screen density when scaling text, but will also consider the user's accessibility settings. It is recommended that you specify font sizes using sp.
<string name="poi_name_hint">POI Name</string>
<string name="address_hint">City, State, Postal Code.</string>
<TextView
p1_layout_width="match_parent"
p1_layout_height="wrap_content"
p1_id="@+id/nameTextView "
p1_textSize="20sp"
p1_text="@string/app_name" />
<TextView
p1_text="@string/address_hint"
p1_layout_width="match_parent"
p1_layout_height="wrap_content"
p1_id="@+id/addrTextView "
p1_textSize="14sp" />
Add a TextView to show "the distance from POI:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
p1_minWidth="25px"
p1_minHeight="25px"
p1_layout_width="match_parent"
p1_layout_height="wrap_content"
p1_id="@+id/relativeLayout1"
p1_padding="5dp">
<ImageView
p1_src="@android:drawable/ic_menu_gallery"
p1_layout_width="65dp"
p1_layout_height="65dp"
p1_layout_marginRight="5dp"
p1_id="@+id/poiImageView" />
<LinearLayout
p1_orientation="vertical"
p1_layout_width="wrap_content"
p1_layout_height="wrap_content"
p1_layout_toRightOf="@id/poiImageView"
p1_id="@+id/linearLayout1"
p1_layout_centerVertical="true">
<TextView
p1_layout_width="match_parent"
p1_layout_height="wrap_content"
p1_id="@+id/nameTextView "
p1_textSize="20sp"
p1_text="@string/app_name" />
<TextView
p1_text="@string/address_hint"
p1_layout_width="match_parent"
p1_layout_height="wrap_content"
p1_id="@+id/addrTextView "
p1_textSize="14sp" />
</LinearLayout>
<TextView
p1_text="@string/distance_hint"
p1_layout_width="wrap_content"
p1_layout_height="wrap_content"
p1_id="@+id/textView1"
p1_layout_centerVertical="true"
p1_layout_alignParentRight="true" />
</RelativeLayout>
The first class that is needed is the one that represents the primary focus of the application, a PointofInterest class. POIApp will allow the following attributes "to be captured for the Point Of Interest app:
The POI entity class can be nothing more than a simple .NET class, which houses these attributes.
To create a POI entity class, perform the following steps:
The following code snippet is from POIAppPOIAppPointOfInterest.cs from the code bundle available for this article:
public class PointOfInterest
{
public int Id { get; set;}
public string Name { get; set; }
public string Description { get; set; }
public string Address { get; set; }
public string Image { get; set; }
public double? Latitude { get; set; }
public double? Longitude { get; set; }
}
Note that the Latitude and Longitude attributes are all marked as nullable. In the case of latitude and longitude, (0, 0) is actually a valid location so a null value indicates that the attributes have never been set.
All the adapter "views such as ListView and GridView use an Adapter that acts as a bridge between the data and views. The Adapter iterates through the content and generates Views for each data item in the list.
The Android SDK provides three different adapter implementations such as ArrayAdapter, CursorAdapter, and SimpleAdapter. An ArrayAdapter expects an array or a list as input, while CursorAdapter accepts the instance of the Cursor, and SimpleAdapter maps the static data defined in the resources. The type of adapter that suits your app need is purely based on the input data type.
The BaseAdapter is the generic implementation for all of the three adapter types, and it implements the IListAdapter, ISpinnerAdapter, and IDisposable interfaces. This means that the BaseAdapter can be used for ListView, GridView, "or Spinners.
For POIApp, we will create a subtype of BaseAdapter<T> as it meets our specific needs, works well in many scenarios, and allows the use of our custom layout.
In order to create "POIListViewAdapter, we will start by creating a custom adapter "as follows:
Now that the adapter class has been created, we need to provide a constructor "and implement four abstract methods.
Let's implement a "constructor that accepts all the information we will need to work with to populate the list.
Typically, you need to pass at least two parameters: an instance of an activity because we need the activity context while accessing the standard common "resources and an input data list that can be enumerated to populate the ListView. The following code shows the constructor from the code bundle:
private readonly Activity context;
private List<PointOfInterest> poiListData;
public POIListViewAdapter (Activity _context, List<PointOfInterest> _poiListData)
:base()
{
this.context = _context;
this.poiListData = _poiListData;
}
The BaseAdapter<T> class "provides an abstract definition for a read-only Count property. In our case, we simply need to provide the count of POIs as provided in poiListData. The following code example demonstrates the implementation from the code bundle:
public override int Count {
get {
return poiListData.Count;
}
}
The BaseAdapter<T> class "provides an abstract definition for a method that returns a long ID for a row in the data source. We can use the position parameter to access a POI object in the list and return the corresponding ID. The following code example demonstrates the implementation from the code bundle:
public override long GetItemId (int position)
{
return position;
}
The BaseAdapter<T> class "provides an abstract definition for an index getter method that returns a typed object based on a position parameter passed in as an index. We can use the position parameter to access the POI object from poiListData and return an instance. The following code example demonstrates the implementation from the code bundle:
public override PointOfInterest this[int index] {
get{
return poiListData [index];
}
}
The BaseAdapter<T> class "provides an abstract definition for GetView(), which returns a view instance that represents a single row in the ListView item. As in other scenarios, you can choose to construct the view entirely in code or to inflate it from a layout file. We will use the layout file we previously created. The following code example demonstrates inflating a view from a layout file:
view = context.LayoutInflater.Inflate (Resource.Layout.POIListItem, null, false);
The first parameter of Inflate is a resource ID and the second is a root ViewGroup, which in this case can be left null since the view will be added to the ListView item when it is returned.
The GetView() method is called" for each row in the source dataset. For datasets with large numbers of rows, hundreds, or even thousands, it would require a great deal of resources to create a separate view for each row, and it would seem wasteful since only a few rows are visible at any given time. The AdapterView architecture addresses this need by placing row Views into a queue that can be reused as they" scroll out of view of the user. The GetView() method accepts a parameter named convertView, which is of type view. When a view is available for reuse, convertView will contain a reference to the view; otherwise, it will be null and a new view should be created. The following code example depicts the use of convertView to facilitate the reuse of row Views:
var view = convertView;
if (view == null){
view = context.LayoutInflater.Inflate (Resource.Layout.POIListItem, null);
}
Now that we have an instance of the "view, we need to populate the fields. The View class defines a named FindViewById<T> method, which returns a typed instance of a widget contained in the view. You pass in the resource ID defined in the layout file to specify the control you wish to access.
The following code returns access to nameTextView and sets the Text property:
PointOfInterest poi = this [position];
view.FindViewById<TextView>(Resource.Id.nameTextView).Text = poi.Name;
Populating addrTextView is slightly more complicated because we only want to use the portions of the address we have, and we want to hide the TextView if none of the address components are present.
The View.Visibility property allows you to control the visibility property "of a view. In our case, we want to use the ViewState.Gone value if none of "the components of the address are present. The following code shows the "logic in GetView:
if (String.IsNullOrEmpty (poi.Address)) {
view.FindViewById<TextView> (Resource.Id.addrTextView).Visibility = ViewStates.Gone;
} else{
view.FindViewById<TextView>(Resource.Id.addrTextView).Text = poi.Address;
}
Populating the value for the distance text view requires an understanding of the location services. We need to do some calculation, by considering the user's current location with the POI latitude and longitude.
Image downloading and" processing is a complex task. You need to consider the various aspects, such as network logic, to download images from the server, caching downloaded images for performance, and image resizing for avoiding the memory out conditions. Instead of writing our own logic for doing all the earlier mentioned tasks, we can use UrlImageViewHelper, which is a free component available in the Xamarin Component Store.
The Xamarin Component Store provides a set of reusable components, "including both free and premium components, that can be easily plugged into "any Xamarin-based application.
The following steps will walk you "through the process of adding a component from the Xamarin Component Store:
var imageView = view.FindViewById<ImageView> (Resource.Id.poiImageView);
if (!String.IsNullOrEmpty (poi.Address)) {
Koush.UrlImageViewHelper.SetUrlDrawable (imageView, poi.Image, Resource.Drawable.ic_placeholder);
}
Let us examine how the preceding code snippet works:
Android apps must be "granted permissions while accessing certain features, such as downloading data from the Internet, saving an image in storage, and so on. You must specify the permissions that an app requires in the AndroidManifest.xml file. This allows the installer to show potential users the set of permissions an app requires at the time of installation.
To set the appropriate permissions, perform the following steps:
In this article, we covered a lot about how to create user interface elements using different layout managers and widgets such as TextView, ImageView, ProgressBar, and ListView.